mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:30:43 +00:00
fix(agents): preserve CLI wake-up session metadata (#74171)
* Fix CLI wake-up resume metadata * Rerun CI * ci: re-trigger parity gate
This commit is contained in:
@@ -25,7 +25,11 @@ import {
|
||||
resetClaudeLiveSessionsForTest,
|
||||
runClaudeLiveSessionTurn,
|
||||
} from "./cli-runner/claude-live-session.js";
|
||||
import { buildCliEnvAuthLog, executePreparedCliRun } from "./cli-runner/execute.js";
|
||||
import {
|
||||
buildCliEnvAuthLog,
|
||||
buildCliExecLogLine,
|
||||
executePreparedCliRun,
|
||||
} from "./cli-runner/execute.js";
|
||||
import { buildSystemPrompt } from "./cli-runner/helpers.js";
|
||||
import { setCliRunnerPrepareTestDeps } from "./cli-runner/prepare.js";
|
||||
import type { PreparedCliRunContext } from "./cli-runner/types.js";
|
||||
@@ -127,6 +131,27 @@ function buildPreparedCliRunContext(params: {
|
||||
}
|
||||
|
||||
describe("runCliAgent spawn path", () => {
|
||||
it("formats redacted CLI resume diagnostics without exposing raw session ids", () => {
|
||||
const logLine = buildCliExecLogLine({
|
||||
provider: "claude-cli",
|
||||
model: "claude-opus-4-7",
|
||||
promptChars: 42,
|
||||
trigger: "heartbeat",
|
||||
useResume: true,
|
||||
cliSessionId: "claude-session-secret",
|
||||
resolvedSessionId: "claude-session-secret",
|
||||
reusableSessionId: "claude-session-secret",
|
||||
hasHistoryPrompt: false,
|
||||
});
|
||||
|
||||
expect(logLine).toContain("trigger=heartbeat");
|
||||
expect(logLine).toContain("useResume=true");
|
||||
expect(logLine).toContain("session=present");
|
||||
expect(logLine).toContain("reuse=reusable");
|
||||
expect(logLine).toContain("historyPrompt=none");
|
||||
expect(logLine).not.toContain("claude-session-secret");
|
||||
});
|
||||
|
||||
it("does not inject hardcoded 'Tools are disabled' text into CLI arguments", async () => {
|
||||
supervisorSpawnMock.mockResolvedValueOnce(
|
||||
createManagedRun({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from "node:crypto";
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import { emitAgentEvent } from "../../infra/agent-events.js";
|
||||
import { isTruthyEnvValue } from "../../infra/env.js";
|
||||
@@ -164,6 +165,44 @@ function buildCliEnvMcpLog(childEnv: Record<string, string>): string {
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
function fingerprintCliSessionId(sessionId?: string): string {
|
||||
const trimmed = sessionId?.trim();
|
||||
if (!trimmed) {
|
||||
return "none";
|
||||
}
|
||||
return crypto.createHash("sha256").update(trimmed).digest("hex").slice(0, 12);
|
||||
}
|
||||
|
||||
export function buildCliExecLogLine(params: {
|
||||
provider: string;
|
||||
model: string;
|
||||
promptChars: number;
|
||||
trigger?: string;
|
||||
useResume: boolean;
|
||||
cliSessionId?: string;
|
||||
resolvedSessionId?: string;
|
||||
reusableSessionId?: string;
|
||||
invalidatedReason?: string;
|
||||
hasHistoryPrompt: boolean;
|
||||
}): string {
|
||||
const reuseState = params.reusableSessionId
|
||||
? "reusable"
|
||||
: params.invalidatedReason
|
||||
? `invalidated:${params.invalidatedReason}`
|
||||
: "none";
|
||||
return [
|
||||
`cli exec: provider=${params.provider}`,
|
||||
`model=${params.model}`,
|
||||
`promptChars=${params.promptChars}`,
|
||||
`trigger=${params.trigger ?? "unknown"}`,
|
||||
`useResume=${params.useResume ? "true" : "false"}`,
|
||||
`session=${params.cliSessionId ? "present" : "none"}`,
|
||||
`resumeSession=${params.useResume ? fingerprintCliSessionId(params.resolvedSessionId) : "none"}`,
|
||||
`reuse=${reuseState}`,
|
||||
`historyPrompt=${params.hasHistoryPrompt ? "present" : "none"}`,
|
||||
].join(" ");
|
||||
}
|
||||
|
||||
export function buildCliEnvAuthLog(childEnv: Record<string, string>): string {
|
||||
const hostKeys = listPresentCliAuthEnvKeys(process.env);
|
||||
const childKeys = listPresentCliAuthEnvKeys(childEnv);
|
||||
@@ -273,7 +312,18 @@ export async function executePreparedCliRun(
|
||||
: undefined;
|
||||
try {
|
||||
cliBackendLog.info(
|
||||
`cli exec: provider=${params.provider} model=${context.normalizedModel} promptChars=${basePrompt.length}`,
|
||||
buildCliExecLogLine({
|
||||
provider: params.provider,
|
||||
model: context.normalizedModel,
|
||||
promptChars: basePrompt.length,
|
||||
trigger: params.trigger,
|
||||
useResume,
|
||||
cliSessionId: cliSessionIdToUse,
|
||||
resolvedSessionId,
|
||||
reusableSessionId: context.reusableCliSession.sessionId,
|
||||
invalidatedReason: context.reusableCliSession.invalidatedReason,
|
||||
hasHistoryPrompt: Boolean(context.openClawHistoryPrompt),
|
||||
}),
|
||||
);
|
||||
const logOutputText =
|
||||
isTruthyEnvValue(process.env[CLI_BACKEND_LOG_OUTPUT_ENV]) ||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import {
|
||||
buildExecOverridePromptHint,
|
||||
resolvePromptSessionContextForSystemEvent,
|
||||
resolvePromptSilentReplyConversationType,
|
||||
} from "./get-reply-run.js";
|
||||
import { buildGetReplyCtx, buildGetReplyGroupCtx } from "./get-reply.test-fixtures.js";
|
||||
@@ -106,3 +109,95 @@ describe("resolvePromptSilentReplyConversationType", () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolvePromptSessionContextForSystemEvent", () => {
|
||||
it("rebuilds missing system-event chat metadata from the persisted session entry", () => {
|
||||
const sessionCtx = {
|
||||
Body: "wake up",
|
||||
Provider: "cron-event",
|
||||
Surface: "cron-event",
|
||||
} as TemplateContext;
|
||||
const sessionEntry = {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
chatType: "channel",
|
||||
channel: "discord",
|
||||
groupId: "guild-1",
|
||||
groupChannel: "#ops",
|
||||
space: "Ops Guild",
|
||||
origin: {
|
||||
provider: "discord",
|
||||
surface: "discord",
|
||||
chatType: "channel",
|
||||
to: "channel-1",
|
||||
accountId: "acct-1",
|
||||
threadId: "thread-1",
|
||||
},
|
||||
lastChannel: "discord",
|
||||
lastTo: "channel-1",
|
||||
lastAccountId: "acct-1",
|
||||
lastThreadId: "thread-1",
|
||||
} satisfies SessionEntry;
|
||||
|
||||
const result = resolvePromptSessionContextForSystemEvent({
|
||||
sessionCtx,
|
||||
sessionEntry,
|
||||
ctx: { Provider: "cron-event" },
|
||||
});
|
||||
|
||||
expect(result).not.toBe(sessionCtx);
|
||||
expect(result).toMatchObject({
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "channel",
|
||||
GroupChannel: "#ops",
|
||||
GroupSpace: "Ops Guild",
|
||||
OriginatingChannel: "discord",
|
||||
OriginatingTo: "channel-1",
|
||||
AccountId: "acct-1",
|
||||
MessageThreadId: "thread-1",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps normal user turns on their live chat metadata", () => {
|
||||
const sessionCtx = buildGetReplyGroupCtx({
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "group",
|
||||
}) as TemplateContext;
|
||||
const result = resolvePromptSessionContextForSystemEvent({
|
||||
sessionCtx,
|
||||
sessionEntry: {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
chatType: "direct",
|
||||
channel: "telegram",
|
||||
},
|
||||
ctx: { Provider: "discord" },
|
||||
});
|
||||
|
||||
expect(result).toBe(sessionCtx);
|
||||
});
|
||||
|
||||
it("does not overwrite explicit system-event chat metadata", () => {
|
||||
const sessionCtx = {
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "direct",
|
||||
OriginatingChannel: "discord",
|
||||
} as TemplateContext;
|
||||
const result = resolvePromptSessionContextForSystemEvent({
|
||||
sessionCtx,
|
||||
sessionEntry: {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
chatType: "channel",
|
||||
channel: "discord",
|
||||
groupChannel: "#ops",
|
||||
},
|
||||
ctx: { Provider: "heartbeat" },
|
||||
});
|
||||
|
||||
expect(result).toBe(sessionCtx);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,6 +130,7 @@ let runReplyAgent: typeof import("./agent-runner.runtime.js").runReplyAgent;
|
||||
let routeReply: typeof import("./route-reply.runtime.js").routeReply;
|
||||
let drainFormattedSystemEvents: typeof import("./session-system-events.js").drainFormattedSystemEvents;
|
||||
let resolveTypingMode: typeof import("./typing-mode.js").resolveTypingMode;
|
||||
let buildGroupChatContext: typeof import("./groups.js").buildGroupChatContext;
|
||||
let buildInboundUserContextPrefix: typeof import("./inbound-meta.js").buildInboundUserContextPrefix;
|
||||
let getActiveReplyRunCount: typeof import("./reply-run-registry.js").getActiveReplyRunCount;
|
||||
let replyRunTesting: typeof import("./reply-run-registry.js").__testing;
|
||||
@@ -241,6 +242,7 @@ describe("runPreparedReply media-only handling", () => {
|
||||
({ routeReply } = await import("./route-reply.runtime.js"));
|
||||
({ drainFormattedSystemEvents } = await import("./session-system-events.js"));
|
||||
({ resolveTypingMode } = await import("./typing-mode.js"));
|
||||
({ buildGroupChatContext } = await import("./groups.js"));
|
||||
({ buildInboundUserContextPrefix } = await import("./inbound-meta.js"));
|
||||
({ __testing: replyRunTesting, getActiveReplyRunCount } =
|
||||
await import("./reply-run-registry.js"));
|
||||
@@ -1019,6 +1021,62 @@ describe("runPreparedReply media-only handling", () => {
|
||||
expect(call?.followupRun.transcriptPrompt).toBe("[OpenClaw heartbeat poll]");
|
||||
});
|
||||
|
||||
it("uses persisted Discord chat metadata for system-event CLI static prompt identity", async () => {
|
||||
vi.mocked(buildGroupChatContext).mockImplementationOnce(({ sessionCtx }) =>
|
||||
[`group`, sessionCtx.Provider, sessionCtx.ChatType, sessionCtx.GroupChannel].join(":"),
|
||||
);
|
||||
|
||||
await runPreparedReply(
|
||||
baseParams({
|
||||
opts: { isHeartbeat: true },
|
||||
isNewSession: false,
|
||||
systemSent: true,
|
||||
ctx: {
|
||||
Body: "scheduled wake",
|
||||
RawBody: "scheduled wake",
|
||||
CommandBody: "scheduled wake",
|
||||
Provider: "cron-event",
|
||||
SessionKey: "agent:main:discord:guild-1:channel-1",
|
||||
},
|
||||
sessionCtx: {
|
||||
Body: "scheduled wake",
|
||||
BodyStripped: "scheduled wake",
|
||||
Provider: "cron-event",
|
||||
},
|
||||
sessionEntry: {
|
||||
sessionId: "session-1",
|
||||
updatedAt: 1,
|
||||
systemSent: true,
|
||||
chatType: "channel",
|
||||
channel: "discord",
|
||||
groupId: "guild-1",
|
||||
groupChannel: "#ops",
|
||||
lastChannel: "discord",
|
||||
lastTo: "channel-1",
|
||||
origin: {
|
||||
provider: "discord",
|
||||
surface: "discord",
|
||||
chatType: "channel",
|
||||
to: "channel-1",
|
||||
},
|
||||
} as SessionEntry,
|
||||
}),
|
||||
);
|
||||
|
||||
const call = vi.mocked(runReplyAgent).mock.calls.at(-1)?.[0];
|
||||
expect(buildGroupChatContext).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionCtx: expect.objectContaining({
|
||||
Provider: "discord",
|
||||
Surface: "discord",
|
||||
ChatType: "channel",
|
||||
GroupChannel: "#ops",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(call?.followupRun.run.extraSystemPromptStatic).toBe("group:discord:channel:#ops");
|
||||
});
|
||||
|
||||
it("uses a non-empty transcript marker while keeping bare reset startup instructions out of visible transcript prompt", async () => {
|
||||
await runPreparedReply(
|
||||
baseParams({
|
||||
|
||||
@@ -45,6 +45,7 @@ import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||
import { applySessionHints } from "./body.js";
|
||||
import type { buildCommandContext } from "./commands.js";
|
||||
import type { InlineDirectives } from "./directive-handling.js";
|
||||
import { isSystemEventProvider } from "./effective-reply-route.js";
|
||||
import { shouldUseReplyFastTestRuntime } from "./get-reply-fast-path.js";
|
||||
import { resolvePreparedReplyQueueState } from "./get-reply-run-queue.js";
|
||||
import {
|
||||
@@ -94,6 +95,111 @@ export function resolvePromptSilentReplyConversationType(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function normalizePromptRouteChannel(raw?: string | null): string | undefined {
|
||||
const normalized = normalizeOptionalString(raw);
|
||||
return normalized && normalized !== "none" ? normalized : undefined;
|
||||
}
|
||||
|
||||
function resolvePersistedPromptProvider(entry?: SessionEntry): string | undefined {
|
||||
return (
|
||||
normalizePromptRouteChannel(entry?.origin?.provider) ??
|
||||
normalizePromptRouteChannel(entry?.channel) ??
|
||||
normalizePromptRouteChannel(entry?.lastChannel) ??
|
||||
normalizePromptRouteChannel(entry?.deliveryContext?.channel)
|
||||
);
|
||||
}
|
||||
|
||||
function resolvePersistedPromptSurface(entry?: SessionEntry): string | undefined {
|
||||
return (
|
||||
normalizePromptRouteChannel(entry?.origin?.surface) ?? resolvePersistedPromptProvider(entry)
|
||||
);
|
||||
}
|
||||
|
||||
export function resolvePromptSessionContextForSystemEvent(params: {
|
||||
sessionCtx: TemplateContext;
|
||||
sessionEntry?: SessionEntry;
|
||||
ctx?: Pick<MsgContext, "Provider">;
|
||||
isHeartbeat?: boolean;
|
||||
}): TemplateContext {
|
||||
const { sessionCtx, sessionEntry } = params;
|
||||
const isSystemEvent =
|
||||
params.isHeartbeat === true ||
|
||||
isSystemEventProvider(params.ctx?.Provider) ||
|
||||
isSystemEventProvider(sessionCtx.Provider);
|
||||
if (!isSystemEvent || !sessionEntry) {
|
||||
return sessionCtx;
|
||||
}
|
||||
|
||||
const persistedChatType =
|
||||
normalizeChatType(sessionEntry.chatType) ?? normalizeChatType(sessionEntry.origin?.chatType);
|
||||
const liveChatType = normalizeChatType(sessionCtx.ChatType);
|
||||
const effectiveChatType = liveChatType ?? persistedChatType;
|
||||
const persistedProvider = resolvePersistedPromptProvider(sessionEntry);
|
||||
const persistedSurface = resolvePersistedPromptSurface(sessionEntry);
|
||||
const liveProvider = normalizeOptionalString(sessionCtx.Provider);
|
||||
const liveSurface = normalizeOptionalString(sessionCtx.Surface);
|
||||
const nextProvider =
|
||||
liveProvider && !isSystemEventProvider(liveProvider)
|
||||
? liveProvider
|
||||
: (persistedProvider ?? liveProvider);
|
||||
const nextSurface =
|
||||
liveSurface && !isSystemEventProvider(liveSurface)
|
||||
? liveSurface
|
||||
: (persistedSurface ?? liveSurface);
|
||||
|
||||
const next: TemplateContext = { ...sessionCtx };
|
||||
let changed = false;
|
||||
const setIfMissing = <K extends keyof TemplateContext>(key: K, value: TemplateContext[K]) => {
|
||||
if (next[key] != null && next[key] !== "") {
|
||||
return;
|
||||
}
|
||||
if (value == null || value === "") {
|
||||
return;
|
||||
}
|
||||
next[key] = value;
|
||||
changed = true;
|
||||
};
|
||||
const setIfChanged = <K extends keyof TemplateContext>(key: K, value: TemplateContext[K]) => {
|
||||
if (value == null || value === "" || next[key] === value) {
|
||||
return;
|
||||
}
|
||||
next[key] = value;
|
||||
changed = true;
|
||||
};
|
||||
|
||||
setIfChanged("Provider", nextProvider);
|
||||
setIfChanged("Surface", nextSurface);
|
||||
setIfMissing("ChatType", persistedChatType);
|
||||
if (effectiveChatType === "group" || effectiveChatType === "channel") {
|
||||
setIfMissing("GroupSubject", normalizeOptionalString(sessionEntry.subject));
|
||||
setIfMissing("GroupChannel", normalizeOptionalString(sessionEntry.groupChannel));
|
||||
setIfMissing("GroupSpace", normalizeOptionalString(sessionEntry.space));
|
||||
}
|
||||
setIfMissing("OriginatingChannel", persistedProvider);
|
||||
setIfMissing(
|
||||
"OriginatingTo",
|
||||
normalizeOptionalString(
|
||||
sessionEntry.lastTo ?? sessionEntry.deliveryContext?.to ?? sessionEntry.origin?.to,
|
||||
),
|
||||
);
|
||||
setIfMissing(
|
||||
"AccountId",
|
||||
normalizeOptionalString(
|
||||
sessionEntry.lastAccountId ??
|
||||
sessionEntry.deliveryContext?.accountId ??
|
||||
sessionEntry.origin?.accountId,
|
||||
),
|
||||
);
|
||||
setIfMissing(
|
||||
"MessageThreadId",
|
||||
sessionEntry.lastThreadId ??
|
||||
sessionEntry.deliveryContext?.threadId ??
|
||||
sessionEntry.origin?.threadId,
|
||||
);
|
||||
|
||||
return changed ? next : sessionCtx;
|
||||
}
|
||||
|
||||
export function buildExecOverridePromptHint(params: {
|
||||
execOverrides?: ExecOverrides;
|
||||
elevatedLevel: ElevatedLevel;
|
||||
@@ -278,15 +384,6 @@ export async function runPreparedReply(
|
||||
ctx,
|
||||
sessionKey,
|
||||
});
|
||||
const silentReplySettings = resolveSilentReplySettings({
|
||||
cfg,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
surface: sessionCtx.Surface ?? sessionCtx.Provider,
|
||||
conversationType: resolvePromptSilentReplyConversationType({
|
||||
ctx: sessionCtx,
|
||||
inboundSessionKey: ctx.SessionKey,
|
||||
}),
|
||||
});
|
||||
let {
|
||||
sessionEntry,
|
||||
resolvedThinkLevel,
|
||||
@@ -296,6 +393,22 @@ export async function runPreparedReply(
|
||||
execOverrides,
|
||||
abortedLastRun,
|
||||
} = params;
|
||||
const isHeartbeat = opts?.isHeartbeat === true;
|
||||
const promptSessionCtx = resolvePromptSessionContextForSystemEvent({
|
||||
sessionCtx,
|
||||
sessionEntry,
|
||||
ctx,
|
||||
isHeartbeat,
|
||||
});
|
||||
const silentReplySettings = resolveSilentReplySettings({
|
||||
cfg,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
surface: promptSessionCtx.Surface ?? promptSessionCtx.Provider,
|
||||
conversationType: resolvePromptSilentReplyConversationType({
|
||||
ctx: promptSessionCtx,
|
||||
inboundSessionKey: ctx.SessionKey,
|
||||
}),
|
||||
});
|
||||
const useFastReplyRuntime = shouldUseReplyFastTestRuntime({
|
||||
cfg,
|
||||
isFastTestEnv: process.env.OPENCLAW_TEST_FAST === "1",
|
||||
@@ -310,9 +423,9 @@ export async function runPreparedReply(
|
||||
let currentSystemSent = systemSent;
|
||||
|
||||
const isFirstTurnInSession = isNewSession || !currentSystemSent;
|
||||
const isGroupChat = sessionCtx.ChatType === "group" || sessionCtx.ChatType === "channel";
|
||||
const isGroupChat =
|
||||
promptSessionCtx.ChatType === "group" || promptSessionCtx.ChatType === "channel";
|
||||
const wasMentioned = ctx.WasMentioned === true;
|
||||
const isHeartbeat = opts?.isHeartbeat === true;
|
||||
const { typingPolicy, suppressTyping } = resolveRunTypingPolicy({
|
||||
requestedPolicy: opts?.typingPolicy,
|
||||
suppressTyping: opts?.suppressTyping === true,
|
||||
@@ -332,9 +445,9 @@ export async function runPreparedReply(
|
||||
isGroupChat && (isFirstTurnInSession || sessionEntry?.groupActivationNeedsSystemIntro),
|
||||
);
|
||||
const directChatContext =
|
||||
sessionCtx.ChatType === "direct" || sessionCtx.ChatType === "dm"
|
||||
promptSessionCtx.ChatType === "direct" || promptSessionCtx.ChatType === "dm"
|
||||
? buildDirectChatContext({
|
||||
sessionCtx,
|
||||
sessionCtx: promptSessionCtx,
|
||||
silentReplyPolicy: silentReplySettings.policy,
|
||||
silentReplyRewrite: silentReplySettings.rewrite,
|
||||
silentToken: SILENT_REPLY_TOKEN,
|
||||
@@ -343,7 +456,7 @@ export async function runPreparedReply(
|
||||
// Always include persistent group chat context (provider + reply guidance).
|
||||
const groupChatContext = isGroupChat
|
||||
? buildGroupChatContext({
|
||||
sessionCtx,
|
||||
sessionCtx: promptSessionCtx,
|
||||
sourceReplyDeliveryMode: opts?.sourceReplyDeliveryMode,
|
||||
silentReplyPolicy: silentReplySettings.policy,
|
||||
silentReplyRewrite: silentReplySettings.rewrite,
|
||||
@@ -354,7 +467,7 @@ export async function runPreparedReply(
|
||||
const groupIntro = shouldInjectGroupIntro
|
||||
? buildGroupIntro({
|
||||
cfg,
|
||||
sessionCtx,
|
||||
sessionCtx: promptSessionCtx,
|
||||
sessionEntry,
|
||||
defaultActivation,
|
||||
silentToken: SILENT_REPLY_TOKEN,
|
||||
@@ -370,7 +483,7 @@ export async function runPreparedReply(
|
||||
silentReplyPolicy: silentReplySettings.policy,
|
||||
silentReplyRewrite: silentReplySettings.rewrite,
|
||||
}).allowEmptyAssistantReplyAsSilent;
|
||||
const groupSystemPrompt = normalizeOptionalString(sessionCtx.GroupSystemPrompt) ?? "";
|
||||
const groupSystemPrompt = normalizeOptionalString(promptSessionCtx.GroupSystemPrompt) ?? "";
|
||||
const inboundMetaPrompt = buildInboundMetaSystemPrompt(
|
||||
isNewSession ? sessionCtx : { ...sessionCtx, ThreadStarterBody: undefined },
|
||||
{ includeFormattingHints: !useFastReplyRuntime },
|
||||
|
||||
Reference in New Issue
Block a user