UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

372 lines (371 loc) 14.9 kB
import { w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js"; import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { a as writeRuntimeJson } from "./runtime-CF2WjnNZ.js"; import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.js"; import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js"; import { O as parseAgentSessionKey, S as isAcpSessionKey } from "./session-key-BnWWjqNc.js"; import { n as sanitizeTerminalText } from "./safe-text-BGBqp1a4.js"; import "./defaults-CdX9UGcX.js"; import "./config-Cs0XXL3x.js"; import { n as isRich, r as theme, t as colorize } from "./theme-vjDs9tao.js"; import { n as info } from "./globals-CTaGxEqj.js"; import { t as ExpectedCliError } from "./failure-output-Dc5ce9_F.js"; import { t as normalizeChatType } from "./chat-type-CG0X_HJM.js"; import { f as sessionDeliveryOrigin, n as deliveryContextFromSession } from "./delivery-context.shared-CXmRgetN.js"; import "./session-accessor-YsytfDtG.js"; import { a as listSessionEntriesReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { r as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-Dp-kgpcT.js"; import { l as resolveSessionTotalTokens, o as resolveFreshSessionTotalTokens } from "./types-BHV0IaPK.js"; import { a as resolveStoredSessionKeyForAgentStore } from "./session-store-key-8xEjWSNi.js"; import { c as prepareCliProviderClassifier } from "./model-selection-di2kjKCB.js"; import { t as classifySessionKind } from "./classify-session-kind-txf57MUM.js"; import { i as readAcpSessionMetaBatch } from "./session-meta-dzG4VkYo.js"; import "./sessions-9nxpeTwt.js"; import { t as resolveProjectedSessionContextTokens } from "./context-token-provenance-CAdnGN1p.js"; import { n as resolveModelAgentRuntimeMetadata } from "./agent-runtime-metadata-BVVh8XFs.js"; import { c as resolveAuthoredModelContextTokens } from "./context-resolution-DlNIXncb.js"; import { t as resolveAgentRuntimeLabel } from "./agent-runtime-label-BA4eUY6k.js"; import { n as renderTable, t as getTerminalTableWidth } from "./table-DzN9OC44.js"; import { t as resolveRuntimePolicySessionKey } from "./runtime-policy-session-key-DCz5SylW.js"; import { t as resolveCommandSessionStoreTargets } from "./session-store-targets-BtyWd3X7.js"; import { a as toSessionDisplayRow, i as formatSessionModelCell, l as resolveSessionDisplayModelRef, n as formatSessionFlagsCell, r as formatSessionKeyCell, s as resolveSessionDisplayDefaults, t as formatSessionAgeCell } from "./sessions-table-D6J9rdye.js"; //#region src/commands/sessions.ts /** * Session listing command. * * It loads one or more agent session stores, enriches rows with model/runtime * metadata, and emits JSON or terminal tables. */ const DEFAULT_SESSIONS_LIMIT = 100; const TOP_N_SELECTION_LIMIT = 200; const contextLookupRuntimeLoader = createLazyImportLoader(() => import("./context-M2p7wU0f.js")); const formatKTokens = (value) => `${(value / 1e3).toFixed(value >= 1e4 ? 0 : 1)}k`; /** True ACP sessions use the child runtime's model, not the configured fallback. */ function applyAcpModelOverlayIfNeeded(modelRef, sessionKey, acpRuntime) { if (!acpRuntime || !isAcpSessionKey(sessionKey)) return modelRef; return { provider: "acpx", model: `${parseAgentSessionKey(sessionKey)?.agentId ?? "acp"}-acp` }; } function compareSessionRowsByUpdatedAt(a, b) { return (b.updatedAt ?? 0) - (a.updatedAt ?? 0); } function selectNewestSessionRows(rows, limit) { if (limit === void 0) return rows.toSorted(compareSessionRowsByUpdatedAt); if (limit > TOP_N_SELECTION_LIMIT) return rows.toSorted(compareSessionRowsByUpdatedAt).slice(0, limit); const selected = []; for (const row of rows) { const insertAt = selected.findIndex((candidate) => compareSessionRowsByUpdatedAt(row, candidate) < 0); if (insertAt >= 0) { selected.splice(insertAt, 0, row); if (selected.length > limit) selected.pop(); } else if (selected.length < limit) selected.push(row); } return selected; } function parseSessionsLimit(value) { if (value === void 0) return DEFAULT_SESSIONS_LIMIT; if (typeof value === "string") { const trimmed = value.trim(); if (trimmed.toLowerCase() === "all") return; if (!/^\d+$/.test(trimmed)) return null; return parseStrictPositiveInteger(trimmed) ?? null; } return Number.isInteger(value) && value > 0 ? value : null; } const colorByPct = (label, pct, rich) => { if (!rich || pct === null) return label; if (pct >= 95) return theme.error(label); if (pct >= 80) return theme.warn(label); if (pct >= 60) return theme.success(label); return theme.muted(label); }; const formatTokensCell = (total, freshTotal, contextTokens, rich) => { const ctxLabel = contextTokens ? formatKTokens(contextTokens) : "?"; if (total === void 0) { const label = `unknown/${ctxLabel} (?%)`; return rich ? theme.muted(label) : label; } const pct = contextTokens && freshTotal !== void 0 ? Math.min(999, Math.round(freshTotal / contextTokens * 100)) : null; const label = `${formatKTokens(total)}/${ctxLabel} (${pct ?? "?"}%)`; return colorByPct(label, pct, rich); }; const formatKindCell = (kind, rich) => { if (!rich) return kind; if (kind === "group") return theme.accentBright(kind); if (kind === "global") return theme.warn(kind); if (kind === "direct") return theme.accent(kind); return theme.muted(kind); }; function resolveSessionRuntimeLabel(params) { const id = normalizeOptionalLowercaseString(params.agentRuntime.id); const resolvedHarness = id && id !== "openclaw" && id !== "auto" ? id : void 0; return resolveAgentRuntimeLabel({ config: params.cfg, sessionEntry: params.entry, resolvedHarness, fallbackProvider: params.modelProvider, classifyCliProvider: params.classifyCliProvider }); } function resolveSessionStoreDisplayPath(target) { return resolveSqliteTargetFromSessionStorePath(target.storePath, { agentId: target.agentId }).path; } function toJsonSessionRow(row) { const { displayModelRef, runtimeLabel, ...jsonRow } = row; return jsonRow; } function stripChannelRecipientPrefix(value, channel) { const raw = normalizeOptionalString(value); const normalizedChannel = normalizeOptionalLowercaseString(channel); if (!raw || !normalizedChannel) return raw; const prefix = `${normalizedChannel}:`; if (!raw.toLowerCase().startsWith(prefix)) return raw; const stripped = raw.slice(prefix.length); const topicMarkerIndex = stripped.toLowerCase().indexOf(":topic:"); return topicMarkerIndex >= 0 ? stripped.slice(0, topicMarkerIndex) : stripped; } function resolveDisplayRuntimePolicySessionKey(params) { const { cfg, entry, key } = params; const origin = sessionDeliveryOrigin(entry); const deliveryContext = deliveryContextFromSession(entry); const chatType = normalizeChatType(origin?.chatType ?? entry.chatType); if (chatType !== "direct") return; const channel = normalizeOptionalString(origin?.provider ?? deliveryContext?.channel ?? origin?.surface); const to = normalizeOptionalString(origin?.to ?? deliveryContext?.to); const from = normalizeOptionalString(origin?.from); const nativeDirectUserId = normalizeOptionalString(origin?.nativeDirectUserId); const peerId = nativeDirectUserId ?? stripChannelRecipientPrefix(to, channel) ?? stripChannelRecipientPrefix(from, channel); const runtimePolicySessionKey = resolveRuntimePolicySessionKey({ agentId: params.agentId, cfg, sessionKey: key, ctx: { SessionKey: key, AgentId: params.agentId, Provider: channel, Surface: normalizeOptionalString(origin?.surface), AccountId: normalizeOptionalString(origin?.accountId ?? deliveryContext?.accountId), ChatType: chatType, NativeDirectUserId: nativeDirectUserId, SenderId: peerId, OriginatingTo: to, From: from, To: to } }); return runtimePolicySessionKey && runtimePolicySessionKey !== key ? runtimePolicySessionKey : void 0; } /** Lists sessions across selected stores with optional JSON output. */ async function sessionsCommand(opts, runtime) { const aggregateAgents = opts.allAgents === true; const cfg = getRuntimeConfig(); const displayDefaults = resolveSessionDisplayDefaults(cfg); const { lookupContextTokens, resolveContextTokensForModel } = await contextLookupRuntimeLoader.load(); const configContextTokens = lookupContextTokens(displayDefaults.model, { allowAsyncLoad: false }) ?? 2e5; const targets = resolveCommandSessionStoreTargets({ cfg, opts }); let activeMinutes; if (opts.active !== void 0) { const parsed = parseStrictPositiveInteger(opts.active); if (parsed === void 0) { const message = "--active must be a positive number of minutes, for example --active 30."; throw new ExpectedCliError({ message, humanOutput: message, machineOutput: message }); } activeMinutes = parsed; } const limit = parseSessionsLimit(opts.limit); if (limit === null) { const message = "--limit must be a positive integer or \"all\", for example --limit 25."; throw new ExpectedCliError({ message, humanOutput: message, machineOutput: message }); } const classifyCliProvider = prepareCliProviderClassifier(cfg); const activeSince = activeMinutes === void 0 ? void 0 : Date.now() - activeMinutes * 6e4; const sessionEntries = targets.flatMap((target) => { return listSessionEntriesReadOnly({ agentId: target.agentId, storePath: target.storePath, projection: "list" }).filter(({ entry }) => activeSince === void 0 || typeof entry.updatedAt === "number" && entry.updatedAt >= activeSince).map(({ sessionKey, entry }) => { const row = toSessionDisplayRow(sessionKey, entry); const agentId = parseAgentSessionKey(row.key)?.agentId ?? target.agentId; return { acpSessionKey: resolveStoredSessionKeyForAgentStore({ cfg, agentId, sessionKey: row.key }), agentId, entry, row }; }); }); const acpSessionMetaByEntry = readAcpSessionMetaBatch({ cfg, entries: sessionEntries.map(({ acpSessionKey, agentId, entry }) => ({ sessionKey: acpSessionKey, agentId, entry })) }); const allRows = sessionEntries.map(({ acpSessionKey, agentId, entry, row }) => { const acpMeta = acpSessionMetaByEntry.get(entry); const acpRuntime = acpMeta != null; const modelRef = applyAcpModelOverlayIfNeeded(resolveSessionDisplayModelRef(cfg, row, classifyCliProvider, agentId), acpSessionKey, acpRuntime); const agentRuntime = resolveModelAgentRuntimeMetadata({ cfg, agentId, sessionEntry: entry, provider: modelRef.provider, model: modelRef.model, sessionKey: acpSessionKey, acpRuntime, acpBackend: acpMeta?.backend }); const resolvedContextTokens = !(typeof entry.contextTokens === "number" && entry.contextTokens > 0) && classifyCliProvider(agentRuntime.id) ? lookupContextTokens(modelRef.model, { allowAsyncLoad: false }) : resolveContextTokensForModel({ cfg, provider: modelRef.provider, model: modelRef.model, allowAsyncLoad: false }); const contextTokens = resolveProjectedSessionContextTokens({ entry, provider: modelRef.provider, model: modelRef.model, agentHarnessId: agentRuntime.id, resolvedContextTokens, authoredContextTokens: resolveAuthoredModelContextTokens({ cfg, provider: modelRef.provider, model: modelRef.model }) }); return Object.assign({}, row, { agentId, acpRuntime, agentRuntime, contextTokens, displayModelRef: modelRef, kind: classifySessionKind(row.key, entry), runtimePolicySessionKey: resolveDisplayRuntimePolicySessionKey({ agentId, cfg, key: row.key, entry }), runtimeLabel: resolveSessionRuntimeLabel({ cfg, entry, agentRuntime, modelProvider: modelRef.provider, classifyCliProvider }) }); }); const totalCount = allRows.length; const rows = selectNewestSessionRows(allRows, limit); const hasMore = rows.length < totalCount; if (opts.json) { const multi = targets.length > 1; const aggregate = aggregateAgents || multi; writeRuntimeJson(runtime, { path: aggregate || !targets[0] ? null : resolveSessionStoreDisplayPath(targets[0]), stores: aggregate ? targets.map((target) => ({ agentId: target.agentId, path: resolveSessionStoreDisplayPath(target) })) : void 0, allAgents: aggregateAgents ? true : void 0, count: rows.length, totalCount, limitApplied: limit ?? null, hasMore, activeMinutes: activeMinutes ?? null, sessions: rows.map((row) => { const r = toJsonSessionRow(row); const modelRef = row.displayModelRef; return { ...r, totalTokens: resolveSessionTotalTokens(r) ?? null, totalTokensFresh: resolveFreshSessionTotalTokens(r) !== void 0, contextTokens: r.contextTokens ?? configContextTokens ?? null, modelProvider: modelRef.provider, model: modelRef.model }; }) }); return; } const primaryTarget = targets[0]; if (primaryTarget && targets.length === 1 && !aggregateAgents) runtime.log(info(`Session store: ${resolveSessionStoreDisplayPath(primaryTarget)}`)); else runtime.log(info(`Session stores: ${targets.length} (${targets.map((t) => t.agentId).join(", ")})`)); runtime.log(info(hasMore && limit !== void 0 ? `Sessions listed: ${rows.length} of ${totalCount} (limit ${limit})` : `Sessions listed: ${rows.length}`)); if (activeMinutes) runtime.log(info(`Filtered to last ${activeMinutes} minute(s)`)); if (rows.length === 0) { runtime.log("No sessions found."); return; } const rich = isRich(); const showAgentColumn = aggregateAgents || targets.length > 1; runtime.log(renderTable({ width: getTerminalTableWidth(), columns: [ ...showAgentColumn ? [{ key: "agent", header: "Agent" }] : [], { key: "kind", header: "Kind" }, { key: "key", header: "Key" }, { key: "age", header: "Age" }, { key: "model", header: "Model" }, { key: "runtime", header: "Runtime" }, { key: "tokens", header: "Tokens (ctx %)" }, { key: "flags", header: "Flags", flex: true } ].map((column) => Object.assign(column, { header: colorize(rich, theme.heading, column.header) })), rows: rows.map((row) => ({ agent: colorize(rich, theme.accentBright, sanitizeTerminalText(row.agentId)), kind: formatKindCell(row.kind, rich), key: formatSessionKeyCell(row.key, rich), age: formatSessionAgeCell(row.updatedAt, rich), model: formatSessionModelCell(row.displayModelRef.model, rich), runtime: colorize(rich, theme.info, sanitizeTerminalText(row.runtimeLabel)), tokens: formatTokensCell(resolveSessionTotalTokens(row), resolveFreshSessionTotalTokens(row), row.contextTokens ?? configContextTokens, rich), flags: formatSessionFlagsCell(row, rich) })) }).trimEnd()); } //#endregion export { sessionsCommand };