




















@@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
66import type { OpenClawConfig } from "../../config/types.openclaw.js";
77import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
88import { __testing as cliBackendsTesting } from "../cli-backends.js";
9+import { hashCliSessionText } from "../cli-session.js";
910import { buildActiveMusicGenerationTaskPromptContextForSession } from "../music-generation-task-status.js";
1011import { buildActiveVideoGenerationTaskPromptContextForSession } from "../video-generation-task-status.js";
1112import {
@@ -52,11 +53,15 @@ const mockBuildActiveMusicGenerationTaskPromptContextForSession = vi.mocked(
5253buildActiveMusicGenerationTaskPromptContextForSession,
5354);
545555-function createCliBackendConfig(params: { systemPromptOverride?: string } = {}): OpenClawConfig {
56+function createCliBackendConfig(
57+params: { systemPromptOverride?: string | null } = {},
58+): OpenClawConfig {
5659return {
5760agents: {
5861defaults: {
59-systemPromptOverride: params.systemPromptOverride ?? "test system prompt",
62+ ...(params.systemPromptOverride !== null
63+ ? { systemPromptOverride: params.systemPromptOverride ?? "test system prompt" }
64+ : {}),
6065cliBackends: {
6166"test-cli": {
6267command: "test-cli",
@@ -308,7 +313,7 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
308313model: "test-model",
309314timeoutMs: 1_000,
310315runId: "run-test-legacy-merge",
311-config: createCliBackendConfig(),
316+config: createCliBackendConfig({ systemPromptOverride: null }),
312317});
313318314319expect(context.params.prompt).toBe("prompt prepend\n\nlegacy prepend\n\nlatest ask");
@@ -355,6 +360,63 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
355360}
356361});
357362363+it("uses explicit static prompt text for CLI session reuse hashing", async () => {
364+const { dir, sessionFile } = createSessionFile();
365+try {
366+const context = await prepareCliRunContext({
367+sessionId: "session-test",
368+ sessionFile,
369+workspaceDir: dir,
370+prompt: "latest ask",
371+provider: "test-cli",
372+model: "test-model",
373+timeoutMs: 1_000,
374+runId: "run-test-static-prompt",
375+extraSystemPrompt: "## Inbound Context\nchannel=telegram",
376+extraSystemPromptStatic: "",
377+cliSessionBinding: {
378+sessionId: "cli-session",
379+},
380+config: createCliBackendConfig({ systemPromptOverride: null }),
381+});
382+383+expect(context.systemPrompt).toContain("## Inbound Context\nchannel=telegram");
384+expect(context.extraSystemPromptHash).toBeUndefined();
385+expect(context.reusableCliSession).toEqual({ sessionId: "cli-session" });
386+} finally {
387+fs.rmSync(dir, { recursive: true, force: true });
388+}
389+});
390+391+it("ignores volatile prompt text when static prompt text matches", async () => {
392+const { dir, sessionFile } = createSessionFile();
393+try {
394+const staticPrompt = "## Direct Context\nYou are in a Telegram direct conversation.";
395+const context = await prepareCliRunContext({
396+sessionId: "session-test",
397+ sessionFile,
398+workspaceDir: dir,
399+prompt: "latest ask",
400+provider: "test-cli",
401+model: "test-model",
402+timeoutMs: 1_000,
403+runId: "run-test-volatile-prompt",
404+extraSystemPrompt: `## Inbound Context\nchannel=heartbeat\n\n${staticPrompt}`,
405+extraSystemPromptStatic: staticPrompt,
406+cliSessionBinding: {
407+sessionId: "cli-session",
408+extraSystemPromptHash: hashCliSessionText(staticPrompt),
409+},
410+config: createCliBackendConfig(),
411+});
412+413+expect(context.extraSystemPromptHash).toBe(hashCliSessionText(staticPrompt));
414+expect(context.reusableCliSession).toEqual({ sessionId: "cli-session" });
415+} finally {
416+fs.rmSync(dir, { recursive: true, force: true });
417+}
418+});
419+358420it("applies direct-run prepend system context helpers on the CLI path", async () => {
359421const { dir, sessionFile } = createSessionFile();
360422try {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。