openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
229 lines (228 loc) • 8.71 kB
JavaScript
import { C as parseStrictNonNegativeInteger } from "./number-coercion-CLj0HTDM.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { f as resolveAgentIdFromSessionKey } from "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js";
import "./config-Cs0XXL3x.js";
import "./session-accessor-YsytfDtG.js";
import { a as listSessionEntriesReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { a as resolveStoredSessionKeyForAgentStore } from "./session-store-key-8xEjWSNi.js";
import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent, o as classifyAgentRunTerminalOutcome } from "./agent-run-terminal-outcome-CigeY75d.js";
import { r as readAcpSessionMeta } from "./session-meta-dzG4VkYo.js";
import { n as loadSqliteTrajectoryRuntimeEventRowsSync } from "./runtime-store.sqlite-CvHzANNM.js";
import { t as formatTextCell } from "./text-format-C-twF_E2.js";
import { t as resolveCommandSessionStoreTargets } from "./session-store-targets-BtyWd3X7.js";
//#region src/commands/sessions-tail.ts
const DEFAULT_TAIL_COUNT = 80;
const SESSION_KEY_PAD = 30;
const EVENT_TYPE_PAD = 16;
const FOLLOW_INTERVAL_MS = 1e3;
function parseTailCount(value) {
if (value === void 0) return DEFAULT_TAIL_COUNT;
return parseStrictNonNegativeInteger(value) ?? null;
}
function formatTimestamp(ts) {
const date = new Date(ts);
if (Number.isNaN(date.getTime())) return "--:--:--";
return date.toISOString().slice(11, 19);
}
function toolName(data) {
return normalizeOptionalString(data?.name) ?? normalizeOptionalString(data?.toolName) ?? "tool";
}
function resultStatus(data) {
if (data?.success === true) return "ok";
if (data?.success === false || data?.isError === true) return "error";
return normalizeOptionalString(data?.status) ?? "done";
}
function modelCompletionStatus(data) {
const outcome = buildAgentRunTerminalOutcomeFromLifecycleEvent({
phase: "end",
data: {
...data,
stopReason: data?.timedOut === true ? "timeout" : data?.stopReason
}
});
return {
success: data?.promptError || data?.promptErrorSource || data?.terminalError ? "error" : "done",
failure: "error",
timeout: "timeout",
cancellation: "aborted"
}[classifyAgentRunTerminalOutcome(outcome)];
}
function safePreview(event) {
const data = event.data;
switch (event.type) {
case "session.started": return "session started";
case "context.compiled": {
const tools = Array.isArray(data?.tools) ? data.tools.length : void 0;
return tools === void 0 ? "context compiled" : `context compiled (${tools} tools)`;
}
case "prompt.submitted": return "prompt submitted";
case "prompt.skipped": {
const reason = normalizeOptionalString(data?.reason);
return `prompt skipped${reason ? `: ${reason}` : ""}`;
}
case "tool.call": return `${toolName(data)} {...redacted...}`;
case "tool.timeout": return `${toolName(data)} timeout`;
case "tool.result": return `${toolName(data)} ${resultStatus(data)}`;
case "model.completed": {
const model = [event.provider?.trim(), event.modelId?.trim()].filter(Boolean).join("/");
const status = modelCompletionStatus(data);
return model ? `${model} ${status}` : status;
}
case "session.ended": return normalizeOptionalString(data?.status) ?? "ended";
case "trace.truncated": return "trajectory truncated";
default: return normalizeOptionalString(data?.status) ?? normalizeOptionalString(data?.name) ?? "";
}
}
function formatProgressLine(event) {
const sessionKey = event.sessionKey ?? event.sessionId;
const sessionLabel = formatTextCell(sanitizeTerminalText(sessionKey), SESSION_KEY_PAD);
const typeLabel = formatTextCell(sanitizeTerminalText(event.type), EVENT_TYPE_PAD);
const preview = safePreview(event);
return [
formatTimestamp(event.ts),
typeLabel,
sessionLabel,
preview
].join(" ").trimEnd();
}
function readTailSnapshot(selection, tailEvents) {
const rows = loadSqliteTrajectoryRuntimeEventRowsSync({
agentId: selection.agentId,
sessionId: selection.sessionId,
storePath: selection.storePath,
tailEvents
});
return {
events: rows.map((row) => row.event),
maxStorageSeq: rows.at(-1)?.seq ?? -1
};
}
function renderEvents(events, runtime) {
for (const event of events) runtime.log(formatProgressLine(event));
}
function isRunningSession(selection) {
const cfg = getRuntimeConfig();
const acpMeta = readAcpSessionMeta({ sessionKey: resolveStoredSessionKeyForAgentStore({
cfg,
agentId: selection.agentId,
sessionKey: selection.key
}) });
return selection.entry.status === "running" || acpMeta?.state === "running";
}
function compareSelectionsByUpdatedAt(a, b) {
return (b.entry.updatedAt ?? 0) - (a.entry.updatedAt ?? 0);
}
function buildTailSelection(params) {
const sessionId = params.entry.sessionId?.trim();
return sessionId ? {
...params,
sessionId
} : null;
}
function selectSessionsToTail(selections, sessionKey) {
const requested = sessionKey?.trim();
if (requested) return selections.filter((selection) => selection.key === requested);
const running = selections.filter((selection) => isRunningSession(selection));
if (running.length > 0) return running.toSorted(compareSelectionsByUpdatedAt);
const latest = selections.toSorted(compareSelectionsByUpdatedAt)[0];
return latest ? [latest] : [];
}
function readNewSqliteFollowEvents(state) {
const rows = loadSqliteTrajectoryRuntimeEventRowsSync({
agentId: state.selection.agentId,
afterSeq: state.lastStorageSeq,
sessionId: state.selection.sessionId,
storePath: state.selection.storePath
});
if (rows.length === 0) return [];
state.lastStorageSeq = rows.at(-1)?.seq ?? state.lastStorageSeq;
return rows.map((row) => row.event);
}
function followSelections(selections, runtime, initialSnapshots) {
const states = selections.map((selection) => {
return {
lastStorageSeq: initialSnapshots.get(selection)?.maxStorageSeq ?? -1,
selection
};
});
return new Promise((resolve) => {
let finished = false;
const interval = setInterval(() => {
for (const state of states) try {
renderEvents(readNewSqliteFollowEvents(state), runtime);
} catch (error) {
runtime.error(`Failed to read trajectory progress for ${state.selection.key}: ${formatErrorMessage(error)}`);
return finish("ERROR");
}
}, FOLLOW_INTERVAL_MS);
const finish = (outcome) => {
if (!finished) {
finished = true;
clearInterval(interval);
process.off("SIGINT", stopSigint);
process.off("SIGTERM", stopSigterm);
resolve(outcome);
}
};
const stopSigint = () => finish("SIGINT");
const stopSigterm = () => finish("SIGTERM");
process.once("SIGINT", stopSigint);
process.once("SIGTERM", stopSigterm);
});
}
function resolveTailTargetAgent(opts) {
if (opts.agent !== void 0 || opts.store !== void 0 || opts.allAgents === true) return opts.agent;
return opts.sessionKey?.trim() ? resolveAgentIdFromSessionKey(opts.sessionKey) : void 0;
}
/** Tails recent trajectory events for the selected session(s). */
async function sessionsTailCommand(opts, runtime) {
const tailCount = parseTailCount(opts.tail);
if (tailCount === null) {
runtime.error("--tail must be a non-negative integer, for example --tail 25.");
runtime.exit(1);
return;
}
const cfg = getRuntimeConfig();
const targets = resolveCommandSessionStoreTargets({
cfg,
opts: {
store: opts.store,
agent: resolveTailTargetAgent(opts),
allAgents: opts.allAgents
}
});
const selections = [];
for (const target of targets) for (const { sessionKey, entry } of listSessionEntriesReadOnly({
agentId: target.agentId,
storePath: target.storePath
})) {
const selection = buildTailSelection({
agentId: target.agentId,
entry,
key: sessionKey,
storePath: target.storePath
});
if (selection) selections.push(selection);
}
const selected = selectSessionsToTail(selections, opts.sessionKey);
if (selected.length === 0) {
const suffix = opts.sessionKey ? ` for ${opts.sessionKey}` : "";
runtime.log(`No sessions found${suffix}.`);
return;
}
const followSnapshots = /* @__PURE__ */ new Map();
for (const selection of selected) {
const snapshot = readTailSnapshot(selection, Math.max(tailCount, opts.follow ? 1 : 0));
followSnapshots.set(selection, snapshot);
renderEvents(tailCount > 0 ? snapshot.events.slice(-tailCount) : [], runtime);
}
if (opts.follow) {
const outcome = await followSelections(selected, runtime, followSnapshots);
runtime.exit(outcome === "ERROR" ? 1 : outcome === "SIGINT" ? 130 : 143);
}
}
//#endregion
export { sessionsTailCommand };