mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 17:21:13 +00:00
* Gateway: harden node event trust boundaries * Gateway: preserve trusted summary prefixes * Gateway: prefix multiline channel summaries
112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
import { resolveUserTimezone } from "../../agents/date-time.js";
|
|
import type { OpenClawConfig } from "../../config/config.js";
|
|
import { buildChannelSummary } from "../../infra/channel-summary.js";
|
|
import {
|
|
formatUtcTimestamp,
|
|
formatZonedTimestamp,
|
|
resolveTimezone,
|
|
} from "../../infra/format-time/format-datetime.ts";
|
|
import { drainSystemEventEntries } from "../../infra/system-events.js";
|
|
|
|
/** Drain queued system events, format as `System:` lines, return the block (or undefined). */
|
|
export async function drainFormattedSystemEvents(params: {
|
|
cfg: OpenClawConfig;
|
|
sessionKey: string;
|
|
isMainSession: boolean;
|
|
isNewSession: boolean;
|
|
}): Promise<string | undefined> {
|
|
const compactSystemEvent = (line: string): string | null => {
|
|
const trimmed = line.trim();
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
const lower = trimmed.toLowerCase();
|
|
if (lower.includes("reason periodic")) {
|
|
return null;
|
|
}
|
|
// Filter out the actual heartbeat prompt, but not cron jobs that mention "heartbeat".
|
|
// The heartbeat prompt starts with "Read HEARTBEAT.md" - cron payloads won't match this.
|
|
if (lower.startsWith("read heartbeat.md")) {
|
|
return null;
|
|
}
|
|
if (lower.includes("heartbeat poll") || lower.includes("heartbeat wake")) {
|
|
return null;
|
|
}
|
|
if (trimmed.startsWith("Node:")) {
|
|
return trimmed.replace(/ · last input [^·]+/i, "").trim();
|
|
}
|
|
return trimmed;
|
|
};
|
|
|
|
const resolveSystemEventTimezone = (cfg: OpenClawConfig) => {
|
|
const raw = cfg.agents?.defaults?.envelopeTimezone?.trim();
|
|
if (!raw) {
|
|
return { mode: "local" as const };
|
|
}
|
|
const lowered = raw.toLowerCase();
|
|
if (lowered === "utc" || lowered === "gmt") {
|
|
return { mode: "utc" as const };
|
|
}
|
|
if (lowered === "local" || lowered === "host") {
|
|
return { mode: "local" as const };
|
|
}
|
|
if (lowered === "user") {
|
|
return {
|
|
mode: "iana" as const,
|
|
timeZone: resolveUserTimezone(cfg.agents?.defaults?.userTimezone),
|
|
};
|
|
}
|
|
const explicit = resolveTimezone(raw);
|
|
return explicit ? { mode: "iana" as const, timeZone: explicit } : { mode: "local" as const };
|
|
};
|
|
|
|
const formatSystemEventTimestamp = (ts: number, cfg: OpenClawConfig) => {
|
|
const date = new Date(ts);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return "unknown-time";
|
|
}
|
|
const zone = resolveSystemEventTimezone(cfg);
|
|
if (zone.mode === "utc") {
|
|
return formatUtcTimestamp(date, { displaySeconds: true });
|
|
}
|
|
if (zone.mode === "local") {
|
|
return formatZonedTimestamp(date, { displaySeconds: true }) ?? "unknown-time";
|
|
}
|
|
return (
|
|
formatZonedTimestamp(date, { timeZone: zone.timeZone, displaySeconds: true }) ??
|
|
"unknown-time"
|
|
);
|
|
};
|
|
|
|
const systemLines: string[] = [];
|
|
const queued = drainSystemEventEntries(params.sessionKey);
|
|
systemLines.push(
|
|
...queued.flatMap((event) => {
|
|
const compacted = compactSystemEvent(event.text);
|
|
if (!compacted) {
|
|
return [];
|
|
}
|
|
const prefix = event.trusted === false ? "System (untrusted)" : "System";
|
|
const timestamp = `[${formatSystemEventTimestamp(event.ts, params.cfg)}]`;
|
|
return compacted
|
|
.split("\n")
|
|
.map((subline, index) => `${prefix}: ${index === 0 ? `${timestamp} ` : ""}${subline}`);
|
|
}),
|
|
);
|
|
if (params.isMainSession && params.isNewSession) {
|
|
const summary = await buildChannelSummary(params.cfg);
|
|
if (summary.length > 0) {
|
|
systemLines.unshift(
|
|
...summary.flatMap((line) => line.split("\n").map((subline) => `System: ${subline}`)),
|
|
);
|
|
}
|
|
}
|
|
if (systemLines.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
// Each sub-line gets its own prefix so continuation lines can't be mistaken
|
|
// for regular user content.
|
|
return systemLines.join("\n");
|
|
}
|