openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
586 lines (585 loc) • 24.6 kB
JavaScript
import { b as redactToolPayloadText } from "./redact-DJ8j6ADa.mjs";
import { D as resolveChannelStreamingProgressCommentary, E as resolveChannelStreamingPreviewToolProgress, F as formatChannelProgressDraftDiffStat, M as isChannelProgressPriorityLine, N as removeChannelProgressDraftLine, P as createProgressDraftDiffStatTracker, _ as resolveChannelProgressDraftConfig, a as createChannelProgressDraftGate, b as resolveChannelProgressDraftMaxLines, d as isChannelProgressDraftWorkToolName, h as normalizeChannelProgressDraftLineIdentity, i as copyProgressDraftLineMetadata, j as isChannelProgressAttentionLine, k as resolveChannelStreamingSuppressDefaultToolProgressMessages, l as formatChannelProgressDraftTextForStreaming, n as buildChannelProgressDraftLineForEntry, p as mergeChannelProgressDraftLineForStreaming, v as resolveChannelProgressDraftLabel, y as resolveChannelProgressDraftMaxLineChars } from "./streaming-CHnBADiw.mjs";
import { t as settleProgressVisibilityCallbackResult } from "./progress-visibility-DVUJibF4.mjs";
import { n as projectAgentActivityItem, t as isCompleteAgentPreamble } from "./agent-activity-presentation-BdV3Df9D.mjs";
import { a as sanitizeProgressStatusText, i as resolveCommentaryLineId, n as formatReasoningProgressDisplayLine, r as normalizeCommentaryProgressText, t as createReasoningProgressAccumulator } from "./progress-draft-status-text-LJ5uSwZq.mjs";
//#region src/channels/progress-draft-events.ts
function createChannelProgressDraftEventHandlers(params) {
const pushEvent = (input, detailMode) => {
const options = detailMode ? { detailMode } : void 0;
const line = params.buildLine ? params.buildLine(input, options) : buildChannelProgressDraftLineForEntry(params.entry, input, options);
return params.pushLine(line, input.event === "tool" ? { toolName: input.name?.trim() } : {});
};
return {
pushToolEvent: (payload) => {
params.onTool?.(payload);
const { detailMode, ...input } = payload;
const activity = projectAgentActivityItem({
name: payload.name,
status: "running"
}, { args: payload.args });
return params.preparedItems || activity.hideFromChannelProgress ? Promise.resolve(false) : pushEvent({
event: "tool",
...input
}, detailMode);
},
pushItemEvent: (payload) => {
const { kind: itemKind, ...input } = payload;
params.onItem?.(payload);
if (payload.hideFromChannelProgress || payload.suppressChannelProgress) return Promise.resolve(false);
return pushEvent({
event: "item",
...input,
itemKind
});
},
pushApprovalEvent: (payload) => {
return payload.phase === "requested" ? pushEvent({
event: "approval",
...payload
}) : Promise.resolve(false);
},
pushCommandOutputEvent: (payload) => !params.preparedItems && payload.phase === "end" ? pushEvent({
event: "command-output",
...payload
}) : Promise.resolve(false),
pushPatchEvent: (payload) => !params.preparedItems && payload.phase === "end" ? pushEvent({
event: "patch",
...payload
}) : Promise.resolve(false)
};
}
function routePreparedProgressItem(params) {
const { payload, handlers } = params;
if (payload.kind !== "preamble") {
const id = payload.itemId;
if (payload.hideFromChannelProgress && id) return handlers.pushItemEvent(payload).then(() => params.clearLine(id));
return handlers.pushItemEvent(payload);
}
if (!isCompleteAgentPreamble(payload)) return Promise.resolve(false);
if (!params.progressMode) return handlers.pushItemEvent(payload);
return params.commentary ? params.pushCommentary(payload.progressText, {
itemId: payload.itemId,
complete: true
}) : params.pushHeadline(payload.progressText, { itemId: payload.itemId });
}
//#endregion
//#region src/channels/progress-draft-snapshot.ts
/** Own public display data before adapters render it or receipts retain it. */
function redactProgressDraftLine(line) {
if (typeof line === "string") return redactToolPayloadText(line);
const redacted = {
...line,
text: redactToolPayloadText(line.text),
label: redactToolPayloadText(line.label),
...line.detail !== void 0 ? { detail: redactToolPayloadText(line.detail) } : {},
...line.status !== void 0 ? { status: redactToolPayloadText(line.status) } : {},
...line.icon !== void 0 ? { icon: redactToolPayloadText(line.icon) } : {},
...line.toolName !== void 0 ? { toolName: redactToolPayloadText(line.toolName) } : {}
};
copyProgressDraftLineMetadata(line, redacted);
return redacted;
}
function redactProgressPlanSteps(steps) {
return steps?.map((step) => ({
...step,
step: redactToolPayloadText(step.step)
}));
}
function createProgressDraftSnapshotState(params) {
const snapshot = params.initialSnapshot;
return {
displayEntry: snapshot ? { streaming: { progress: {
...resolveChannelProgressDraftConfig(params.entry),
label: snapshot.label === void 0 ? false : redactToolPayloadText(snapshot.label)
} } } : params.entry,
transferredStatus: snapshot?.statusHeadline ? {
text: redactToolPayloadText(snapshot.statusHeadline),
format: snapshot.statusHeadlineFormat
} : void 0,
transferredDiffStat: snapshot?.diffStat ? { ...snapshot.diffStat } : void 0,
lines: snapshot?.lines.map(redactProgressDraftLine) ?? [],
planSteps: redactProgressPlanSteps(snapshot?.plan),
planExplanation: redactToolPayloadText(snapshot?.planExplanation ?? ""),
planExplanationFormat: snapshot?.planExplanationFormat
};
}
function snapshotProgressDraftState(params) {
const statusHeadline = params.status.text;
const label = resolveChannelProgressDraftLabel({
entry: params.entry,
seed: params.seed,
narration: statusHeadline
});
return {
lines: params.lines.map((line) => typeof line === "string" ? line : { ...line }),
...label ? { label } : {},
...statusHeadline ? { statusHeadline } : {},
...statusHeadline && params.status.format ? { statusHeadlineFormat: params.status.format } : {},
...params.plan ? { plan: params.plan.map((step) => ({ ...step })) } : {},
...params.planExplanation ? { planExplanation: params.planExplanation } : {},
...params.planExplanation && params.planExplanationFormat ? { planExplanationFormat: params.planExplanationFormat } : {},
...params.diffStat ? { diffStat: params.diffStat } : {}
};
}
//#endregion
//#region src/channels/progress-work-counter.ts
/**
* Per-turn work counters for live channel progress surfaces. These describe the
* turn while it runs; nothing here survives into the finished transcript.
*/
function createChannelProgressWorkCounter(params) {
const now = params?.now ?? Date.now;
let startedAt = now();
let toolCalls = 0;
return {
noteItem(item) {
if (item.phase === "start" && item.kind !== "preamble" && !item.hideFromChannelProgress && !item.suppressChannelProgress) toolCalls += 1;
},
noteToolCall(toolName) {
if (isChannelProgressDraftWorkToolName(toolName)) toolCalls += 1;
},
reset() {
startedAt = now();
toolCalls = 0;
},
get toolCalls() {
return toolCalls;
},
get elapsedSeconds() {
return Math.max(1, Math.round((now() - startedAt) / 1e3));
}
};
}
//#endregion
//#region src/channels/progress-draft-compositor.ts
const PROGRESS_STATUS_PREAMBLE_FRESH_MS = 2e4;
function createChannelProgressDraftCompositor(params) {
const now = params.now ?? Date.now;
const setTimeoutFn = params.setTimeoutFn ?? setTimeout;
const clearTimeoutFn = params.clearTimeoutFn ?? clearTimeout;
const reasoningLinePrefix = params.reasoningLinePrefix ?? "";
const commentaryLinePrefix = params.commentaryLinePrefix ?? "";
const commentaryItalics = params.commentaryItalics ?? true;
const stripLaneItalics = (text) => text.split("\n").map((line) => line.replace(/^_(.*)_$/su, "$1")).join("\n");
const previewToolProgressEnabled = params.active && resolveChannelStreamingPreviewToolProgress(params.entry, params.mode !== "progress", params.mode);
const quietProgress = params.presentation === "summary" || !previewToolProgressEnabled;
const commentaryProgressEnabled = params.active && resolveChannelStreamingProgressCommentary(params.entry, false, params.mode);
const thinkingProgressEnabled = params.active && (params.reasoningGate ?? true);
const suppressDefaultToolProgressMessages = params.active && resolveChannelStreamingSuppressDefaultToolProgressMessages(params.entry, {
draftStreamActive: true,
mode: params.mode,
previewToolProgressEnabled
});
let { displayEntry, transferredStatus, transferredDiffStat, lines, planSteps, planExplanation, planExplanationFormat } = createProgressDraftSnapshotState(params);
let progressSuppressed = false;
let renderGeneration = 0;
let lastRenderedText = "";
let lastRenderedStatusFormat;
let lastRenderedLines = lines;
let lastRenderedDiffStatKey = "";
const reasoningProgress = createReasoningProgressAccumulator();
let lastReasoningLine;
let lastIdLessCommentaryId;
let lastIdLessCommentaryBare = "";
let preambleText = "";
let preambleItemId;
let preambleAt;
let narrationText = "";
let finalReplyStarted = false;
let finalReplyDelivered = false;
const isTurnActive = () => params.active && !finalReplyStarted && !finalReplyDelivered;
const canUpdateProgress = () => isTurnActive() && params.mode === "progress" && !progressSuppressed;
const diffStatTracker = createProgressDraftDiffStatTracker({ canStage: () => canUpdateProgress() && !transferredDiffStat });
let preambleExpiryTimer;
let lastStartRendered = false;
const clearPreambleExpiryTimer = () => {
if (preambleExpiryTimer !== void 0) {
clearTimeoutFn(preambleExpiryTimer);
preambleExpiryTimer = void 0;
}
};
const resolveStatusText = () => {
if (transferredStatus) return transferredStatus;
const preambleIsFresh = preambleAt !== void 0 && now() - preambleAt < 2e4;
if (preambleText && (preambleIsFresh || !(narrationText || planExplanation))) return { text: preambleText };
return narrationText ? { text: narrationText } : {
text: planExplanation,
format: planExplanationFormat
};
};
const formatDraftText = (draftLines = lines, options) => {
const status = resolveStatusText();
const narration = status.text || void 0;
const linesRenderedByChannel = params.rendersRollingLinesNatively === true && Boolean(narration || planSteps?.length);
return formatChannelProgressDraftTextForStreaming({
presentation: params.presentation,
entry: displayEntry,
lines: linesRenderedByChannel ? [] : draftLines,
seed: params.seed,
formatLine: options?.formatted === false ? void 0 : params.formatLine,
formatPlainText: options?.formatted === false ? (text) => text : params.formatPlainText,
onPreparedBlocks: options?.onPreparedBlocks,
narration,
narrationFormat: status.format,
plan: planSteps,
diffStat: resolveDiffStat()
});
};
const resolveDiffStat = () => transferredDiffStat ? { ...transferredDiffStat } : diffStatTracker.resolve();
const getSnapshot = () => snapshotProgressDraftState({
entry: displayEntry,
seed: params.seed,
status: resolveStatusText(),
lines,
plan: planSteps,
planExplanation,
planExplanationFormat,
diffStat: resolveDiffStat()
});
const clearActivityState = (suppressed) => {
clearPreambleExpiryTimer();
progressSuppressed = suppressed;
displayEntry = params.entry;
transferredStatus = void 0;
transferredDiffStat = void 0;
lines = [];
renderGeneration += 1;
lastRenderedText = "";
lastRenderedStatusFormat = void 0;
lastRenderedLines = lines;
lastRenderedDiffStatKey = "";
reasoningProgress.reset();
lastReasoningLine = void 0;
lastIdLessCommentaryId = void 0;
lastIdLessCommentaryBare = "";
preambleText = "";
preambleItemId = void 0;
preambleAt = void 0;
narrationText = "";
diffStatTracker.reset();
lastStartRendered = false;
};
const clearProgressState = (suppressed) => {
clearActivityState(suppressed);
planSteps = void 0;
planExplanation = "";
planExplanationFormat = void 0;
};
const publish = async (options) => {
if (!params.update) return false;
let blocks = [];
const text = formatDraftText(lines, { onPreparedBlocks: (prepared) => {
blocks = prepared;
} });
const statusFormat = resolveStatusText().format;
const diffStatKey = JSON.stringify(resolveDiffStat() ?? null);
const structuredStateChanged = params.updateOnLineChange === true && (lines !== lastRenderedLines || diffStatKey !== lastRenderedDiffStatKey);
if (!text || text === lastRenderedText && statusFormat === lastRenderedStatusFormat && !structuredStateChanged) return false;
const generation = renderGeneration;
const observed = await settleProgressVisibilityCallbackResult(params.update(text, {
...options,
lines: [...lines],
snapshot: {
...getSnapshot(),
...blocks.some((block) => block.format === "plain") ? { preparedBlocks: blocks } : {}
}
}));
if (observed.visible && generation === renderGeneration) {
lastRenderedText = text;
lastRenderedStatusFormat = statusFormat;
lastRenderedLines = lines;
lastRenderedDiffStatKey = diffStatKey;
}
return observed.visible;
};
const render = async (options) => {
if (!isTurnActive() || params.mode !== "progress") return false;
return await publish(options);
};
const schedulePreambleExpiryRefresh = () => {
clearPreambleExpiryTimer();
if (!preambleText || !narrationText || preambleAt === void 0 || !gate?.hasStarted || finalReplyStarted || finalReplyDelivered) return;
const remaining = PROGRESS_STATUS_PREAMBLE_FRESH_MS - (now() - preambleAt);
if (remaining <= 0) return;
preambleExpiryTimer = setTimeoutFn(() => {
preambleExpiryTimer = void 0;
render().catch((err) => {
console.warn(`[progress-draft] channel progress status refresh failed: ${String(err)}`);
});
}, remaining);
};
const gate = params.update && createChannelProgressDraftGate({
onStart: async () => {
lastStartRendered = await render({ flush: true });
schedulePreambleExpiryRefresh();
},
setTimeoutFn,
clearTimeoutFn
});
const startAndRender = async (options) => {
const alreadyStarted = gate?.hasStarted;
if (!alreadyStarted) lastStartRendered = false;
await gate?.startNow();
if (!gate?.hasStarted) return false;
return alreadyStarted ? await render(options) : lastStartRendered;
};
const noteWorkAndRender = async () => {
const alreadyStarted = gate?.hasStarted;
const progressActive = await gate?.noteWork();
return (alreadyStarted || progressActive) && gate?.hasStarted ? await render() : false;
};
const renderAfterRetraction = async () => {
if (!isTurnActive() || !params.update || params.mode === "progress" && !gate?.hasStarted) return false;
if (lines.length || resolveStatusText().text || planSteps?.length || formatChannelProgressDraftDiffStat(resolveDiffStat())) return await publish();
if (!params.deleteCurrent) return lastRenderedText ? await publish() : false;
renderGeneration += 1;
lastRenderedText = "";
await params.deleteCurrent();
return true;
};
const clearLine = async (lineId) => {
const nextLines = removeChannelProgressDraftLine(lines, lineId);
if (nextLines === lines) return false;
lines = nextLines;
return await renderAfterRetraction();
};
const mergeLine = (line) => mergeChannelProgressDraftLineForStreaming(lines, line, { maxLines: resolveChannelProgressDraftMaxLines(params.entry) });
const noteProgress = async (inputLine, options) => {
if (!isTurnActive()) return false;
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return false;
const line = inputLine === void 0 ? void 0 : redactProgressDraftLine(inputLine);
if (params.isEmptyLine?.(line)) return false;
const normalized = normalizeChannelProgressDraftLineIdentity(line);
if (!normalized || progressSuppressed) return false;
if (params.mode !== "progress" && !previewToolProgressEnabled) return false;
const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
const shouldStoreLine = !quietProgress || typeof progressLine === "object" && progressLine.kind === "approval";
const needsAttention = shouldStoreLine && (isChannelProgressPriorityLine(progressLine) || typeof progressLine === "object" && progressLine.status?.toLowerCase() === "failed");
const shouldStartImmediately = shouldStoreLine && isChannelProgressAttentionLine(progressLine);
const nextLines = shouldStoreLine ? mergeLine(progressLine) : lines;
const lineChanged = nextLines !== lines;
const hasUnconfirmedRender = params.update && formatDraftText(nextLines) !== lastRenderedText;
const diffStatChanged = params.updateOnLineChange === true && JSON.stringify(resolveDiffStat() ?? null) !== lastRenderedDiffStatKey;
if (shouldStoreLine && !lineChanged && !hasUnconfirmedRender && !diffStatChanged) return false;
if (quietProgress || shouldStoreLine && lineChanged) {
reasoningProgress.reset();
lastReasoningLine = void 0;
}
if (shouldStoreLine && params.update && params.tryNativeUpdate) {
const text = formatDraftText(nextLines, { formatted: false });
if (text && await params.tryNativeUpdate(text)) {
lines = nextLines;
lastRenderedText = text;
lastRenderedLines = lines;
return true;
}
}
lines = nextLines;
if (params.mode !== "progress") return shouldStoreLine ? await publish() : false;
if (options?.startImmediately || params.shouldStartNow?.(line) || shouldStartImmediately) {
const flush = options?.flush === true || needsAttention;
return await startAndRender(flush ? { flush: true } : void 0);
}
return await noteWorkAndRender();
};
const progressEventHandlers = createChannelProgressDraftEventHandlers({
entry: params.entry,
preparedItems: params.preparedItems,
pushLine: noteProgress,
onTool: diffStatTracker.stageToolEvent,
onItem: diffStatTracker.commitItemEvent,
...params.buildProgressEventLine ? { buildLine: params.buildProgressEventLine } : {}
});
const compositor = {
get previewToolProgressEnabled() {
return previewToolProgressEnabled;
},
get commentaryProgressEnabled() {
return commentaryProgressEnabled;
},
get suppressDefaultToolProgressMessages() {
return suppressDefaultToolProgressMessages;
},
get hasStarted() {
return gate?.hasStarted ?? false;
},
get isVisible() {
return Boolean(lastRenderedText) && !finalReplyStarted && !finalReplyDelivered;
},
get hasStatusHeadline() {
return Boolean(resolveStatusText().text);
},
get hasPlanProgress() {
return Boolean(planSteps?.length);
},
getSnapshot,
getText: () => formatDraftText(),
markFinalReplyStarted() {
finalReplyStarted = true;
gate?.cancel();
clearPreambleExpiryTimer();
},
markFinalReplyDelivered() {
finalReplyDelivered = true;
clearPreambleExpiryTimer();
},
beginNewTurn(options) {
if (options?.force !== true && !finalReplyStarted && !finalReplyDelivered) return false;
finalReplyStarted = false;
finalReplyDelivered = false;
gate?.reset();
clearProgressState(false);
return true;
},
reset() {
clearProgressState(false);
},
resetActivity(options) {
clearActivityState(options?.suppressed === true);
},
beginAssistantMessage() {
if (progressSuppressed) clearActivityState(false);
reasoningProgress.reset();
},
resetReasoningProgress() {
reasoningProgress.reset();
},
mergeReasoningProgress: reasoningProgress.merge,
suppress() {
clearProgressState(true);
},
cancel() {
gate?.cancel();
clearPreambleExpiryTimer();
},
async start() {
await gate?.startNow();
},
async noteActivity(options) {
if (!canUpdateProgress()) return false;
if (options?.startImmediately) return await startAndRender({ flush: true });
return await noteWorkAndRender();
},
pushToolProgress: noteProgress,
...progressEventHandlers,
pushItemEvent: (payload) => routePreparedProgressItem({
payload,
progressMode: params.mode === "progress",
commentary: commentaryProgressEnabled,
handlers: progressEventHandlers,
clearLine,
pushCommentary: (text, options) => compositor.pushCommentaryProgress(text, options),
pushHeadline: (text, options) => compositor.pushPreambleHeadline(text, options)
}),
async pushApprovalEvent(payload) {
if (payload.phase === "resolved" && payload.approvalId) return await clearLine(`approval:${payload.approvalId}`);
return await progressEventHandlers.pushApprovalEvent(payload);
},
async pushPlanProgress(steps, options) {
if (!isTurnActive() || progressSuppressed) return false;
if (params.mode !== "progress" && !previewToolProgressEnabled) return false;
transferredStatus = void 0;
planSteps = steps && steps.length > 0 ? redactProgressPlanSteps(steps) : void 0;
planExplanation = redactToolPayloadText(options?.explanation ?? "").replace(/\s+/g, " ").trim();
planExplanationFormat = options?.explanationFormat;
if (!planSteps && !planExplanation) return await renderAfterRetraction();
return params.mode === "progress" ? await startAndRender() : await publish({ flush: true });
},
async pushPreambleHeadline(text, options) {
if (!canUpdateProgress()) return false;
if (commentaryProgressEnabled) return false;
const itemId = options?.itemId?.trim() || void 0;
const normalized = sanitizeProgressStatusText(text ?? "").replace(/\s+/g, " ").trim();
if (!normalized) {
if (!itemId || itemId !== preambleItemId) return false;
preambleText = "";
preambleItemId = void 0;
preambleAt = void 0;
clearPreambleExpiryTimer();
return await renderAfterRetraction();
}
const isNewPreambleItem = Boolean(itemId && itemId !== preambleItemId);
if (isNewPreambleItem) preambleItemId = itemId;
else if (!itemId) preambleItemId = void 0;
if (normalized === preambleText && !isNewPreambleItem) return false;
transferredStatus = void 0;
preambleText = normalized;
preambleAt = now();
schedulePreambleExpiryRefresh();
return gate?.hasStarted ? await render() : false;
},
async pushNarrationProgress(text) {
if (!canUpdateProgress()) return false;
const normalized = redactToolPayloadText(text ?? "").replace(/\s+/g, " ").trim();
if (normalized === narrationText) return false;
transferredStatus = void 0;
if (!normalized) {
narrationText = "";
clearPreambleExpiryTimer();
return await render();
}
narrationText = normalized;
schedulePreambleExpiryRefresh();
return gate?.hasStarted ? await render() : false;
},
async pushReasoningProgress(text, options) {
if (!canUpdateProgress() || !text || !thinkingProgressEnabled) return false;
const normalized = reasoningProgress.merge(text, options);
if (!normalized) return false;
const compactLine = formatReasoningProgressDisplayLine(normalized, resolveChannelProgressDraftMaxLineChars(params.entry));
if (!compactLine) return false;
const displayLine = `${reasoningLinePrefix}${compactLine}`;
const priorIndex = lastReasoningLine === void 0 ? -1 : lines.lastIndexOf(lastReasoningLine);
if (params.presentation === "summary") lines = mergeLine({
id: "reasoning",
kind: "item",
text: stripLaneItalics(compactLine),
label: "Reasoning",
prefix: false
});
else if (priorIndex >= 0) {
lines = [...lines];
lines[priorIndex] = displayLine;
} else lines = mergeLine(displayLine);
lastReasoningLine = displayLine;
if (await gate?.noteWork() && gate?.hasStarted) return await render();
return false;
},
async pushCommentaryProgress(text, options) {
if (!isTurnActive() || params.mode !== "progress" || !commentaryProgressEnabled) return false;
const itemId = options?.itemId?.trim();
if (!text && !itemId) return false;
const normalized = normalizeCommentaryProgressText(text ?? "");
const bareNormalized = stripLaneItalics(normalized);
const lineId = resolveCommentaryLineId({
itemId,
normalized,
bareNormalized,
lastIdLessCommentaryId,
lastIdLessCommentaryBare
});
if (!normalized) {
if (lineId) await clearLine(lineId);
return false;
}
const line = {
id: lineId,
text: `${commentaryLinePrefix}${commentaryItalics ? normalized : bareNormalized}`,
kind: "item",
label: "Commentary",
prefix: false,
...options?.complete !== void 0 ? { complete: options.complete } : {}
};
lines = mergeLine(line);
if (!itemId) {
lastIdLessCommentaryId = lineId;
lastIdLessCommentaryBare = bareNormalized;
}
return await startAndRender();
}
};
return compositor;
}
//#endregion
export { createChannelProgressDraftCompositor as n, createChannelProgressWorkCounter as r, PROGRESS_STATUS_PREAMBLE_FRESH_MS as t };