refactor: lazy load outbound transcript mirroring

This commit is contained in:
Shakker 2026-04-01 16:55:42 +01:00 committed by Shakker
parent e26a590f7a
commit 4a81771290
6 changed files with 72 additions and 63 deletions

View File

@ -0,0 +1,52 @@
import path from "node:path";
function stripQuery(value: string): string {
const noHash = value.split("#")[0] ?? value;
return noHash.split("?")[0] ?? noHash;
}
function extractFileNameFromMediaUrl(value: string): string | null {
const trimmed = value.trim();
if (!trimmed) {
return null;
}
const cleaned = stripQuery(trimmed);
try {
const parsed = new URL(cleaned);
const base = path.basename(parsed.pathname);
if (!base) {
return null;
}
try {
return decodeURIComponent(base);
} catch {
return base;
}
} catch {
const base = path.basename(cleaned);
if (!base || base === "/" || base === ".") {
return null;
}
return base;
}
}
export function resolveMirroredTranscriptText(params: {
text?: string;
mediaUrls?: string[];
}): string | null {
const mediaUrls = params.mediaUrls?.filter((url) => url && url.trim()) ?? [];
if (mediaUrls.length > 0) {
const names = mediaUrls
.map((url) => extractFileNameFromMediaUrl(url))
.filter((name): name is string => Boolean(name && name.trim()));
if (names.length > 0) {
return names.join(", ");
}
return "media";
}
const text = params.text ?? "";
const trimmed = text.trim();
return trimmed ? trimmed : null;
}

View File

@ -0,0 +1 @@
export { appendAssistantMessageToSessionTranscript } from "./transcript.js";

View File

@ -11,59 +11,9 @@ import {
} from "./paths.js";
import { resolveAndPersistSessionFile } from "./session-file.js";
import { loadSessionStore, normalizeStoreSessionKey } from "./store.js";
import { resolveMirroredTranscriptText } from "./transcript-mirror.js";
import type { SessionEntry } from "./types.js";
function stripQuery(value: string): string {
const noHash = value.split("#")[0] ?? value;
return noHash.split("?")[0] ?? noHash;
}
function extractFileNameFromMediaUrl(value: string): string | null {
const trimmed = value.trim();
if (!trimmed) {
return null;
}
const cleaned = stripQuery(trimmed);
try {
const parsed = new URL(cleaned);
const base = path.basename(parsed.pathname);
if (!base) {
return null;
}
try {
return decodeURIComponent(base);
} catch {
return base;
}
} catch {
const base = path.basename(cleaned);
if (!base || base === "/" || base === ".") {
return null;
}
return base;
}
}
export function resolveMirroredTranscriptText(params: {
text?: string;
mediaUrls?: string[];
}): string | null {
const mediaUrls = params.mediaUrls?.filter((url) => url && url.trim()) ?? [];
if (mediaUrls.length > 0) {
const names = mediaUrls
.map((url) => extractFileNameFromMediaUrl(url))
.filter((name): name is string => Boolean(name && name.trim()));
if (names.length > 0) {
return names.join(", ");
}
return "media";
}
const text = params.text ?? "";
const trimmed = text.trim();
return trimmed ? trimmed : null;
}
async function ensureSessionHeader(params: {
sessionFile: string;
sessionId: string;

View File

@ -100,10 +100,10 @@ export const internalHookMocks = _internalHookMocks;
export const queueMocks = _queueMocks;
export const logMocks = _logMocks;
vi.mock("../../config/sessions.js", async () => {
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
"../../config/sessions.js",
);
vi.mock("../../config/sessions/transcript.runtime.js", async () => {
const actual = await vi.importActual<
typeof import("../../config/sessions/transcript.runtime.js")
>("../../config/sessions/transcript.runtime.js");
return {
...actual,
appendAssistantMessageToSessionTranscript: _mocks.appendAssistantMessageToSessionTranscript,

View File

@ -41,10 +41,10 @@ const logMocks = vi.hoisted(() => ({
warn: vi.fn(),
}));
vi.mock("../../config/sessions.js", async () => {
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
"../../config/sessions.js",
);
vi.mock("../../config/sessions/transcript.runtime.js", async () => {
const actual = await vi.importActual<
typeof import("../../config/sessions/transcript.runtime.js")
>("../../config/sessions/transcript.runtime.js");
return {
...actual,
appendAssistantMessageToSessionTranscript: mocks.appendAssistantMessageToSessionTranscript,

View File

@ -15,10 +15,7 @@ import type {
ChannelOutboundContext,
} from "../../channels/plugins/types.js";
import type { OpenClawConfig } from "../../config/config.js";
import {
appendAssistantMessageToSessionTranscript,
resolveMirroredTranscriptText,
} from "../../config/sessions.js";
import { resolveMirroredTranscriptText } from "../../config/sessions/transcript-mirror.js";
import { fireAndForgetHook } from "../../hooks/fire-and-forget.js";
import { createInternalHookEvent, triggerInternalHook } from "../../hooks/internal-hooks.js";
import {
@ -49,6 +46,14 @@ export { normalizeOutboundPayloads } from "./payloads.js";
export { resolveOutboundSendDep, type OutboundSendDeps } from "./send-deps.js";
const log = createSubsystemLogger("outbound/deliver");
let transcriptRuntimePromise:
| Promise<typeof import("../../config/sessions/transcript.runtime.js")>
| undefined;
async function loadTranscriptRuntime() {
transcriptRuntimePromise ??= import("../../config/sessions/transcript.runtime.js");
return await transcriptRuntimePromise;
}
export type OutboundDeliveryResult = {
channel: Exclude<OutboundChannel, "none">;
@ -791,6 +796,7 @@ async function deliverOutboundPayloadsCore(
mediaUrls: params.mirror.mediaUrls,
});
if (mirrorText) {
const { appendAssistantMessageToSessionTranscript } = await loadTranscriptRuntime();
await appendAssistantMessageToSessionTranscript({
agentId: params.mirror.agentId,
sessionKey: params.mirror.sessionKey,