openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,125 lines (1,124 loc) โข 48.8 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { g as readStringValue, l as normalizeOptionalString, m as readNonBlankString } from "./string-coerce-CIXf7egm.js";
import { a as writeRuntimeJson } from "./runtime-CF2WjnNZ.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { h as redactToolPayloadText, l as redactSecrets } from "./redact-BtvPPfTi.js";
import { d as pathExists } from "./fs-safe-B6pvPGnf.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { _ as resolveConfiguredAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { O as parseAgentSessionKey, f as resolveAgentIdFromSessionKey } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { r as readRegularFile } from "./regular-file-Equ4rrfE.js";
import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js";
import "./config-Cs0XXL3x.js";
import { t as ExpectedCliError } from "./failure-output-Dc5ce9_F.js";
import { i as hasMeaningfulRetiredMediaCarrier, t as PERSISTED_LEGACY_MEDIA_KEYS } from "./media-facts-8Sl_nPCc.js";
import { z as resolveSessionTranscriptReadTarget } from "./session-accessor-YsytfDtG.js";
import { b as loadTranscriptEvents } from "./session-accessor.sqlite-transcript-store-B0zw3fAU.js";
import { l as loadSessionEntry, u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { n as parseSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js";
import { c as scanSessionTranscriptTree, t as isCanonicalSessionTranscriptEntry } from "./transcript-tree-BH69pMSi.js";
import { a as resolveTrajectoryFilePath, i as TRAJECTORY_RUNTIME_FILE_MAX_BYTES, o as resolveTrajectoryPointerFilePath, s as safeTrajectorySessionFileName, t as TRAJECTORY_POINTER_FILE_MAX_BYTES } from "./paths-DBZSw_GC.js";
import { t as listSessionEntriesCore } from "./session-accessor.entry-CQ2WPpfn.js";
import { t as resolvePreferredSessionKeyForSessionIdMatches } from "./session-id-resolution-BbcM7Och.js";
import { n as redactSupportString } from "./diagnostic-support-redaction-DJtn51OP.js";
import { t as sanitizeDiagnosticPayload } from "./payload-redaction-DPHUXa81.js";
import { t as safeJsonStringify } from "./safe-json-CY5cd4H1.js";
import { r as loadSqliteTrajectoryRuntimeEvents } from "./runtime-store.sqlite-CvHzANNM.js";
import { n as parseSessionFileEntriesWithWarnings, t as isSessionFileEntry } from "./session-file-parser-CISggO0B.js";
import { a as writeSupportBundleDirectory, i as textSupportBundleFile, n as jsonlSupportBundleFile, r as supportBundleContents, t as jsonSupportBundleFile } from "./diagnostic-support-bundle-C-YIJlsI.js";
import { n as resolveExplicitSessionStorePath } from "./session-store-targets-BtyWd3X7.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/trajectory/runtime-file.ts
async function isRegularNonSymlinkFile(filePath) {
try {
const linkStat = await fs.lstat(filePath);
if (linkStat.isSymbolicLink() || !linkStat.isFile()) return false;
const stat = await fs.stat(filePath);
return stat.isFile() && stat.dev === linkStat.dev && stat.ino === linkStat.ino;
} catch {
return false;
}
}
async function readRuntimePointerFile(sessionFile, sessionId) {
const pointerPath = resolveTrajectoryPointerFilePath(sessionFile);
try {
const { buffer } = await readRegularFile({
filePath: pointerPath,
maxBytes: TRAJECTORY_POINTER_FILE_MAX_BYTES
});
const parsed = JSON.parse(buffer.toString("utf8"));
if (!isRecord(parsed)) return;
if (parsed.sessionId !== sessionId || typeof parsed.runtimeFile !== "string") return;
const runtimeFile = path.resolve(parsed.runtimeFile);
const safeRuntimeFileName = `${safeTrajectorySessionFileName(sessionId)}.jsonl`;
if (runtimeFile !== path.resolve(resolveTrajectoryFilePath({
env: {},
sessionFile,
sessionId
})) && path.basename(runtimeFile) !== safeRuntimeFileName) return;
return runtimeFile;
} catch {
return;
}
}
async function resolveTrajectoryRuntimeFile(params) {
if (params.runtimeFile) return params.runtimeFile;
const candidates = [
await readRuntimePointerFile(params.sessionFile, params.sessionId),
resolveTrajectoryFilePath({
env: {},
sessionFile: params.sessionFile,
sessionId: params.sessionId
}),
resolveTrajectoryFilePath({
sessionFile: params.sessionFile,
sessionId: params.sessionId
})
].filter((candidate) => Boolean(candidate));
for (const candidate of candidates) if (await isRegularNonSymlinkFile(candidate)) return candidate;
}
//#endregion
//#region src/trajectory/export.ts
const MAX_TRAJECTORY_RUNTIME_EVENTS = 2e5;
const MAX_TRAJECTORY_TOTAL_EVENTS = 25e4;
const MAX_TRAJECTORY_SESSION_FILE_BYTES = 52428800;
const MAX_TRAJECTORY_WARNING_ROWS = 20;
function normalizeCompleteSessionTarget(target) {
if (!target) return;
const agentId = normalizeOptionalString(target.agentId);
const sessionId = normalizeOptionalString(target.sessionId);
const sessionKey = normalizeOptionalString(target.sessionKey);
const storePath = normalizeOptionalString(target.storePath);
return agentId && sessionId && sessionKey && storePath ? {
agentId,
sessionId,
sessionKey,
storePath
} : void 0;
}
function isFiniteNumber(value) {
return typeof value === "number" && Number.isFinite(value);
}
function formatSessionParseWarnings(warnings) {
return warnings.map((warning) => ({
source: "session",
code: warning.code,
row: warning.row,
message: warning.code === "invalid-session-json" ? "Skipped a session JSONL row that is not valid JSON." : "Skipped a session JSONL row that is not a session entry object."
}));
}
function collectSessionEntries(rows, warnings = []) {
const entries = [];
const rowByEntry = /* @__PURE__ */ new Map();
for (const row of rows) {
if (!isSessionFileEntry(row.value)) {
warnings.push({
source: "session",
code: "invalid-session-row",
row: row.row,
message: "Skipped a session JSONL row that is not a session entry object."
});
continue;
}
entries.push(row.value);
rowByEntry.set(row.value, row.row);
}
return {
entries,
warnings,
rowByEntry
};
}
function migrateLegacySessionEntries(entries) {
const version = entries.find((entry) => entry.type === "session")?.version ?? 1;
if (version < 2) {
let previousId = null;
let index = 0;
for (const entry of entries) {
if (entry.type === "session") {
entry.version = 2;
continue;
}
const mutable = entry;
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.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.message;
if (message?.role === "hookMessage") message.role = "custom";
}
}
}
async function readSessionEntries(params) {
const completeTarget = normalizeCompleteSessionTarget(params.sessionTarget);
if (completeTarget) {
const targetKeyAgentId = parseAgentSessionKey(completeTarget.sessionKey)?.agentId;
const targetKeyEntry = loadSessionEntry({
agentId: completeTarget.agentId,
sessionKey: completeTarget.sessionKey,
storePath: completeTarget.storePath
});
if (completeTarget.sessionId !== params.sessionId || params.sessionKey !== void 0 && completeTarget.sessionKey !== params.sessionKey || targetKeyAgentId && targetKeyAgentId !== completeTarget.agentId || targetKeyEntry && targetKeyEntry.sessionId !== completeTarget.sessionId) throw new Error("Trajectory export transcript target does not match the requested session");
return collectSessionEntries((await loadTranscriptEvents({
agentId: completeTarget.agentId,
sessionId: completeTarget.sessionId,
sessionKey: completeTarget.sessionKey,
storePath: completeTarget.storePath
})).map((value, index) => ({
row: index + 1,
value
})));
}
const incompleteTarget = params.sessionTarget ? {
agentId: normalizeOptionalString(params.sessionTarget.agentId),
sessionId: normalizeOptionalString(params.sessionTarget.sessionId),
sessionKey: normalizeOptionalString(params.sessionTarget.sessionKey),
storePath: normalizeOptionalString(params.sessionTarget.storePath)
} : void 0;
if (!params.sessionFile) throw new Error("Trajectory export requires a transcript identity or artifact file");
const marker = parseSqliteSessionFileMarker(params.sessionFile);
if (!marker) {
const { entries, warnings, rowByEntry } = parseSessionFileEntriesWithWarnings(await fs.readFile(params.sessionFile, "utf8"));
return {
entries,
warnings: formatSessionParseWarnings(warnings),
rowByEntry
};
}
if (marker.sessionId !== params.sessionId) throw new Error("Trajectory export legacy marker does not match the requested session");
const targetKeyAgentId = parseAgentSessionKey(incompleteTarget?.sessionKey)?.agentId;
const targetKeyEntry = incompleteTarget?.sessionKey && marker ? loadSessionEntry({
agentId: marker.agentId,
sessionKey: incompleteTarget.sessionKey,
storePath: marker.storePath
}) : void 0;
if (incompleteTarget && (incompleteTarget.agentId && incompleteTarget.agentId !== marker.agentId || incompleteTarget.sessionId && incompleteTarget.sessionId !== marker.sessionId || targetKeyAgentId && targetKeyAgentId !== marker.agentId || incompleteTarget.sessionKey && targetKeyEntry?.sessionId !== marker.sessionId || incompleteTarget.storePath && path.resolve(incompleteTarget.storePath) !== path.resolve(marker.storePath))) throw new Error("Trajectory export transcript target conflicts with the legacy marker");
const suppliedKeyEntry = params.sessionKey ? loadSessionEntry({
agentId: marker.agentId,
sessionKey: params.sessionKey,
storePath: marker.storePath
}) : void 0;
const markerMatches = listSessionEntriesCore({
agentId: marker.agentId,
storePath: marker.storePath
}).filter(({ entry }) => entry.sessionId === marker.sessionId);
if (suppliedKeyEntry && suppliedKeyEntry.sessionId !== marker.sessionId) throw new Error("Trajectory export session key conflicts with the legacy marker");
if (params.sessionKey && !suppliedKeyEntry && markerMatches.length > 0) throw new Error("Trajectory export session key is not mapped to the legacy marker");
const markerSessionKey = suppliedKeyEntry ? params.sessionKey : resolvePreferredSessionKeyForSessionIdMatches(markerMatches.map(({ sessionKey, entry }) => [sessionKey, entry]), marker.sessionId) ?? (markerMatches.length === 0 ? params.sessionKey : void 0);
if (!markerSessionKey && markerMatches.length > 0) throw new Error("Trajectory export legacy marker session key is ambiguous");
return collectSessionEntries((await loadTranscriptEvents({
agentId: marker.agentId,
sessionId: marker.sessionId,
...markerSessionKey ? { sessionKey: markerSessionKey } : {},
storePath: marker.storePath
})).map((value, index) => ({
row: index + 1,
value
})));
}
async function readSessionBranch(params) {
const { entries: fileEntries, warnings, rowByEntry } = await readSessionEntries(params);
migrateLegacySessionEntries(fileEntries);
const header = fileEntries.find((entry) => entry.type === "session") ?? null;
const entries = fileEntries.filter((entry) => entry.type !== "session" && isCanonicalSessionTranscriptEntry(entry) && typeof entry.id === "string");
const tree = scanSessionTranscriptTree(fileEntries);
if (!tree.hasLeafUpdate) return {
header,
leafId: entries.at(-1)?.id ?? null,
branchEntries: entries,
warnings
};
const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
const branchEntries = [];
const seen = /* @__PURE__ */ new Set();
let descendantEntry;
let currentId = tree.leafId;
while (currentId) {
if (seen.has(currentId)) {
const cycleEntry = tree.byId.get(currentId)?.entry;
warnings.push({
source: "session",
code: "cyclic-session-branch",
row: cycleEntry ? rowByEntry.get(cycleEntry) ?? 0 : 0,
message: "Stopped trajectory session branch export at a cyclic parent link."
});
break;
}
seen.add(currentId);
const current = tree.byId.get(currentId);
if (!current) {
warnings.push({
source: "session",
code: "incomplete-session-branch",
row: 0,
message: "Exported the reachable session branch suffix after a missing parent link."
});
break;
}
const visibleEntry = entriesById.get(currentId);
if (visibleEntry) {
const normalizedEntry = {
...visibleEntry,
parentId: current.parentId
};
if (descendantEntry) descendantEntry.parentId = normalizedEntry.id;
branchEntries.unshift(normalizedEntry);
descendantEntry = normalizedEntry;
}
if (current.parentId && !tree.byId.has(current.parentId)) {
warnings.push({
source: "session",
code: "incomplete-session-branch",
row: rowByEntry.get(current.entry) ?? 0,
message: "Exported the reachable session branch suffix after a missing parent link."
});
break;
}
currentId = current.parentId;
}
return {
header,
leafId: tree.leafId,
branchEntries,
warnings
};
}
async function parseJsonlFile(filePath, params) {
let stat;
try {
stat = await fs.stat(filePath);
} catch (error) {
if (error.code === "ENOENT") return {
events: [],
warnings: []
};
throw error;
}
if (!stat.isFile()) return {
events: [],
warnings: []
};
if (stat.size > params.maxBytes) throw new Error(`Trajectory runtime file is too large to export (${stat.size} bytes; limit ${params.maxBytes})`);
const rows = (await fs.readFile(filePath, "utf8")).split(/\r?\n/u);
const parsed = [];
const warnings = [];
for (const [index, rawLine] of rows.entries()) {
const row = rawLine.trim();
if (!row) continue;
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);
if (!params.validate || params.validate(value)) {
const typedValue = value;
if (!params.include || params.include(typedValue)) parsed.push(typedValue);
} else warnings.push({
source: "runtime",
code: "invalid-runtime-event",
row: index + 1,
message: "Skipped a runtime trajectory JSONL row that does not match the session schema."
});
} catch {
warnings.push({
source: "runtime",
code: "invalid-runtime-json",
row: index + 1,
message: "Skipped a runtime trajectory JSONL row that is not valid JSON."
});
}
}
return {
events: parsed,
warnings
};
}
async function readRuntimeTrajectoryEvents(params) {
const marker = normalizeCompleteSessionTarget(params.sessionTarget) ?? parseSqliteSessionFileMarker(params.sessionFile);
if (marker && marker.sessionId !== params.sessionId) throw new Error("Trajectory runtime target does not match the requested session");
if (marker) {
const events = await loadSqliteTrajectoryRuntimeEvents({
agentId: marker.agentId,
sessionId: marker.sessionId,
storePath: marker.storePath
});
if (events.length > MAX_TRAJECTORY_RUNTIME_EVENTS) throw new Error(`Trajectory runtime store has too many events to export (limit ${MAX_TRAJECTORY_RUNTIME_EVENTS})`);
return {
events,
warnings: []
};
}
if (!params.sessionFile) return {
events: [],
warnings: []
};
const runtimeFile = await resolveTrajectoryRuntimeFile({
runtimeFile: params.runtimeFile,
sessionFile: params.sessionFile,
sessionId: params.sessionId
});
if (!runtimeFile) return {
events: [],
warnings: []
};
return {
...await parseJsonlFile(runtimeFile, {
maxBytes: TRAJECTORY_RUNTIME_FILE_MAX_BYTES,
maxEvents: MAX_TRAJECTORY_RUNTIME_EVENTS,
include: (value) => value.sessionId === params.sessionId,
validate: isRuntimeTrajectoryEvent
}),
runtimeFile
};
}
function isRuntimeTrajectoryEvent(value) {
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.isFinite(Date.parse(value.ts)) && isFiniteNumber(value.seq) && typeof value.sessionId === "string" && (!("data" in value) || value.data === void 0 || isRecord(value.data));
}
function summarizeJsonlWarnings(warnings) {
const byKey = /* @__PURE__ */ new Map();
for (const warning of warnings) {
const key = `${warning.source}:${warning.code}`;
const existing = byKey.get(key);
if (existing) {
existing.count += 1;
if (existing.rows.length < MAX_TRAJECTORY_WARNING_ROWS) existing.rows.push(warning.row);
continue;
}
byKey.set(key, {
source: warning.source,
code: warning.code,
count: 1,
rows: [warning.row],
message: warning.message
});
}
return [...byKey.values()];
}
function normalizeTimestamp(value) {
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 (/* @__PURE__ */ new Date(0)).toISOString();
}
function resolveMessageEventType(message) {
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) {
if (message.role !== "assistant" || !Array.isArray(message.content)) return [];
return message.content.flatMap((block, index) => {
if (!block || typeof block !== "object") return [];
const typedBlock = block;
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 : void 0,
name: typeof typedBlock.name === "string" ? typedBlock.name : void 0,
arguments: typedBlock.arguments ?? typedBlock.input ?? typedBlock.parameters,
index
}];
});
}
function sanitizeTrajectoryExportValue(value) {
return redactSecrets(sanitizeDiagnosticPayload(value));
}
function buildTranscriptEvents(params) {
const events = [];
let seq = 0;
for (const entry of params.entries) {
const push = (type, data) => {
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 "reset":
push("session.reset", {
reason: entry.reason,
firstKeptEntryId: entry.firstKeptEntryId
});
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 });
}
}
return events;
}
function assertCanonicalTrajectoryInputs(entries, runtimeEvents) {
const branchHasLegacy = entries.some((entry) => entry.type === "message" && isRecord(entry.message) && (Object.hasOwn(entry.message, "media") || PERSISTED_LEGACY_MEDIA_KEYS.some((key) => Object.hasOwn(entry.message, key))));
const runtimeHasLegacy = runtimeEvents.some((event) => Array.isArray(event.data?.messagesSnapshot) && event.data.messagesSnapshot.some((message) => isRecord(message) && hasMeaningfulRetiredMediaCarrier(message)));
if (branchHasLegacy || runtimeHasLegacy) throw new Error("Trajectory export input contains retired top-level media fields; migrate the source transcript before exporting.");
}
function sortTrajectoryEvents(events) {
const sourceOrder = {
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, events) {
const lines = events.map((event) => safeJsonStringify(event)).filter((line) => Boolean(line));
return jsonlSupportBundleFile(pathName, lines);
}
function redactTrajectoryBundleFileContent(file) {
return {
...file,
content: redactToolPayloadText(file.content)
};
}
function buildTrajectoryExportRedaction(params) {
const env = process.env;
return {
env,
stateDir: resolveStateDir(env),
workspaceDir: path.resolve(params.workspaceDir)
};
}
function redactWorkspacePathString(value, redaction) {
const workspaceDir = redaction.workspaceDir;
if (!workspaceDir) return value;
const normalizedWorkspaceDir = workspaceDir.replaceAll("\\", "/");
let next = value;
for (const candidate of /* @__PURE__ */ 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, redaction) {
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, redaction) {
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;
const next = {};
for (const [key, entry] of Object.entries(record)) next[key] = redactLocalPathValues(entry, redaction);
return next;
}
function uniqueRedactedObjectKey(key, usedKeys) {
if (!usedKeys.has(key)) {
usedKeys.add(key);
return key;
}
let index = 2;
while (usedKeys.has(`${key}#${index}`)) index += 1;
const unique = `${key}#${index}`;
usedKeys.add(unique);
return unique;
}
function redactTrajectoryExportObjectKeys(value, redaction) {
if (Array.isArray(value)) return value.map((entry) => redactTrajectoryExportObjectKeys(entry, redaction));
if (!value || typeof value !== "object") return value;
const usedKeys = /* @__PURE__ */ new Set();
const next = {};
for (const [key, entry] of Object.entries(value)) {
const redactedKey = redactToolPayloadText(maybeRedactPathString(key, redaction));
next[uniqueRedactedObjectKey(redactedKey, usedKeys)] = redactTrajectoryExportObjectKeys(entry, redaction);
}
return next;
}
function redactTrajectoryExportValue(value, redaction) {
return redactTrajectoryExportObjectKeys(sanitizeTrajectoryExportValue(redactLocalPathValues(value, redaction)), redaction);
}
function redactEventForExport(event, redaction) {
return redactTrajectoryExportValue(event, redaction);
}
function resolveRuntimeContext(runtimeEvents) {
const runtimeData = runtimeEvents.findLast((event) => event.type === "context.compiled")?.data;
const toolsValue = Array.isArray(runtimeData?.tools) ? runtimeData.tools : void 0;
return {
systemPrompt: typeof runtimeData?.systemPrompt === "string" ? runtimeData.systemPrompt : void 0,
tools: toolsValue
};
}
function resolveLatestRuntimeEventData(runtimeEvents, type) {
return runtimeEvents.findLast((candidate) => candidate.type === type)?.data;
}
function normalizePathForMatch(value) {
return value.replaceAll("\\", "/").trim().toLowerCase();
}
function collectPotentialPathStrings(value) {
const found = /* @__PURE__ */ new Set();
const visit = (input) => {
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) {
if (!params.skills || typeof params.skills !== "object") return params.skills;
const skillsRecord = params.skills;
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 : void 0;
const normalizedPath = rawPath ? normalizePathForMatch(rawPath) : void 0;
const skillDirName = rawPath?.replaceAll("\\", "/").split("/").slice(-2, -1)[0]?.toLowerCase() ?? void 0;
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) {
const runtimeMetadata = resolveLatestRuntimeEventData(params.runtimeEvents, "trace.metadata");
if (!runtimeMetadata) return;
const modelFallback = (() => {
const latest = params.runtimeEvents.findLast((event) => event.provider || event.modelId || event.modelApi);
if (!latest?.provider && !latest?.modelId && !latest?.modelApi) return;
return {
provider: latest.provider,
name: latest.modelId,
api: latest.modelApi
};
})();
return {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
generatedAt: (/* @__PURE__ */ 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) {
const cohortStart = params.runtimeEvents.findLastIndex((event) => event.type === "session.started");
const selectedEnd = (cohortStart < 0 ? params.runtimeEvents.filter((event) => event.type === "session.ended" && isFiniteNumber(event.data?.startedAt)).toSorted((left, right) => Number(left.data?.startedAt) - Number(right.data?.startedAt)).at(-1) : void 0) ?? (cohortStart < 0 ? params.runtimeEvents.findLast((event) => event.type === "session.ended") : void 0);
const cohortRunId = params.runtimeEvents[cohortStart]?.runId ?? selectedEnd?.runId ?? params.runtimeEvents.at(-1)?.runId;
const cohortEnd = selectedEnd ? params.runtimeEvents.lastIndexOf(selectedEnd) + 1 : params.runtimeEvents.length;
const partialStart = selectedEnd ? params.runtimeEvents.findLastIndex((event, index) => index < cohortEnd - 1 && event.type === "session.ended" && event.runId === cohortRunId) + 1 : cohortStart;
const cohort = params.runtimeEvents.slice(Math.max(0, partialStart), cohortEnd).filter((event) => cohortRunId === void 0 || event.runId === cohortRunId);
const runtimeArtifacts = resolveLatestRuntimeEventData(cohort, "trace.artifacts");
const runtimeCompletion = resolveLatestRuntimeEventData(cohort, "model.completed");
const runtimeEnd = resolveLatestRuntimeEventData(cohort, "session.ended");
if (!runtimeArtifacts && !runtimeCompletion && !runtimeEnd) return;
return {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
generatedAt: (/* @__PURE__ */ 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,
timedOutDuringToolExecution: runtimeArtifacts?.timedOutDuringToolExecution ?? runtimeEnd?.timedOutDuringToolExecution,
timedOutByRunBudget: runtimeArtifacts?.timedOutByRunBudget ?? runtimeEnd?.timedOutByRunBudget,
promptError: runtimeArtifacts?.promptError ?? runtimeEnd?.promptError ?? runtimeCompletion?.promptError,
promptErrorSource: runtimeArtifacts?.promptErrorSource ?? runtimeCompletion?.promptErrorSource,
terminalError: runtimeArtifacts?.terminalError ?? runtimeEnd?.terminalError ?? runtimeCompletion?.terminalError,
usage: runtimeArtifacts?.usage ?? runtimeCompletion?.usage,
promptCache: runtimeArtifacts?.promptCache ?? runtimeCompletion?.promptCache,
compactionCount: runtimeArtifacts?.compactionCount ?? runtimeCompletion?.compactionCount,
assistantTexts: runtimeArtifacts?.assistantTexts ?? runtimeCompletion?.assistantTexts,
stopReason: runtimeArtifacts?.stopReason ?? runtimeCompletion?.stopReason ?? runtimeEnd?.stopReason,
finalPromptText: runtimeArtifacts?.finalPromptText ?? runtimeCompletion?.finalPromptText,
finalPromptTextOriginalLength: runtimeArtifacts?.finalPromptTextOriginalLength ?? runtimeCompletion?.finalPromptTextOriginalLength,
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) {
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) => typeof prompt === "string");
const systemPrompt = (typeof latestCompiled?.systemPrompt === "string" ? latestCompiled.systemPrompt : void 0) ?? params.runtimeContext.systemPrompt;
const skillsPrompt = runtimeMetadata?.prompting && typeof runtimeMetadata.prompting === "object" && typeof runtimeMetadata.prompting.skillsPrompt === "string" ? runtimeMetadata.prompting.skillsPrompt : void 0;
const userPromptPrefixText = runtimeMetadata?.prompting && typeof runtimeMetadata.prompting === "object" && typeof runtimeMetadata.prompting.userPromptPrefixText === "string" ? runtimeMetadata.prompting.userPromptPrefixText : void 0;
const promptReport = runtimeMetadata?.prompting && typeof runtimeMetadata.prompting === "object" && typeof runtimeMetadata.prompting.systemPromptReport === "object" ? runtimeMetadata.prompting.systemPromptReport : void 0;
if (!systemPrompt && submittedPrompts.length === 0 && !skillsPrompt && !userPromptPrefixText) return;
return {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
generatedAt: (/* @__PURE__ */ 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
};
}
function resolveDefaultTrajectoryExportDir(params) {
const timestamp = (params.now ?? /* @__PURE__ */ 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}`);
}
async function exportTrajectoryBundle(params) {
const redaction = buildTrajectoryExportRedaction({ workspaceDir: params.workspaceDir });
const sessionTarget = normalizeCompleteSessionTarget(params.sessionTarget);
if (params.sessionFile && !sessionTarget && !parseSqliteSessionFileMarker(params.sessionFile)) {
const sessionStat = await fs.stat(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, warnings: sessionWarnings } = await readSessionBranch({
sessionFile: params.sessionFile,
sessionTarget: params.sessionTarget,
sessionId: params.sessionId,
...params.sessionKey ? { sessionKey: params.sessionKey } : {}
});
const runtimeParse = await readRuntimeTrajectoryEvents({
runtimeFile: params.runtimeFile,
sessionFile: params.sessionFile,
sessionTarget,
sessionId: params.sessionId
});
const runtimeFile = runtimeParse.runtimeFile;
const runtimeEvents = runtimeParse.events;
assertCanonicalTrajectoryInputs(branchEntries, runtimeEvents);
const projectedBranchEntries = branchEntries;
const transcriptEvents = buildTranscriptEvents({
entries: projectedBranchEntries,
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 = {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
generatedAt: (/* @__PURE__ */ 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(sessionTarget?.sessionKey ?? params.sessionFile ?? params.sessionId, redaction),
runtime: runtimeFile && await isRegularNonSymlinkFile(runtimeFile) ? maybeRedactPathString(runtimeFile, redaction) : void 0
}
};
const warnings = summarizeJsonlWarnings([...sessionWarnings, ...runtimeParse.warnings]);
if (warnings.length > 0) manifest.warnings = warnings;
const bundleRuntimeContext = resolveRuntimeContext(runtimeEvents);
const files = [];
const supplementalFiles = [];
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", redactTrajectoryExportValue(metadataCapture, redaction)));
supplementalFiles.push("metadata.json");
}
if (artifactsCapture) {
files.push(jsonSupportBundleFile("artifacts.json", redactTrajectoryExportValue(artifactsCapture, redaction)));
supplementalFiles.push("artifacts.json");
}
if (promptsCapture) {
files.push(jsonSupportBundleFile("prompts.json", redactTrajectoryExportValue(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", redactTrajectoryExportValue({
header,
leafId,
entries: projectedBranchEntries
}, redaction)));
if (bundleRuntimeContext.systemPrompt) files.push(textSupportBundleFile("system-prompt.txt", redactTrajectoryExportValue(bundleRuntimeContext.systemPrompt, redaction)));
if (bundleRuntimeContext.tools) files.push(jsonSupportBundleFile("tools.json", redactTrajectoryExportValue(bundleRuntimeContext.tools, redaction)));
const redactedFiles = files.map(redactTrajectoryBundleFileContent);
manifest.contents = [...supportBundleContents(redactedFiles)];
const redactedManifest = redactTrajectoryExportValue(manifest, redaction);
const manifestFile = redactTrajectoryBundleFileContent(jsonSupportBundleFile("manifest.json", redactedManifest));
await writeSupportBundleDirectory({
outputDir: params.outputDir,
files: [manifestFile, ...redactedFiles]
});
return {
manifest: redactedManifest,
outputDir: params.outputDir,
events,
header,
runtimeFile: runtimeFile && await isRegularNonSymlinkFile(runtimeFile) ? runtimeFile : void 0,
supplementalFiles
};
}
//#endregion
//#region src/trajectory/command-export.ts
async function validateExistingExportDirectory(params) {
const linkStat = await fs.lstat(params.dir);
if (linkStat.isSymbolicLink() || !linkStat.isDirectory()) throw new Error(`${params.label} must be a real directory inside the workspace`);
const realDir = await fs.realpath(params.dir);
if (!isPathInside(params.realWorkspace, realDir)) throw new Error("Trajectory exports directory must stay inside the workspace");
return realDir;
}
async function mkdirIfMissingThenValidate(params) {
try {
await fs.mkdir(params.dir, { mode: 448 });
} catch (error) {
if (error.code !== "EEXIST") throw error;
}
return await validateExistingExportDirectory(params);
}
async function resolveTrajectoryExportBaseDir(workspaceDir) {
const workspacePath = path.resolve(workspaceDir);
const realWorkspace = await fs.realpath(workspacePath);
const stateDir = path.join(workspacePath, ".openclaw");
await mkdirIfMissingThenValidate({
dir: stateDir,
label: "OpenClaw state directory",
realWorkspace
});
const baseDir = path.join(stateDir, "trajectory-exports");
const realBase = await mkdirIfMissingThenValidate({
dir: baseDir,
label: "Trajectory exports directory",
realWorkspace
});
return {
baseDir: path.resolve(baseDir),
realBase
};
}
async function resolveTrajectoryCommandOutputDir(params) {
const { baseDir, realBase } = await resolveTrajectoryExportBaseDir(params.workspaceDir);
const raw = params.outputPath?.trim();
if (!raw) {
const defaultDir = resolveDefaultTrajectoryExportDir({
workspaceDir: params.workspaceDir,
sessionId: params.sessionId
});
return path.join(baseDir, path.basename(defaultDir));
}
if (path.isAbsolute(raw) || raw.startsWith("~")) throw new Error("Output path must be relative to the workspace trajectory exports directory");
const resolvedBase = path.resolve(baseDir);
const outputDir = path.resolve(resolvedBase, raw);
const relative = path.relative(resolvedBase, outputDir);
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("Output path must stay inside the workspace trajectory exports directory");
let existingParent = outputDir;
while (!await pathExists(existingParent)) {
const next = path.dirname(existingParent);
if (next === existingParent) break;
existingParent = next;
}
const realExistingParent = await fs.realpath(existingParent);
if (!isPathInside(realBase, realExistingParent)) throw new Error("Output path must stay inside the real trajectory exports directory");
return outputDir;
}
async function exportTrajectoryForCommand(params) {
const bundle = await exportTrajectoryBundle({
outputDir: params.outputDir ?? await resolveTrajectoryCommandOutputDir({
outputPath: params.outputPath,
workspaceDir: params.workspaceDir,
sessionId: params.sessionId
}),
sessionFile: params.sessionFile,
sessionTarget: params.sessionTarget,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir
});
const relativePath = path.relative(params.workspaceDir, bundle.outputDir);
const displayPath = relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath) ? relativePath : path.basename(bundle.outputDir);
const files = [
"manifest.json",
"events.jsonl",
"session-branch.json"
];
if (bundle.events.some((event) => event.type === "context.compiled")) files.push("system-prompt.txt", "tools.json");
files.push(...bundle.supplementalFiles);
return {
outputDir: bundle.outputDir,
displayPath,
sessionId: params.sessionId,
eventCount: bundle.manifest.eventCount,
runtimeEventCount: bundle.manifest.runtimeEventCount,
transcriptEventCount: bundle.manifest.transcriptEventCount,
files
};
}
function formatTrajectoryCommandExportSummary(summary) {
return [
"โ
Trajectory exported!",
"",
`๐ฆ Bundle: ${summary.displayPath}`,
`๐งต Session: ${summary.sessionId}`,
`๐ Events: ${summary.eventCount}`,
`๐งช Runtime events: ${summary.runtimeEventCount}`,
`๐ Transcript events: ${summary.transcriptEventCount}`,
`๐ Files: ${summary.files.join(", ")}`
].join("\n");
}
//#endregion
//#region src/commands/export-trajectory.ts
/** CLI command for exporting a session transcript as a trajectory artifact. */
const ENCODED_EXPORT_REQUEST_RE = /^[A-Za-z0-9_-]{1,65536}$/u;
function decodeExportTrajectoryRequest(encoded) {
if (!ENCODED_EXPORT_REQUEST_RE.test(encoded)) throw new Error("Encoded trajectory export request is invalid");
const bytes = Buffer.from(encoded, "base64url");
if (bytes.toString("base64url") !== encoded) throw new Error("Encoded trajectory export request is invalid");
let decoded;
try {
decoded = JSON.parse(bytes.toString("utf8"));
} catch {
throw new Error("Encoded trajectory export request is invalid JSON");
}
if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) throw new Error("Encoded trajectory export request must be a JSON object");
const request = decoded;
const opts = {};
const sessionKey = readNonBlankString(request.sessionKey);
if (sessionKey !== void 0) opts.sessionKey = sessionKey;
const output = readNonBlankString(request.output);
if (output !== void 0) opts.output = output;
const store = readStringValue(request.store);
if (store !== void 0) opts.store = store;
const agent = readStringValue(request.agent);
if (agent !== void 0) opts.agent = agent;
const workspace = readNonBlankString(request.workspace);
if (workspace !== void 0) opts.workspace = workspace;
return opts;
}
function resolveExportTrajectoryOptions(opts) {
const encoded = opts.requestJsonBase64;
if (encoded === void 0 || encoded.length === 0) return opts;
return {
...opts,
...decodeExportTrajectoryRequest(encoded)
};
}
function throwTrajectoryExportError(message) {
throw new ExpectedCliError({
message,
humanOutput: message,
machineOutput: message
});
}
/** Resolves the requested session and exports its trajectory summary or JSON result. */
async function exportTrajectoryCommand(opts, runtime) {
let resolvedOpts;
try {
resolvedOpts = resolveExportTrajectoryOptions(opts);
} catch (error) {
throwTrajectoryExportError(`Failed to decode trajectory export request: ${formatErrorMessage(error)}`);
}
const sessionKey = resolvedOpts.sessionKey?.trim();
if (!sessionKey) throwTrajectoryExportError(`--session-key is required. Run ${formatCliCommand("openclaw sessions")} to choose a session.`);
const requestedAgent = resolvedOpts.agent?.trim();
if (resolvedOpts.agent !== void 0 && !requestedAgent) throwTrajectoryExportError("--agent must not be blank");
if (resolvedOpts.store !== void 0 && !resolvedOpts.store.trim()) throwTrajectoryExportError("--store must not be blank");
let targetAgentId;
try {
targetAgentId = requestedAgent ? resolveConfiguredAgentId(getRuntimeConfig(), requestedAgent) : resolveAgentIdFromSessionKey(sessionKey);
} catch (error) {
throwTrajectoryExportError(formatErrorMessage(error));
}
let storePath = resolvedOpts.store ? resolveSessionStorePathCore(resolvedOpts.store, { agentId: targetAgentId }) : resolveSessionStorePathCore(getRuntimeConfig().session?.store, { agentId: targetAgentId });
if (resolvedOpts.store) try {
storePath = resolveExplicitSessionStorePath({
storePath,
inputStorePath: resolvedOpts.store,
agentId: targetAgentId
});
} catch (error) {
throwTrajectoryExportError(formatErrorMessage(error));
}
const entry = loadSessionEntryReadOnly({
agentId: targetAgentId,
sessionKey,
storePath
});
if (!entry?.sessionId) throwTrajectoryExportError(`Session not found: ${sessionKey}. Run ${formatCliCommand("openclaw sessions")} to see available sessions.`);
let sessionTarget;
try {
sessionTarget = resolveSessionTranscriptReadTarget({
agentId: targetAgentId,
sessionEntry: entry,
sessionId: entry.sessionId,
sessionKey,
storePath
});
} catch (error) {
throwTrajectoryExportError(`Failed to resolve session file: ${formatErrorMessage(error)}`);
}
let summary;
try {
summary = await exportTrajectoryForCommand({
outputPath: resolvedOpts.output,
sessionTarget: {
agentId: sessionTarget.agentId ?? targetAgentId,
sessionId: sessionTarget.sessionId,
sessionKey: sessionTarget.sessionKey ?? sessionKey,
storePath: sessionTarget.storePath
},
sessionId: entry.sessionId,
sessionKey,
workspaceDir: path.resolve(resolvedOpts.workspace ?? process.cwd())
});
} catch (error) {
throwTrajectoryExportError(`Failed to export trajectory: ${formatErrorMessage(error)}`);
}
if (resolvedOpts.json) {
writeRuntimeJson(runtime, summary);
return;
}
runtime.log(formatTrajectoryCommandExportSummary(summary));
}
//#endregion
export { exportTrajectoryCommand };