openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
468 lines (467 loc) • 23 kB
JavaScript
import { M as resolveNonNegativeIntegerOption } from "./number-coercion-CLj0HTDM.js";
import "./src-vebZIeLe.js";
import { t as stableStringify } from "./stable-stringify-C8X7niaI.js";
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import "./config-Cs0XXL3x.js";
import { A as listTasksForOwnerKey, w as listFreshTasksForOwnerKey } from "./task-registry-BP9SCk1B.js";
import "./runtime-internal-CdH7E8By.js";
//#region src/agents/session-async-task-status.ts
/**
* Session async-task lookup helpers for avoiding duplicate long-running work
* and reporting the active task back through tool/status metadata.
*/
const DEFAULT_ACTIVE_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
/** Find the active queued/running task that matches a session and optional filters. */
function findActiveSessionTask(params) {
const normalizedSessionKey = normalizeOptionalString(params.sessionKey);
if (!normalizedSessionKey) return;
const statuses = params.statuses ?? DEFAULT_ACTIVE_STATUSES;
const taskKind = normalizeOptionalString(params.taskKind);
const taskLabel = normalizeOptionalString(params.task);
const sourceIdPrefix = normalizeOptionalString(params.sourceIdPrefix);
const matches = listTasksForOwnerKey(normalizedSessionKey).filter((task) => {
if (task.scopeKind !== "session") return false;
if (params.runtime && task.runtime !== params.runtime) return false;
if (!statuses.has(task.status)) return false;
if (taskKind && task.taskKind !== taskKind) return false;
if (taskLabel) {
if (normalizeOptionalString(task.task) !== taskLabel) return false;
}
if (sourceIdPrefix) {
const sourceId = normalizeOptionalString(task.sourceId) ?? "";
if (sourceId !== sourceIdPrefix && !sourceId.startsWith(`${sourceIdPrefix}:`)) return false;
}
return true;
});
if (matches.length === 0) return;
return matches.find((task) => task.status === "running") ?? matches[0];
}
/** Build tool details that point callers at the already-active async task. */
function buildSessionAsyncTaskStatusDetails(task) {
return {
async: true,
active: true,
existingTask: true,
status: task.status,
task: {
taskId: task.taskId,
...task.runId ? { runId: task.runId } : {}
},
...task.taskKind ? { taskKind: task.taskKind } : {},
...task.progressSummary ? { progressSummary: task.progressSummary } : {},
...task.sourceId ? { sourceId: task.sourceId } : {}
};
}
//#endregion
//#region src/agents/media-generation-task-status-shared.ts
/**
* Shared media generation task status and duplicate-guard helpers.
*
* Image/video task modules use this to track recent starts, find active
* background tasks, and build consistent user/prompt status messages.
*/
/** Marks media as ready while requester delivery is still being confirmed. */
const MEDIA_GENERATION_DELIVERING_COMPLETION_PROGRESS = "Generated media; delivering completion";
const recentMediaGenerationTaskStarts = /* @__PURE__ */ new Map();
const RECENT_MEDIA_GENERATION_TASK_START_CACHE_MS = 12e4;
/** Builds a stable request key for media generation duplicate detection. */
function buildMediaGenerationRequestKey(value) {
return stableStringify(value);
}
function buildRecentMediaGenerationTaskKey(params) {
const sessionKey = normalizeOptionalString(params.sessionKey);
const taskKind = normalizeOptionalString(params.taskKind);
const sourcePrefix = normalizeOptionalString(params.sourcePrefix);
if (!sessionKey || !taskKind || !sourcePrefix) return;
return `${params.agentId?.trim() ?? "unknown"}\0${sessionKey}\0${taskKind}\0${sourcePrefix}`;
}
function isRecentMediaGenerationTaskRecord(params) {
const activityAt = params.task.endedAt ?? params.task.lastEventAt ?? params.task.startedAt ?? params.task.createdAt;
return Number.isFinite(activityAt) && params.nowMs - activityAt <= params.maxAgeMs;
}
function pruneRecentMediaGenerationTaskStarts(params) {
for (const [key, entries] of recentMediaGenerationTaskStarts.entries()) {
if (params.preserveKey === key) continue;
const freshEntries = entries.filter((entry) => isRecentMediaGenerationTaskRecord({
task: entry.task,
...params
}));
if (freshEntries.length > 0) recentMediaGenerationTaskStarts.set(key, freshEntries);
else recentMediaGenerationTaskStarts.delete(key);
}
}
function mediaGenerationSourceMatches(task, sourcePrefix) {
const sourceId = task.sourceId?.trim() ?? "";
return sourceId === sourcePrefix || sourceId.startsWith(`${sourcePrefix}:`);
}
function mediaGenerationTaskLabelMatches(task, taskLabel) {
return normalizeOptionalString(task.task) === taskLabel;
}
function resolveMediaGenerationTaskRequesterAgentId(task) {
const explicit = normalizeOptionalString(task.requesterAgentId);
if (explicit) return explicit;
const ownerKey = normalizeOptionalString(task.ownerKey ?? task.requesterSessionKey);
const parsed = parseAgentSessionKey(ownerKey)?.agentId;
if (parsed) return parsed;
if (!ownerKey) return;
try {
return resolveSessionAgentId({
config: getRuntimeConfig(),
sessionKey: ownerKey
});
} catch {
return;
}
}
function isTaskStillBlockingDuplicateGuard(task) {
return task.status === "queued" || task.status === "running";
}
function isTaskRecentSuccessfulDuplicate(params) {
return params.task.status === "succeeded" && params.task.terminalOutcome !== "blocked" && Boolean(params.requestKey && params.cachedRequestKey === params.requestKey) && isRecentMediaGenerationTaskRecord({
task: params.task,
maxAgeMs: params.maxAgeMs,
nowMs: params.nowMs
});
}
function recentMediaGenerationTaskStartMatches(left, right) {
if (left.requestKey && right.requestKey) return left.requestKey === right.requestKey;
if (left.task.runId && right.task.runId) return left.task.runId === right.task.runId;
return left.task.taskId === right.task.taskId;
}
function findPersistedTaskForRecentMediaGenerationStart(params) {
return listFreshTasksForOwnerKey(params.sessionKey).find((task) => {
if (task.runtime !== "cli" || task.scopeKind !== "session" || task.taskKind !== params.taskKind || !mediaGenerationSourceMatches(task, params.sourcePrefix) || params.agentId && resolveMediaGenerationTaskRequesterAgentId(task) !== params.agentId) return false;
if (task.taskId === params.cachedTask.taskId) return true;
return Boolean(task.runId && task.runId === params.cachedTask.runId);
});
}
/** Records a just-started media task so duplicate guards work before persistence. */
function recordRecentMediaGenerationTaskStartForSession(params) {
const key = buildRecentMediaGenerationTaskKey(params);
const sessionKey = normalizeOptionalString(params.sessionKey);
if (!key || !sessionKey) return;
const nowMs = params.nowMs ?? Date.now();
pruneRecentMediaGenerationTaskStarts({
maxAgeMs: RECENT_MEDIA_GENERATION_TASK_START_CACHE_MS,
nowMs,
preserveKey: key
});
const entry = {
requestKey: normalizeOptionalString(params.requestKey),
task: {
taskId: params.taskId,
runtime: "cli",
taskKind: params.taskKind,
sourceId: params.providerId?.trim() ? `${params.sourcePrefix}:${params.providerId.trim()}` : params.sourcePrefix,
requesterSessionKey: sessionKey,
requesterAgentId: params.agentId,
ownerKey: sessionKey,
scopeKind: "session",
...params.runId ? { runId: params.runId } : {},
task: params.taskLabel,
status: "running",
deliveryStatus: "not_applicable",
notifyPolicy: "silent",
createdAt: nowMs,
startedAt: nowMs,
lastEventAt: nowMs,
progressSummary: params.progressSummary
}
};
const previousEntries = (recentMediaGenerationTaskStarts.get(key) ?? []).filter((entryLocal) => isRecentMediaGenerationTaskRecord({
task: entryLocal.task,
maxAgeMs: RECENT_MEDIA_GENERATION_TASK_START_CACHE_MS,
nowMs
}));
recentMediaGenerationTaskStarts.set(key, [...previousEntries.filter((previousEntry) => !recentMediaGenerationTaskStartMatches(previousEntry, entry)), entry]);
}
/** Finds a recent started media task from memory or persisted task state. */
function findRecentStartedMediaGenerationTaskForSession(params) {
const key = buildRecentMediaGenerationTaskKey(params);
const sessionKey = normalizeOptionalString(params.sessionKey);
if (!key || !sessionKey) return;
const nowMs = params.nowMs ?? Date.now();
const maxAgeMs = resolveNonNegativeIntegerOption(params.maxAgeMs, 0);
const taskLabel = normalizeOptionalString(params.taskLabel);
pruneRecentMediaGenerationTaskStarts({
maxAgeMs,
nowMs,
preserveKey: key
});
const entries = recentMediaGenerationTaskStarts.get(key);
if (!entries?.length) return;
const retainedEntries = [];
for (const entry of entries.toReversed()) {
const task = entry.task;
const persistedTask = findPersistedTaskForRecentMediaGenerationStart({
sessionKey,
agentId: params.agentId,
cachedTask: task,
taskKind: params.taskKind,
sourcePrefix: params.sourcePrefix
});
if (persistedTask) {
const persistedTaskLabelMatches = !taskLabel || mediaGenerationTaskLabelMatches(persistedTask, taskLabel);
if (isTaskStillBlockingDuplicateGuard(persistedTask) && persistedTaskLabelMatches) return persistedTask;
if (isTaskRecentSuccessfulDuplicate({
task: persistedTask,
requestKey: params.requestKey,
cachedRequestKey: entry.requestKey,
maxAgeMs,
nowMs
})) return persistedTask;
if (isRecentMediaGenerationTaskRecord({
task: persistedTask,
maxAgeMs,
nowMs
})) retainedEntries.push(entry);
continue;
}
if (isRecentMediaGenerationTaskRecord({
task,
maxAgeMs,
nowMs
})) {
const cachedTaskLabelMatches = !taskLabel || mediaGenerationTaskLabelMatches(task, taskLabel);
if (isTaskStillBlockingDuplicateGuard(task) && cachedTaskLabelMatches) return { ...task };
retainedEntries.push(entry);
}
}
if (retainedEntries.length > 0) recentMediaGenerationTaskStarts.set(key, retainedEntries.toReversed());
else recentMediaGenerationTaskStarts.delete(key);
}
/** Clears in-memory duplicate guards between tests. */
function resetRecentMediaGenerationDuplicateGuardsForTests() {
recentMediaGenerationTaskStarts.clear();
}
if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.mediaGenerationDuplicateGuardTestApi")] = { resetRecentMediaGenerationDuplicateGuardsForTests };
/** Extracts a provider id from a media task source id with the given prefix. */
function getMediaGenerationTaskProviderId(task, sourcePrefix) {
const sourceId = task.sourceId?.trim() ?? "";
if (!sourceId.startsWith(`${sourcePrefix}:`)) return;
return sourceId.slice(`${sourcePrefix}:`.length).trim() || void 0;
}
/** Finds the highest-priority active media generation task for a session. */
function findActiveMediaGenerationTaskForSession(params) {
return listActiveMediaGenerationTasksForSession(params)[0];
}
/** Lists active media generation tasks for a session, preferring running tasks. */
function listActiveMediaGenerationTasksForSession(params) {
const sessionKey = normalizeOptionalString(params.sessionKey);
if (!sessionKey) return [];
const taskLabel = normalizeOptionalString(params.taskLabel);
const sourcePrefix = normalizeOptionalString(params.sourcePrefix);
const matches = listFreshTasksForOwnerKey(sessionKey).filter((task) => {
if (task.runtime !== "cli" || task.scopeKind !== "session" || task.taskKind !== params.taskKind || !isTaskStillBlockingDuplicateGuard(task)) return false;
if (params.agentId && resolveMediaGenerationTaskRequesterAgentId(task) !== params.agentId) return false;
if (sourcePrefix && !mediaGenerationSourceMatches(task, sourcePrefix)) return false;
if (taskLabel && !mediaGenerationTaskLabelMatches(task, taskLabel)) return false;
if (params.excludeDeliveringCompletion && task.progressSummary === "Generated media; delivering completion") return false;
return true;
});
return [...matches.filter((task) => task.status === "running"), ...matches.filter((task) => task.status !== "running")];
}
/** Finds a task that should block duplicate media generation for a session. */
function findDuplicateGuardMediaGenerationTaskForSession(params) {
return findRecentStartedMediaGenerationTaskForSession(params) ?? findActiveMediaGenerationTaskForSession({
sessionKey: params.sessionKey,
agentId: params.agentId,
taskKind: params.taskKind,
sourcePrefix: params.sourcePrefix,
taskLabel: params.taskLabel
}) ?? void 0;
}
/** Builds structured status details for one media generation task. */
function buildMediaGenerationTaskStatusDetails(params) {
const provider = getMediaGenerationTaskProviderId(params.task, params.sourcePrefix);
return {
...buildSessionAsyncTaskStatusDetails(params.task),
active: isTaskStillBlockingDuplicateGuard(params.task),
...provider ? { provider } : {}
};
}
/** Builds structured status details for a list of media generation tasks. */
function buildMediaGenerationTaskStatusListDetails(params) {
return {
async: true,
active: true,
existingTask: true,
taskCount: params.tasks.length,
tasks: params.tasks.map((task) => buildMediaGenerationTaskStatusDetails({
task,
sourcePrefix: params.sourcePrefix
}))
};
}
/** Builds user-facing status text for one media generation task. */
function buildMediaGenerationTaskStatusText(params) {
const provider = getMediaGenerationTaskProviderId(params.task, params.sourcePrefix);
const active = params.task.status === "queued" || params.task.status === "running" || params.task.terminalOutcome === "blocked";
return [
active ? `${params.nounLabel} task ${params.task.taskId} is already ${params.task.status}${provider ? ` with ${provider}` : ""}.` : `${params.nounLabel} task ${params.task.taskId} recently ${params.task.status}${provider ? ` with ${provider}` : ""}.`,
params.task.progressSummary ? `Progress: ${params.task.progressSummary}.` : null,
params.duplicateGuard ? active ? `Do not call ${params.toolName} again for this request. Wait for the completion event; the completion agent will send the finished ${params.completionLabel} here.` : `Do not call ${params.toolName} again for the same request; this recent ${params.completionLabel} generation already completed.` : `Wait for the completion event; the completion agent will send the finished ${params.completionLabel} here when it's ready.`
].filter((entry) => Boolean(entry)).join("\n");
}
/** Builds user-facing status text for multiple active media generation tasks. */
function buildMediaGenerationTaskStatusListText(params) {
const nounLabel = normalizeLowercaseStringOrEmpty(params.nounLabel);
return [
`${params.tasks.length} active ${nounLabel} tasks are queued or running for this session.`,
...params.tasks.map((task) => {
const provider = getMediaGenerationTaskProviderId(task, params.sourcePrefix);
const runId = task.runId ? ` (run ${task.runId})` : "";
const progress = task.progressSummary ? ` Progress: ${task.progressSummary}.` : "";
return `- Task ${task.taskId}${runId} is ${task.status}${provider ? ` with ${provider}` : ""}.${progress}`;
}),
`Wait for the completion events; the completion agent will send the finished ${params.completionLabel} here when each is ready.`,
`Only start a new ${params.toolName} call if the user clearly asks for different/new ${params.completionLabel}.`
].join("\n");
}
/** Builds prompt context warning an agent about an active media generation task. */
function buildActiveMediaGenerationTaskPromptContextForSession(params) {
const task = findActiveMediaGenerationTaskForSession({
sessionKey: params.sessionKey,
agentId: params.agentId,
taskKind: params.taskKind,
sourcePrefix: params.sourcePrefix,
excludeDeliveringCompletion: true
});
if (!task) return;
const provider = getMediaGenerationTaskProviderId(task, params.sourcePrefix);
return [
`An active ${normalizeLowercaseStringOrEmpty(params.nounLabel)} background task already exists for this session.`,
`Task ${task.taskId} is currently ${task.status}${provider ? ` via ${provider}` : ""}.`,
task.progressSummary ? `Current progress: ${task.progressSummary}.` : null,
`Do not call \`${params.toolName}\` again for the same request while that task is queued or running.`,
`If the user asks for progress or whether the work is async, explain the active task state or call \`${params.toolName}\` with \`action:"status"\` instead of starting a new generation.`,
`Only start a new \`${params.toolName}\` call if the user clearly asks for different/new ${params.completionLabel}.`
].filter((entry) => Boolean(entry)).join("\n");
}
/** Specializes shared task lookup, duplicate guards, and status text for one media tool. */
function createMediaGenerationTaskStatusOwner(params) {
const taskIdentity = {
taskKind: params.taskKind,
sourcePrefix: params.toolName
};
const taskPresentation = {
sourcePrefix: params.toolName,
nounLabel: params.nounLabel,
toolName: params.toolName
};
return {
findActiveTaskForSession(sessionKey, request) {
return findActiveMediaGenerationTaskForSession({
...taskIdentity,
sessionKey,
taskLabel: request?.prompt,
agentId: request?.agentId
});
},
listActiveTasksForSession(sessionKey, agentId) {
return listActiveMediaGenerationTasksForSession({
...taskIdentity,
sessionKey,
agentId
});
},
findDuplicateGuardTaskForSession(sessionKey, request) {
return findDuplicateGuardMediaGenerationTaskForSession({
...taskIdentity,
sessionKey,
taskLabel: request?.prompt,
requestKey: request?.requestKey,
agentId: request?.agentId,
maxAgeMs: RECENT_MEDIA_GENERATION_TASK_START_CACHE_MS
});
},
buildTaskStatusDetails(task) {
return buildMediaGenerationTaskStatusDetails({
task,
sourcePrefix: params.toolName
});
},
buildTaskStatusListDetails(tasks) {
return buildMediaGenerationTaskStatusListDetails({
tasks,
sourcePrefix: params.toolName
});
},
buildTaskStatusText(task, options) {
return buildMediaGenerationTaskStatusText({
...taskPresentation,
task,
completionLabel: params.completionLabel,
duplicateGuard: options?.duplicateGuard
});
},
buildTaskStatusListText(tasks) {
return buildMediaGenerationTaskStatusListText({
...taskPresentation,
tasks,
completionLabel: params.promptCompletionLabel
});
},
buildActiveTaskPromptContextForSession(sessionKey, agentId) {
return buildActiveMediaGenerationTaskPromptContextForSession({
...taskIdentity,
...taskPresentation,
sessionKey,
agentId,
completionLabel: params.promptCompletionLabel
});
}
};
}
//#endregion
//#region src/agents/media-generation-task-status.ts
/**
* Image generation task status helpers.
*
* These wrap the shared media task status helpers with image-specific task kind,
* source id, duplicate-guard timing, and prompt/status wording.
*/
const IMAGE_GENERATION_TASK_KIND = "image_generation";
/** Image generation keeps multi-task status and prompt-specific duplicate lookup. */
const { findActiveTaskForSession: findActiveImageGenerationTaskForSession, listActiveTasksForSession: listActiveImageGenerationTasksForSession, findDuplicateGuardTaskForSession: findDuplicateGuardImageGenerationTaskForSession, buildTaskStatusDetails: buildImageGenerationTaskStatusDetails, buildTaskStatusListDetails: buildImageGenerationTaskStatusListDetails, buildTaskStatusText: buildImageGenerationTaskStatusText, buildTaskStatusListText: buildImageGenerationTaskStatusListText, buildActiveTaskPromptContextForSession: buildActiveImageGenerationTaskPromptContextForSession } = createMediaGenerationTaskStatusOwner({
taskKind: IMAGE_GENERATION_TASK_KIND,
toolName: "image_generate",
nounLabel: "Image generation",
completionLabel: "image",
promptCompletionLabel: "images"
});
/**
* Music-generation task status adapters. The module specializes the shared
* media-generation task helpers with music task ids, duplicate guards, and
* user-facing status text.
*/
/** Task kind used for music generation task registry records. */
const MUSIC_GENERATION_TASK_KIND = "music_generation";
/** Binds music-specific task identity, duplicate guards, and visible status text. */
const { findActiveTaskForSession: findActiveMusicGenerationTaskForSession, findDuplicateGuardTaskForSession: findDuplicateGuardMusicGenerationTaskForSession, buildTaskStatusDetails: buildMusicGenerationTaskStatusDetails, buildTaskStatusText: buildMusicGenerationTaskStatusText, buildActiveTaskPromptContextForSession: buildActiveMusicGenerationTaskPromptContextForSession } = createMediaGenerationTaskStatusOwner({
taskKind: MUSIC_GENERATION_TASK_KIND,
toolName: "music_generate",
nounLabel: "Music generation",
completionLabel: "music",
promptCompletionLabel: "music tracks"
});
/**
* Video generation task status helpers.
*
* These wrap the generic media task status helpers with video-specific kind,
* source, labels, duplicate-guard timing, and prompt-context wording.
*/
const VIDEO_GENERATION_TASK_KIND = "video_generation";
/** Binds video-specific task identity, duplicate guards, and visible status text. */
const { findActiveTaskForSession: findActiveVideoGenerationTaskForSession, findDuplicateGuardTaskForSession: findDuplicateGuardVideoGenerationTaskForSession, buildTaskStatusDetails: buildVideoGenerationTaskStatusDetails, buildTaskStatusText: buildVideoGenerationTaskStatusText, buildActiveTaskPromptContextForSession: buildActiveVideoGenerationTaskPromptContextForSession } = createMediaGenerationTaskStatusOwner({
taskKind: VIDEO_GENERATION_TASK_KIND,
toolName: "video_generate",
nounLabel: "Video generation",
completionLabel: "video",
promptCompletionLabel: "videos"
});
//#endregion
export { buildMediaGenerationRequestKey as C, MEDIA_GENERATION_DELIVERING_COMPLETION_PROGRESS as S, findActiveSessionTask as T, findActiveVideoGenerationTaskForSession as _, buildActiveMusicGenerationTaskPromptContextForSession as a, findDuplicateGuardVideoGenerationTaskForSession as b, buildImageGenerationTaskStatusListDetails as c, buildMusicGenerationTaskStatusDetails as d, buildMusicGenerationTaskStatusText as f, findActiveMusicGenerationTaskForSession as g, findActiveImageGenerationTaskForSession as h, buildActiveImageGenerationTaskPromptContextForSession as i, buildImageGenerationTaskStatusListText as l, buildVideoGenerationTaskStatusText as m, MUSIC_GENERATION_TASK_KIND as n, buildActiveVideoGenerationTaskPromptContextForSession as o, buildVideoGenerationTaskStatusDetails as p, VIDEO_GENERATION_TASK_KIND as r, buildImageGenerationTaskStatusDetails as s, IMAGE_GENERATION_TASK_KIND as t, buildImageGenerationTaskStatusText as u, findDuplicateGuardImageGenerationTaskForSession as v, recordRecentMediaGenerationTaskStartForSession as w, listActiveImageGenerationTasksForSession as x, findDuplicateGuardMusicGenerationTaskForSession as y };