mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 17:51:22 +00:00
608 lines
20 KiB
TypeScript
608 lines
20 KiB
TypeScript
import path from "node:path";
|
|
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
|
|
export const DEFAULT_MEMORY_DREAMING_ENABLED = true;
|
|
export const DEFAULT_MEMORY_DREAMING_TIMEZONE = undefined;
|
|
export const DEFAULT_MEMORY_DREAMING_VERBOSE_LOGGING = false;
|
|
export const DEFAULT_MEMORY_DREAMING_STORAGE_MODE = "inline";
|
|
export const DEFAULT_MEMORY_DREAMING_SEPARATE_REPORTS = false;
|
|
|
|
export const DEFAULT_MEMORY_LIGHT_DREAMING_CRON_EXPR = "0 */6 * * *";
|
|
export const DEFAULT_MEMORY_LIGHT_DREAMING_LOOKBACK_DAYS = 2;
|
|
export const DEFAULT_MEMORY_LIGHT_DREAMING_LIMIT = 100;
|
|
export const DEFAULT_MEMORY_LIGHT_DREAMING_DEDUPE_SIMILARITY = 0.9;
|
|
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_CRON_EXPR = "0 3 * * *";
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_LIMIT = 10;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_MIN_SCORE = 0.8;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_MIN_RECALL_COUNT = 3;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_MIN_UNIQUE_QUERIES = 3;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECENCY_HALF_LIFE_DAYS = 14;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_MAX_AGE_DAYS = 30;
|
|
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_ENABLED = true;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_TRIGGER_BELOW_HEALTH = 0.35;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_LOOKBACK_DAYS = 30;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_MAX_CANDIDATES = 20;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_MIN_CONFIDENCE = 0.9;
|
|
export const DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_AUTO_WRITE_MIN_CONFIDENCE = 0.97;
|
|
|
|
export const DEFAULT_MEMORY_REM_DREAMING_CRON_EXPR = "0 5 * * 0";
|
|
export const DEFAULT_MEMORY_REM_DREAMING_LOOKBACK_DAYS = 7;
|
|
export const DEFAULT_MEMORY_REM_DREAMING_LIMIT = 10;
|
|
export const DEFAULT_MEMORY_REM_DREAMING_MIN_PATTERN_STRENGTH = 0.75;
|
|
|
|
export const DEFAULT_MEMORY_DREAMING_SPEED = "balanced";
|
|
export const DEFAULT_MEMORY_DREAMING_THINKING = "medium";
|
|
export const DEFAULT_MEMORY_DREAMING_BUDGET = "medium";
|
|
|
|
export type MemoryDreamingSpeed = "fast" | "balanced" | "slow";
|
|
export type MemoryDreamingThinking = "low" | "medium" | "high";
|
|
export type MemoryDreamingBudget = "cheap" | "medium" | "expensive";
|
|
export type MemoryDreamingStorageMode = "inline" | "separate" | "both";
|
|
|
|
export type MemoryLightDreamingSource = "daily" | "sessions" | "recall";
|
|
export type MemoryDeepDreamingSource = "daily" | "memory" | "sessions" | "logs" | "recall";
|
|
export type MemoryRemDreamingSource = "memory" | "daily" | "deep";
|
|
|
|
export type MemoryDreamingExecutionConfig = {
|
|
speed: MemoryDreamingSpeed;
|
|
thinking: MemoryDreamingThinking;
|
|
budget: MemoryDreamingBudget;
|
|
model?: string;
|
|
maxOutputTokens?: number;
|
|
temperature?: number;
|
|
timeoutMs?: number;
|
|
};
|
|
|
|
export type MemoryDreamingStorageConfig = {
|
|
mode: MemoryDreamingStorageMode;
|
|
separateReports: boolean;
|
|
};
|
|
|
|
export type MemoryLightDreamingConfig = {
|
|
enabled: boolean;
|
|
cron: string;
|
|
lookbackDays: number;
|
|
limit: number;
|
|
dedupeSimilarity: number;
|
|
sources: MemoryLightDreamingSource[];
|
|
execution: MemoryDreamingExecutionConfig;
|
|
};
|
|
|
|
export type MemoryDeepDreamingRecoveryConfig = {
|
|
enabled: boolean;
|
|
triggerBelowHealth: number;
|
|
lookbackDays: number;
|
|
maxRecoveredCandidates: number;
|
|
minRecoveryConfidence: number;
|
|
autoWriteMinConfidence: number;
|
|
};
|
|
|
|
export type MemoryDeepDreamingConfig = {
|
|
enabled: boolean;
|
|
cron: string;
|
|
limit: number;
|
|
minScore: number;
|
|
minRecallCount: number;
|
|
minUniqueQueries: number;
|
|
recencyHalfLifeDays: number;
|
|
maxAgeDays?: number;
|
|
sources: MemoryDeepDreamingSource[];
|
|
recovery: MemoryDeepDreamingRecoveryConfig;
|
|
execution: MemoryDreamingExecutionConfig;
|
|
};
|
|
|
|
export type MemoryRemDreamingConfig = {
|
|
enabled: boolean;
|
|
cron: string;
|
|
lookbackDays: number;
|
|
limit: number;
|
|
minPatternStrength: number;
|
|
sources: MemoryRemDreamingSource[];
|
|
execution: MemoryDreamingExecutionConfig;
|
|
};
|
|
|
|
export type MemoryDreamingPhaseName = "light" | "deep" | "rem";
|
|
|
|
export type MemoryDreamingConfig = {
|
|
enabled: boolean;
|
|
timezone?: string;
|
|
verboseLogging: boolean;
|
|
storage: MemoryDreamingStorageConfig;
|
|
execution: {
|
|
defaults: MemoryDreamingExecutionConfig;
|
|
};
|
|
phases: {
|
|
light: MemoryLightDreamingConfig;
|
|
deep: MemoryDeepDreamingConfig;
|
|
rem: MemoryRemDreamingConfig;
|
|
};
|
|
};
|
|
|
|
export type MemoryDreamingWorkspace = {
|
|
workspaceDir: string;
|
|
agentIds: string[];
|
|
};
|
|
|
|
const DEFAULT_MEMORY_LIGHT_DREAMING_SOURCES: MemoryLightDreamingSource[] = [
|
|
"daily",
|
|
"sessions",
|
|
"recall",
|
|
];
|
|
const DEFAULT_MEMORY_DEEP_DREAMING_SOURCES: MemoryDeepDreamingSource[] = [
|
|
"daily",
|
|
"memory",
|
|
"sessions",
|
|
"logs",
|
|
"recall",
|
|
];
|
|
const DEFAULT_MEMORY_REM_DREAMING_SOURCES: MemoryRemDreamingSource[] = ["memory", "daily", "deep"];
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function normalizeTrimmedString(value: unknown): string | undefined {
|
|
if (typeof value !== "string") {
|
|
return undefined;
|
|
}
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
function normalizeNonNegativeInt(value: unknown, fallback: number): number {
|
|
if (typeof value === "string" && value.trim().length === 0) {
|
|
return fallback;
|
|
}
|
|
const num = typeof value === "string" ? Number(value.trim()) : Number(value);
|
|
if (!Number.isFinite(num)) {
|
|
return fallback;
|
|
}
|
|
const floored = Math.floor(num);
|
|
if (floored < 0) {
|
|
return fallback;
|
|
}
|
|
return floored;
|
|
}
|
|
|
|
function normalizeOptionalPositiveInt(value: unknown): number | undefined {
|
|
if (value === undefined || value === null) {
|
|
return undefined;
|
|
}
|
|
if (typeof value === "string" && value.trim().length === 0) {
|
|
return undefined;
|
|
}
|
|
const num = typeof value === "string" ? Number(value.trim()) : Number(value);
|
|
if (!Number.isFinite(num)) {
|
|
return undefined;
|
|
}
|
|
const floored = Math.floor(num);
|
|
if (floored <= 0) {
|
|
return undefined;
|
|
}
|
|
return floored;
|
|
}
|
|
|
|
function normalizeBoolean(value: unknown, fallback: boolean): boolean {
|
|
if (typeof value === "boolean") {
|
|
return value;
|
|
}
|
|
if (typeof value === "string") {
|
|
const normalized = value.trim().toLowerCase();
|
|
if (normalized === "true") {
|
|
return true;
|
|
}
|
|
if (normalized === "false") {
|
|
return false;
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
function normalizeScore(value: unknown, fallback: number): number {
|
|
if (typeof value === "string" && value.trim().length === 0) {
|
|
return fallback;
|
|
}
|
|
const num = typeof value === "string" ? Number(value.trim()) : Number(value);
|
|
if (!Number.isFinite(num) || num < 0 || num > 1) {
|
|
return fallback;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
function normalizeSimilarity(value: unknown, fallback: number): number {
|
|
return normalizeScore(value, fallback);
|
|
}
|
|
|
|
function normalizeStringArray<T extends string>(
|
|
value: unknown,
|
|
allowed: readonly T[],
|
|
fallback: readonly T[],
|
|
): T[] {
|
|
if (!Array.isArray(value)) {
|
|
return [...fallback];
|
|
}
|
|
const allowedSet = new Set(allowed);
|
|
const normalized: T[] = [];
|
|
for (const entry of value) {
|
|
const normalizedEntry = normalizeTrimmedString(entry)?.toLowerCase();
|
|
if (!normalizedEntry || !allowedSet.has(normalizedEntry as T)) {
|
|
continue;
|
|
}
|
|
if (!normalized.includes(normalizedEntry as T)) {
|
|
normalized.push(normalizedEntry as T);
|
|
}
|
|
}
|
|
return normalized.length > 0 ? normalized : [...fallback];
|
|
}
|
|
|
|
function normalizeStorageMode(value: unknown): MemoryDreamingStorageMode {
|
|
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
|
if (normalized === "inline" || normalized === "separate" || normalized === "both") {
|
|
return normalized;
|
|
}
|
|
return DEFAULT_MEMORY_DREAMING_STORAGE_MODE;
|
|
}
|
|
|
|
function normalizeSpeed(value: unknown): MemoryDreamingSpeed | undefined {
|
|
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
|
if (normalized === "fast" || normalized === "balanced" || normalized === "slow") {
|
|
return normalized;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function normalizeThinking(value: unknown): MemoryDreamingThinking | undefined {
|
|
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
|
if (normalized === "low" || normalized === "medium" || normalized === "high") {
|
|
return normalized;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function normalizeBudget(value: unknown): MemoryDreamingBudget | undefined {
|
|
const normalized = normalizeTrimmedString(value)?.toLowerCase();
|
|
if (normalized === "cheap" || normalized === "medium" || normalized === "expensive") {
|
|
return normalized;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function resolveExecutionConfig(
|
|
value: unknown,
|
|
fallback: MemoryDreamingExecutionConfig,
|
|
): MemoryDreamingExecutionConfig {
|
|
const record = asRecord(value);
|
|
const maxOutputTokens = normalizeOptionalPositiveInt(record?.maxOutputTokens);
|
|
const timeoutMs = normalizeOptionalPositiveInt(record?.timeoutMs);
|
|
const temperatureRaw = record?.temperature;
|
|
const temperature =
|
|
typeof temperatureRaw === "number" && Number.isFinite(temperatureRaw) && temperatureRaw >= 0
|
|
? Math.min(2, temperatureRaw)
|
|
: undefined;
|
|
|
|
return {
|
|
speed: normalizeSpeed(record?.speed) ?? fallback.speed,
|
|
thinking: normalizeThinking(record?.thinking) ?? fallback.thinking,
|
|
budget: normalizeBudget(record?.budget) ?? fallback.budget,
|
|
...(normalizeTrimmedString(record?.model)
|
|
? { model: normalizeTrimmedString(record?.model) }
|
|
: {}),
|
|
...(typeof maxOutputTokens === "number" ? { maxOutputTokens } : {}),
|
|
...(typeof temperature === "number" ? { temperature } : {}),
|
|
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
};
|
|
}
|
|
|
|
function normalizePathForComparison(input: string): string {
|
|
const normalized = path.resolve(input);
|
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
}
|
|
|
|
function formatLocalIsoDay(epochMs: number): string {
|
|
const date = new Date(epochMs);
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
export function resolveMemoryCorePluginConfig(
|
|
cfg: OpenClawConfig | Record<string, unknown> | undefined,
|
|
): Record<string, unknown> | undefined {
|
|
const root = asRecord(cfg);
|
|
const plugins = asRecord(root?.plugins);
|
|
const entries = asRecord(plugins?.entries);
|
|
const memoryCore = asRecord(entries?.["memory-core"]);
|
|
return asRecord(memoryCore?.config) ?? undefined;
|
|
}
|
|
|
|
export function resolveMemoryDreamingConfig(params: {
|
|
pluginConfig?: Record<string, unknown>;
|
|
cfg?: OpenClawConfig;
|
|
}): MemoryDreamingConfig {
|
|
const dreaming = asRecord(params.pluginConfig?.dreaming);
|
|
const timezone =
|
|
normalizeTrimmedString(dreaming?.timezone) ??
|
|
normalizeTrimmedString(params.cfg?.agents?.defaults?.userTimezone) ??
|
|
DEFAULT_MEMORY_DREAMING_TIMEZONE;
|
|
const storage = asRecord(dreaming?.storage);
|
|
const execution = asRecord(dreaming?.execution);
|
|
const phases = asRecord(dreaming?.phases);
|
|
|
|
const defaultExecution = resolveExecutionConfig(execution?.defaults, {
|
|
speed: DEFAULT_MEMORY_DREAMING_SPEED,
|
|
thinking: DEFAULT_MEMORY_DREAMING_THINKING,
|
|
budget: DEFAULT_MEMORY_DREAMING_BUDGET,
|
|
});
|
|
|
|
const light = asRecord(phases?.light);
|
|
const deep = asRecord(phases?.deep);
|
|
const rem = asRecord(phases?.rem);
|
|
const deepRecovery = asRecord(deep?.recovery);
|
|
const maxAgeDays = normalizeOptionalPositiveInt(deep?.maxAgeDays);
|
|
|
|
return {
|
|
enabled: normalizeBoolean(dreaming?.enabled, DEFAULT_MEMORY_DREAMING_ENABLED),
|
|
...(timezone ? { timezone } : {}),
|
|
verboseLogging: normalizeBoolean(
|
|
dreaming?.verboseLogging,
|
|
DEFAULT_MEMORY_DREAMING_VERBOSE_LOGGING,
|
|
),
|
|
storage: {
|
|
mode: normalizeStorageMode(storage?.mode),
|
|
separateReports: normalizeBoolean(
|
|
storage?.separateReports,
|
|
DEFAULT_MEMORY_DREAMING_SEPARATE_REPORTS,
|
|
),
|
|
},
|
|
execution: {
|
|
defaults: defaultExecution,
|
|
},
|
|
phases: {
|
|
light: {
|
|
enabled: normalizeBoolean(light?.enabled, true),
|
|
cron: normalizeTrimmedString(light?.cron) ?? DEFAULT_MEMORY_LIGHT_DREAMING_CRON_EXPR,
|
|
lookbackDays: normalizeNonNegativeInt(
|
|
light?.lookbackDays,
|
|
DEFAULT_MEMORY_LIGHT_DREAMING_LOOKBACK_DAYS,
|
|
),
|
|
limit: normalizeNonNegativeInt(light?.limit, DEFAULT_MEMORY_LIGHT_DREAMING_LIMIT),
|
|
dedupeSimilarity: normalizeSimilarity(
|
|
light?.dedupeSimilarity,
|
|
DEFAULT_MEMORY_LIGHT_DREAMING_DEDUPE_SIMILARITY,
|
|
),
|
|
sources: normalizeStringArray(
|
|
light?.sources,
|
|
["daily", "sessions", "recall"] as const,
|
|
DEFAULT_MEMORY_LIGHT_DREAMING_SOURCES,
|
|
),
|
|
execution: resolveExecutionConfig(light?.execution, {
|
|
...defaultExecution,
|
|
speed: "fast",
|
|
thinking: "low",
|
|
budget: "cheap",
|
|
}),
|
|
},
|
|
deep: {
|
|
enabled: normalizeBoolean(deep?.enabled, true),
|
|
cron: normalizeTrimmedString(deep?.cron) ?? DEFAULT_MEMORY_DEEP_DREAMING_CRON_EXPR,
|
|
limit: normalizeNonNegativeInt(deep?.limit, DEFAULT_MEMORY_DEEP_DREAMING_LIMIT),
|
|
minScore: normalizeScore(deep?.minScore, DEFAULT_MEMORY_DEEP_DREAMING_MIN_SCORE),
|
|
minRecallCount: normalizeNonNegativeInt(
|
|
deep?.minRecallCount,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_MIN_RECALL_COUNT,
|
|
),
|
|
minUniqueQueries: normalizeNonNegativeInt(
|
|
deep?.minUniqueQueries,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_MIN_UNIQUE_QUERIES,
|
|
),
|
|
recencyHalfLifeDays: normalizeNonNegativeInt(
|
|
deep?.recencyHalfLifeDays,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECENCY_HALF_LIFE_DAYS,
|
|
),
|
|
...(typeof maxAgeDays === "number"
|
|
? { maxAgeDays }
|
|
: typeof DEFAULT_MEMORY_DEEP_DREAMING_MAX_AGE_DAYS === "number"
|
|
? { maxAgeDays: DEFAULT_MEMORY_DEEP_DREAMING_MAX_AGE_DAYS }
|
|
: {}),
|
|
sources: normalizeStringArray(
|
|
deep?.sources,
|
|
["daily", "memory", "sessions", "logs", "recall"] as const,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_SOURCES,
|
|
),
|
|
recovery: {
|
|
enabled: normalizeBoolean(
|
|
deepRecovery?.enabled,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_ENABLED,
|
|
),
|
|
triggerBelowHealth: normalizeScore(
|
|
deepRecovery?.triggerBelowHealth,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_TRIGGER_BELOW_HEALTH,
|
|
),
|
|
lookbackDays: normalizeNonNegativeInt(
|
|
deepRecovery?.lookbackDays,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_LOOKBACK_DAYS,
|
|
),
|
|
maxRecoveredCandidates: normalizeNonNegativeInt(
|
|
deepRecovery?.maxRecoveredCandidates,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_MAX_CANDIDATES,
|
|
),
|
|
minRecoveryConfidence: normalizeScore(
|
|
deepRecovery?.minRecoveryConfidence,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_MIN_CONFIDENCE,
|
|
),
|
|
autoWriteMinConfidence: normalizeScore(
|
|
deepRecovery?.autoWriteMinConfidence,
|
|
DEFAULT_MEMORY_DEEP_DREAMING_RECOVERY_AUTO_WRITE_MIN_CONFIDENCE,
|
|
),
|
|
},
|
|
execution: resolveExecutionConfig(deep?.execution, {
|
|
...defaultExecution,
|
|
speed: "balanced",
|
|
thinking: "high",
|
|
budget: "medium",
|
|
}),
|
|
},
|
|
rem: {
|
|
enabled: normalizeBoolean(rem?.enabled, true),
|
|
cron: normalizeTrimmedString(rem?.cron) ?? DEFAULT_MEMORY_REM_DREAMING_CRON_EXPR,
|
|
lookbackDays: normalizeNonNegativeInt(
|
|
rem?.lookbackDays,
|
|
DEFAULT_MEMORY_REM_DREAMING_LOOKBACK_DAYS,
|
|
),
|
|
limit: normalizeNonNegativeInt(rem?.limit, DEFAULT_MEMORY_REM_DREAMING_LIMIT),
|
|
minPatternStrength: normalizeScore(
|
|
rem?.minPatternStrength,
|
|
DEFAULT_MEMORY_REM_DREAMING_MIN_PATTERN_STRENGTH,
|
|
),
|
|
sources: normalizeStringArray(
|
|
rem?.sources,
|
|
["memory", "daily", "deep"] as const,
|
|
DEFAULT_MEMORY_REM_DREAMING_SOURCES,
|
|
),
|
|
execution: resolveExecutionConfig(rem?.execution, {
|
|
...defaultExecution,
|
|
speed: "slow",
|
|
thinking: "high",
|
|
budget: "expensive",
|
|
}),
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
export function resolveMemoryDeepDreamingConfig(params: {
|
|
pluginConfig?: Record<string, unknown>;
|
|
cfg?: OpenClawConfig;
|
|
}): MemoryDeepDreamingConfig & {
|
|
timezone?: string;
|
|
verboseLogging: boolean;
|
|
storage: MemoryDreamingStorageConfig;
|
|
} {
|
|
const resolved = resolveMemoryDreamingConfig(params);
|
|
return {
|
|
...resolved.phases.deep,
|
|
enabled: resolved.enabled && resolved.phases.deep.enabled,
|
|
...(resolved.timezone ? { timezone: resolved.timezone } : {}),
|
|
verboseLogging: resolved.verboseLogging,
|
|
storage: resolved.storage,
|
|
};
|
|
}
|
|
|
|
export function resolveMemoryLightDreamingConfig(params: {
|
|
pluginConfig?: Record<string, unknown>;
|
|
cfg?: OpenClawConfig;
|
|
}): MemoryLightDreamingConfig & {
|
|
timezone?: string;
|
|
verboseLogging: boolean;
|
|
storage: MemoryDreamingStorageConfig;
|
|
} {
|
|
const resolved = resolveMemoryDreamingConfig(params);
|
|
return {
|
|
...resolved.phases.light,
|
|
enabled: resolved.enabled && resolved.phases.light.enabled,
|
|
...(resolved.timezone ? { timezone: resolved.timezone } : {}),
|
|
verboseLogging: resolved.verboseLogging,
|
|
storage: resolved.storage,
|
|
};
|
|
}
|
|
|
|
export function resolveMemoryRemDreamingConfig(params: {
|
|
pluginConfig?: Record<string, unknown>;
|
|
cfg?: OpenClawConfig;
|
|
}): MemoryRemDreamingConfig & {
|
|
timezone?: string;
|
|
verboseLogging: boolean;
|
|
storage: MemoryDreamingStorageConfig;
|
|
} {
|
|
const resolved = resolveMemoryDreamingConfig(params);
|
|
return {
|
|
...resolved.phases.rem,
|
|
enabled: resolved.enabled && resolved.phases.rem.enabled,
|
|
...(resolved.timezone ? { timezone: resolved.timezone } : {}),
|
|
verboseLogging: resolved.verboseLogging,
|
|
storage: resolved.storage,
|
|
};
|
|
}
|
|
|
|
export function formatMemoryDreamingDay(epochMs: number, timezone?: string): string {
|
|
if (!timezone) {
|
|
return formatLocalIsoDay(epochMs);
|
|
}
|
|
try {
|
|
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
timeZone: timezone,
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
}).formatToParts(new Date(epochMs));
|
|
const values = new Map(parts.map((part) => [part.type, part.value]));
|
|
const year = values.get("year");
|
|
const month = values.get("month");
|
|
const day = values.get("day");
|
|
if (year && month && day) {
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
} catch {
|
|
// Fall back to host-local day for invalid or unsupported timezones.
|
|
}
|
|
return formatLocalIsoDay(epochMs);
|
|
}
|
|
|
|
export function isSameMemoryDreamingDay(
|
|
firstEpochMs: number,
|
|
secondEpochMs: number,
|
|
timezone?: string,
|
|
): boolean {
|
|
return (
|
|
formatMemoryDreamingDay(firstEpochMs, timezone) ===
|
|
formatMemoryDreamingDay(secondEpochMs, timezone)
|
|
);
|
|
}
|
|
|
|
export function resolveMemoryDreamingWorkspaces(cfg: OpenClawConfig): MemoryDreamingWorkspace[] {
|
|
const configured = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
|
|
const agentIds: string[] = [];
|
|
const seenAgents = new Set<string>();
|
|
for (const entry of configured) {
|
|
if (!entry || typeof entry !== "object" || typeof entry.id !== "string") {
|
|
continue;
|
|
}
|
|
const id = entry.id.trim().toLowerCase();
|
|
if (!id || seenAgents.has(id)) {
|
|
continue;
|
|
}
|
|
seenAgents.add(id);
|
|
agentIds.push(id);
|
|
}
|
|
if (agentIds.length === 0) {
|
|
agentIds.push(resolveDefaultAgentId(cfg));
|
|
}
|
|
|
|
const byWorkspace = new Map<string, MemoryDreamingWorkspace>();
|
|
for (const agentId of agentIds) {
|
|
if (!resolveMemorySearchConfig(cfg, agentId)) {
|
|
continue;
|
|
}
|
|
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId)?.trim();
|
|
if (!workspaceDir) {
|
|
continue;
|
|
}
|
|
const key = normalizePathForComparison(workspaceDir);
|
|
const existing = byWorkspace.get(key);
|
|
if (existing) {
|
|
existing.agentIds.push(agentId);
|
|
continue;
|
|
}
|
|
byWorkspace.set(key, { workspaceDir, agentIds: [agentId] });
|
|
}
|
|
return [...byWorkspace.values()];
|
|
}
|