UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

208 lines (207 loc) 8.72 kB
import { s as asFiniteNumber } from "./number-coercion-CLj0HTDM.js"; import { m as readNonBlankString } from "./string-coerce-CIXf7egm.js"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js"; import { h as redactToolPayloadText } from "./redact-BtvPPfTi.js"; import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js"; import { $ as normalizeAgentRunTerminalReplySnapshot } from "./openclaw-state-db-BRTnL-D8.js"; import { m as stripInternalRuntimeContext, t as INTERNAL_RUNTIME_CONTEXT_BEGIN } from "./internal-runtime-context-UgZVJlox.js"; import { i as isSilentReplyPayloadText } from "./tokens-DbeAFqg7.js"; import { i as buildAgentRunTerminalOutcomeFromLifecycleEvent, o as classifyAgentRunTerminalOutcome } from "./agent-run-terminal-outcome-CigeY75d.js"; import "./heartbeat-ofet59jF.js"; import { f as normalizeAgentPlanSteps } from "./streaming-B_uLiLJZ.js"; //#region src/agents/session-activity-notes.ts const MAX_NOTES = 40; const MAX_NOTE_BYTES = 8192; const DEFAULT_NOTE_MAX_CHARS = 360; const ASSISTANT_NOTE_MAX_CHARS = 240; const ASSISTANT_BUFFER_MAX_CHARS = 4096; const ASSISTANT_BUFFER_THROTTLE_MS = 150; const MAX_ITEM_STATUSES = 160; function createSessionActivityNoteState() { return { noteSequence: 0, notes: [], noteBytes: 0, itemStatuses: /* @__PURE__ */ new Map(), assistantBuffer: "", assistantRawBuffer: "", assistantBufferDirty: false, lastAssistantBufferAt: 0 }; } function assembleAssistantBuffer(value, maxChars) { const openIndex = value.lastIndexOf(INTERNAL_RUNTIME_CONTEXT_BEGIN); if (!(openIndex !== -1 && !value.includes("<<<END_OPENCLAW_INTERNAL_CONTEXT>>>", openIndex))) return keepUtf16SafeTail(stripInternalRuntimeContext(value), maxChars); const head = keepUtf16SafeTail(stripInternalRuntimeContext(value.slice(0, openIndex)), maxChars); const body = keepUtf16SafeTail(value.slice(openIndex + INTERNAL_RUNTIME_CONTEXT_BEGIN.length), maxChars); return `${head}${INTERNAL_RUNTIME_CONTEXT_BEGIN}${body}`; } function syncAssistantBuffer(state, at = Date.now()) { if (!state.assistantBufferDirty) return; state.assistantBuffer = assembleAssistantBuffer(state.assistantRawBuffer, ASSISTANT_BUFFER_MAX_CHARS); state.assistantBufferDirty = false; state.lastAssistantBufferAt = at; } function keepUtf16SafeTail(value, maxChars) { if (value.length <= maxChars) return value; let start = value.length - maxChars; const lead = value.charCodeAt(start); if (lead >= 56320 && lead <= 57343) start += 1; return value.slice(start); } function sanitizeActivityText(value, maxChars) { const normalized = redactToolPayloadText(stripInternalRuntimeContext(value)).replace(/\s+/gu, " ").trim(); return truncateUtf16Safe(normalized, maxChars); } function summarizeToolArgs(args) { if (!args || typeof args !== "object") return ""; const record = args; const summary = {}; for (const key of [ "action", "cmd", "command", "cwd", "file", "filePath", "host", "package", "path", "pattern", "query", "target", "url" ]) { const value = record[key]; if (typeof value === "string") summary[key] = redactToolPayloadText(value); else if (typeof value === "number" || typeof value === "boolean") summary[key] = value; } try { if (Object.keys(summary).length > 0) return sanitizeActivityText(JSON.stringify(summary), 220); return sanitizeActivityText(`args: ${Object.keys(record).toSorted().slice(0, 8).join(", ")}`, 220); } catch { return ""; } } function addActivityNote(state, raw, maxChars) { const text = sanitizeActivityText(raw, maxChars); if (!text) return; state.noteSequence += 1; const note = { sequence: state.noteSequence, text, bytes: Buffer.byteLength(text, "utf8") }; state.notes.push(note); state.noteBytes += note.bytes; while (state.notes.length > MAX_NOTES || state.noteBytes > MAX_NOTE_BYTES) { const removed = state.notes.shift(); state.noteBytes -= removed?.bytes ?? 0; } } function rememberItemStatus(state, itemId, status, limit) { if (state.itemStatuses.get(itemId) === status) return false; state.itemStatuses.delete(itemId); state.itemStatuses.set(itemId, status); pruneMapToMaxSize(state.itemStatuses, limit); return true; } function flushSessionActivityAssistantNote(state, noteMaxChars = DEFAULT_NOTE_MAX_CHARS) { syncAssistantBuffer(state); if (!state.assistantBuffer || state.assistantBuffer.includes("<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>")) return; const visible = keepUtf16SafeTail(sanitizeActivityText(state.assistantBuffer, ASSISTANT_BUFFER_MAX_CHARS), ASSISTANT_NOTE_MAX_CHARS).trim(); if (!visible || visible === "HEARTBEAT_OK" || visible === "[OpenClaw heartbeat poll]" || isSilentReplyPayloadText(visible)) return; if (visible === state.lastAssistantNote) return; state.lastAssistantNote = visible; addActivityNote(state, `Assistant: ${visible}`, noteMaxChars); } function noteSessionActivityEvent(state, event, noteMaxChars = DEFAULT_NOTE_MAX_CHARS) { const data = event.data; switch (event.stream) { case "lifecycle": { const phase = data.phase; if (phase === "start") addActivityNote(state, "Run started", noteMaxChars); else if (phase === "finishing") addActivityNote(state, "Run is wrapping up", noteMaxChars); else if (phase === "end" || phase === "error") { const health = terminalHealthFor(event); const error = readNonBlankString(data.error); addActivityNote(state, error ? `Run ${health}: ${error}` : `Run ${health}`, noteMaxChars); const terminalReply = normalizeAgentRunTerminalReplySnapshot(data.terminalReply); state.terminalReply = terminalReply; if (terminalReply?.disposition === "visible") addActivityNote(state, `Assistant: ${terminalReply.text}`, noteMaxChars); } return; } case "tool": { if (data.phase !== "start") return; const name = readNonBlankString(data.name) ?? "tool"; const args = summarizeToolArgs(data.args); addActivityNote(state, args ? `Tool ${name}: ${args}` : `Tool ${name}`, noteMaxChars); return; } case "command_output": { if (data.phase !== "end") return; const title = readNonBlankString(data.title) ?? readNonBlankString(data.name) ?? "command"; const exitCode = asFiniteNumber(data.exitCode); addActivityNote(state, `${title}: ${readNonBlankString(data.status) ?? (exitCode === 0 ? "completed" : "failed")}${exitCode === void 0 ? "" : ` (exit ${exitCode})`}`, noteMaxChars); return; } case "item": { const status = readNonBlankString(data.status); const title = readNonBlankString(data.title); const itemId = readNonBlankString(data.itemId) ?? title; if (!status || !title || !itemId) return; if (![ "running", "completed", "failed", "blocked" ].includes(status)) return; if (!rememberItemStatus(state, itemId, status, MAX_ITEM_STATUSES)) return; addActivityNote(state, `${title}: ${status}`, noteMaxChars); return; } case "plan": { const steps = normalizeAgentPlanSteps(data.steps); if (!steps) return; state.planProgress = { completed: steps.filter((step) => step.status === "completed").length, total: steps.length }; for (const [index, step] of steps.entries()) { if (!rememberItemStatus(state, `plan:${index}:${step.step}`, step.status, MAX_ITEM_STATUSES)) continue; const status = step.status === "in_progress" ? "running" : step.status; addActivityNote(state, `Plan: ${step.step}: ${status}`, noteMaxChars); } return; } case "assistant": { const full = readNonBlankString(data.text); const delta = readNonBlankString(data.delta); if (full) state.assistantRawBuffer = full; else if (delta) { syncAssistantBuffer(state, event.ts); state.assistantBuffer = assembleAssistantBuffer(state.assistantBuffer + delta, ASSISTANT_BUFFER_MAX_CHARS); state.assistantRawBuffer = state.assistantBuffer; state.lastAssistantBufferAt = event.ts; return; } else return; state.assistantBufferDirty = true; if (event.ts - state.lastAssistantBufferAt >= ASSISTANT_BUFFER_THROTTLE_MS) syncAssistantBuffer(state, event.ts); return; } case "approval": if (data.status !== "pending" && data.phase !== "requested") return; addActivityNote(state, `Waiting for approval: ${readNonBlankString(data.title) ?? "user action"}`, noteMaxChars); } } function terminalHealthFor(event) { const phase = event.data.phase; const outcome = buildAgentRunTerminalOutcomeFromLifecycleEvent({ phase: phase === "end" ? "end" : "error", data: event.data }); return classifyAgentRunTerminalOutcome(outcome) === "success" ? "done" : "failed"; } //#endregion export { terminalHealthFor as i, flushSessionActivityAssistantNote as n, noteSessionActivityEvent as r, createSessionActivityNoteState as t };