You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

27 KiB

会话级系统提示词覆盖 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 让用户可以在每个会话中自定义系统提示词,覆盖 Agent 默认值。

Architecture: agentSessions 表新增 systemPrompt 字段(nullable text),executeChat 中优先使用会话级提示词,回退 Agent 默认。前端弹窗从只读改为可编辑,通过 PUT /config 接口保存。

Tech Stack: Nuxt 3, Drizzle ORM (SQLite), Vue 3 Composition API, Playwright E2E

Global Constraints

  • 数据库迁移命令:bun run db:generate && bun run db:migrate
  • E2E 测试命令:bun run test:e2e(必须传 --config=e2e/playwright.config.ts)
  • Mock LLM 端口 3600,固定回复 你好!这是一个测试回复。
  • Agent 默认 system_prompt(E2E seed):You are a helpful AI assistant.
  • 系统提示词业务限制:5000 字符
  • 前端 toast 提示:const { $toast } = useNuxtApp(); $toast?.error?.(...) / $toast?.success?.(...)
  • 用户名规则:/^[a-zA-Z0-9_]{3,20}$/
  • 固定验证码:FIXED_CAPTCHA(e2e/fixtures/test-users.ts)
  • E2E 测试用户:E2E_ADMIN(e2e/fixtures/test-users.ts)

File Structure

文件 操作 职责
packages/drizzle-pkg/lib/schema/agent.ts Modify agentSessions 表新增 systemPrompt 字段
server/service/agent/session.ts Modify createSession 和 updateSession 支持 systemPrompt
server/api/agents/[agentSlug]/sessions/index.post.ts Modify 创建会话接口读取 body 中 systemPrompt
server/api/agents/[agentSlug]/sessions/[id]/config.put.ts Modify 配置接口支持 systemPrompt 更新
server/service/agent/chat-engine.ts Modify 系统提示词优先级:session > agent
app/composables/useAgentSessions.ts Modify AgentSessionItem 加 systemPrompt,updateSessionConfig 加参数
app/components/agent/AgentSystemPromptModal.vue Modify 从只读改为可编辑+保存
app/components/agent/AgentToolbar.vue Modify 图标和 title 调整
app/pages/chat/[agentSlug]/index.vue Modify systemPromptText 改为 computed,弹窗接保存事件
e2e/specs/agent-chat.spec.ts Modify 新增会话级系统提示词 E2E 测试
e2e/global-setup.ts Modify Mock LLM 记录 system 消息供测试断言

Task 1: 数据库 Schema — agentSessions.systemPrompt 字段

Files:

  • Modify: packages/drizzle-pkg/lib/schema/agent.ts:57-82

Interfaces:

  • Produces: agentSessions.systemPrompt 字段(text("system_prompt"), nullable),后续 task 的 service 和 API 依赖此字段

  • Step 1: 修改 schema,在 agentSessions 表加 systemPrompt 字段

在 packages/drizzle-pkg/lib/schema/agent.ts 的 agentSessions 定义中,enableTools 字段后、lastActiveAt 字段前插入:

    systemPrompt: text("system_prompt"),

完整上下文(修改后 agentSessions 的字段部分):

export const agentSessions = sqliteTable(
  "agent_sessions",
  {
    id: text("id").primaryKey(),
    agentId: integer("agent_id").references(() => agents.id, { onDelete: "set null" }),
    userId: integer("user_id").references(() => users.id, { onDelete: "cascade" }),
    tempToken: text("temp_token", { length: 64 }),
    title: text("title", { length: 100 }).notNull().default("新对话"),
    modelId: integer("model_id"),
    enableThinking: integer("enable_thinking"),
    enableTools: integer("enable_tools"),
    systemPrompt: text("system_prompt"),
    lastActiveAt: integer("last_active_at", { mode: "timestamp_ms" }).defaultNow().notNull(),
    expiresAt: integer("expires_at", { mode: "timestamp_ms" }),
    deletedAt: integer("deleted_at", { mode: "timestamp_ms" }),
    createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(),
    updatedAt: integer("updated_at", { mode: "timestamp_ms" })
      .defaultNow()
      .$onUpdate(() => new Date())
      .notNull(),
  },
  • Step 2: 生成迁移文件

Run: bun run db:generate Expected: 在 packages/drizzle-pkg/drizzle/ 下生成新的 SQL 迁移文件,包含 ALTER TABLE agent_sessions ADD COLUMN system_prompt text;

  • Step 3: 执行迁移

Run: bun run db:migrate Expected: 迁移成功,无报错

  • Step 4: 验证字段存在

Run: sqlite3 packages/drizzle-pkg/db.sqlite ".schema agent_sessions" | grep system_prompt Expected: 输出 system_prompt text,

  • Step 5: Commit
git add packages/drizzle-pkg/lib/schema/agent.ts packages/drizzle-pkg/drizzle/
git commit -m "feat: add systemPrompt column to agentSessions table"

Task 2: Service 层 — createSession 和 updateSession 支持 systemPrompt

Files:

  • Modify: server/service/agent/session.ts:6-30(createSession)
  • Modify: server/service/agent/session.ts:102-122(updateSession)

Interfaces:

  • Consumes: Task 1 的 agentSessions.systemPrompt 字段

  • Produces: createSession 接受 systemPrompt?: string | null,updateSession 接受 systemPrompt?: string | null

  • Step 1: 修改 createSession 函数,加 systemPrompt 参数

在 server/service/agent/session.ts 的 createSession 函数中:

params 类型新增 systemPrompt?: string | null;,values 新增 systemPrompt: params.systemPrompt ?? null,。

修改后的完整函数:

export async function createSession(params: {
  userId?: number | null;
  tempToken?: string | null;
  agentId?: number | null;
  modelId?: number | null;
  enableThinking?: number | null;
  enableTools?: number | null;
  systemPrompt?: string | null;
  expiresAt?: Date | null;
}): Promise<AgentSessionRow> {
  const id = `as_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
  const [row] = await dbGlobal
    .insert(agentSessions)
    .values({
      id,
      userId: params.userId ?? null,
      tempToken: params.tempToken ?? null,
      agentId: params.agentId ?? null,
      modelId: params.modelId ?? null,
      enableThinking: params.enableThinking ?? null,
      enableTools: params.enableTools ?? null,
      systemPrompt: params.systemPrompt ?? null,
      expiresAt: params.expiresAt ?? null,
    })
    .returning();
  return row!;
}
  • Step 2: 修改 updateSession 函数,加 systemPrompt 支持

在 server/service/agent/session.ts 的 updateSession 函数中:

data 参数类型新增 systemPrompt?: string | null;,updates 逻辑新增 if (data.systemPrompt !== undefined) updates.systemPrompt = data.systemPrompt;。

修改后的完整函数:

export async function updateSession(
  id: string,
  data: {
    title?: string;
    agentId?: number | null;
    modelId?: number | null;
    enableThinking?: number | null;
    enableTools?: number | null;
    systemPrompt?: string | null;
    lastActiveAt?: Date;
  },
): Promise<void> {
  const updates: Record<string, any> = {};
  if (data.title !== undefined) updates.title = data.title;
  if (data.agentId !== undefined) updates.agentId = data.agentId;
  if (data.modelId !== undefined) updates.modelId = data.modelId;
  if (data.enableThinking !== undefined) updates.enableThinking = data.enableThinking;
  if (data.enableTools !== undefined) updates.enableTools = data.enableTools;
  if (data.systemPrompt !== undefined) updates.systemPrompt = data.systemPrompt;
  if (data.lastActiveAt !== undefined) updates.lastActiveAt = data.lastActiveAt;
  if (Object.keys(updates).length === 0) return;
  await dbGlobal.update(agentSessions).set(updates).where(eq(agentSessions.id, id));
}
  • Step 3: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | tail -20 Expected: 无新增类型错误(可能有既有的无关错误,确认无 session.ts 相关错误即可)

  • Step 4: Commit
git add server/service/agent/session.ts
git commit -m "feat: support systemPrompt in createSession and updateSession"

Task 3: API 层 — 创建会话和配置更新接口支持 systemPrompt

Files:

  • Modify: server/api/agents/[agentSlug]/sessions/index.post.ts:47-55
  • Modify: server/api/agents/[agentSlug]/sessions/[id]/config.put.ts:29-46

Interfaces:

  • Consumes: Task 2 的 createSession({ systemPrompt }) 和 updateSession({ systemPrompt })

  • Produces: POST /api/agents/:slug/sessions 接受 body systemPrompt,PUT /api/agents/:slug/sessions/:id/config 接受 body systemPrompt

  • Step 1: 修改创建会话接口,读取 body 中 systemPrompt

在 server/api/agents/[agentSlug]/sessions/index.post.ts 中:

在 const identity = ... 之后、createSession 调用之前,读取 body:

  const body = await readBody(event).catch(() => ({}));
  const systemPrompt: string | null =
    typeof body?.systemPrompt === "string" && body.systemPrompt.trim().length > 0
      ? body.systemPrompt.slice(0, 5000)
      : null;

然后修改 createSession 调用,加 systemPrompt:

  const session = await createSession({
    userId: identity.userId,
    tempToken,
    agentId: agent.id,
    modelId,
    enableThinking: agent.enableThinking,
    enableTools: agent.enableTools,
    systemPrompt,
    expiresAt,
  });
  • Step 2: 修改配置更新接口,支持 systemPrompt

在 server/api/agents/[agentSlug]/sessions/[id]/config.put.ts 中:

修改 body 解构和 updates 逻辑:

  const body = await readBody(event);
  const { modelId, enableThinking, enableTools, systemPrompt } = body as {
    modelId?: number;
    enableThinking?: number;
    enableTools?: number;
    systemPrompt?: string | null;
  };

  const updates: {
    modelId?: number | null;
    enableThinking?: number | null;
    enableTools?: number | null;
    systemPrompt?: string | null;
  } = {};
  if (modelId !== undefined) updates.modelId = modelId;
  if (enableThinking !== undefined) updates.enableThinking = enableThinking ? 1 : 0;
  if (enableTools !== undefined) updates.enableTools = enableTools ? 1 : 0;
  if (systemPrompt !== undefined) {
    if (systemPrompt !== null && typeof systemPrompt === "string" && systemPrompt.length > 5000) {
      return R.error("系统提示词不能超过5000字符", null);
    }
    updates.systemPrompt = systemPrompt;
  }
  • Step 3: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | grep -E "config\.put|index\.post" | head -5 Expected: 无输出(无相关类型错误)

  • Step 4: Commit
git add server/api/agents/\[agentSlug\]/sessions/index.post.ts server/api/agents/\[agentSlug\]/sessions/\[id\]/config.put.ts
git commit -m "feat: support systemPrompt in session create and config APIs"

Task 4: Chat Engine — 系统提示词优先级

Files:

  • Modify: server/service/agent/chat-engine.ts:251

Interfaces:

  • Consumes: Task 1 的 session.systemPrompt 字段

  • Produces: executeChat 中 systemPrompt 变量优先使用会话级

  • Step 1: 修改系统提示词优先级逻辑

在 server/service/agent/chat-engine.ts 第 251 行,将:

  const systemPrompt = agent.systemPrompt || undefined;

改为:

  const systemPrompt = session.systemPrompt?.trim() || agent.systemPrompt || undefined;
  • Step 2: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | grep "chat-engine" | head -5 Expected: 无输出

  • Step 3: Commit
git add server/service/agent/chat-engine.ts
git commit -m "feat: session-level systemPrompt takes priority over agent default"

Task 5: 前端 Composable — AgentSessionItem 和 updateSessionConfig

Files:

  • Modify: app/composables/useAgentSessions.ts:4-17(AgentSessionItem 接口)
  • Modify: app/composables/useAgentSessions.ts:111-128(updateSessionConfig)

Interfaces:

  • Consumes: Task 3 的 API 接口

  • Produces: AgentSessionItem.systemPrompt: string | null,updateSessionConfig 接受 systemPrompt?: string | null

  • Step 1: AgentSessionItem 接口加 systemPrompt 字段

在 app/composables/useAgentSessions.ts 的 AgentSessionItem 接口中,enableTools 后加:

  systemPrompt: string | null;

修改后的完整接口:

export interface AgentSessionItem {
  id: string;
  userId: number | null;
  tempToken: string | null;
  title: string;
  modelId: number | null;
  enableThinking: number;
  enableTools: number;
  systemPrompt: string | null;
  lastActiveAt: string;
  expiresAt: string | null;
  deletedAt: string | null;
  createdAt: string;
  updatedAt: string;
}
  • Step 2: updateSessionConfig 加 systemPrompt 参数

在 app/composables/useAgentSessions.ts 的 updateSessionConfig 函数中:

config 参数类型加 systemPrompt?: string | null,body 传该字段,本地 session 同步该字段。

修改后的完整函数:

  async function updateSessionConfig(
    id: string,
    config: { modelId?: number | null; enableThinking?: boolean; enableTools?: boolean; systemPrompt?: string | null },
  ) {
    try {
      await $fetch(`/api/agents/${agentSlug}/sessions/${id}/config`, {
        method: "PUT",
        body: config,
      });
      const s = sessions.value.find((x) => x.id === id);
      if (s) {
        if (config.modelId !== undefined) s.modelId = config.modelId;
        if (config.enableThinking !== undefined) s.enableThinking = config.enableThinking ? 1 : 0;
        if (config.enableTools !== undefined) s.enableTools = config.enableTools ? 1 : 0;
        if (config.systemPrompt !== undefined) s.systemPrompt = config.systemPrompt;
      }
    } catch {
    }
  }
  • Step 3: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | grep "useAgentSessions" | head -5 Expected: 无输出

  • Step 4: Commit
git add app/composables/useAgentSessions.ts
git commit -m "feat: add systemPrompt to AgentSessionItem and updateSessionConfig"

Task 6: 前端弹窗 — AgentSystemPromptModal 改为可编辑

Files:

  • Modify: app/components/agent/AgentSystemPromptModal.vue(完整重写 script 和 template)

Interfaces:

  • Consumes: Task 5 的 updateSessionConfig

  • Produces: 弹窗 emit save: [systemPrompt: string | null]

  • Step 1: 重写 AgentSystemPromptModal.vue 为可编辑弹窗

完整替换文件内容:

<script setup lang="ts">
const props = defineProps<{
  show: boolean;
  systemPrompt: string;
  isCustom: boolean;
}>();

const emit = defineEmits<{
  close: [];
  save: [systemPrompt: string | null];
}>();

const editingText = ref("");
const saving = ref(false);

watch(
  () => props.show,
  (val) => {
    if (val) {
      editingText.value = props.systemPrompt;
    }
  },
);

async function handleSave() {
  saving.value = true;
  try {
    const trimmed = editingText.value.trim();
    emit("save", trimmed.length > 0 ? trimmed.slice(0, 5000) : null);
  } finally {
    saving.value = false;
  }
}

function handleReset() {
  editingText.value = "";
  emit("save", null);
}
</script>

<template>
  <Teleport to="body">
    <div v-if="show" class="agent-prompt-overlay" @click="emit('close')">
      <div class="agent-prompt-modal" @click.stop>
        <div class="modal-header">
          <h3 class="modal-title">系统提示词</h3>
          <button class="modal-close" @click="emit('close')">
            <Icon name="lucide:x" />
          </button>
        </div>
        <div class="modal-body">
          <div v-if="isCustom" class="custom-badge">
            <Icon name="lucide:check-circle" />
            <span>当前使用会话自定义提示词</span>
          </div>
          <div v-else class="default-badge">
            <Icon name="lucide:info" />
            <span>当前使用 Agent 默认提示词,编辑后将覆盖默认值</span>
          </div>
          <textarea
            v-model="editingText"
            class="prompt-textarea"
            placeholder="输入系统提示词,留空则使用 Agent 默认"
            maxlength="5000"
          />
          <div class="char-count">{{ editingText.length }} / 5000</div>
        </div>
        <div class="modal-footer">
          <button class="btn-reset" :disabled="saving" @click="handleReset">
            重置为默认
          </button>
          <button class="btn-save" :disabled="saving" @click="handleSave">
            {{ saving ? "保存中..." : "保存" }}
          </button>
        </div>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.agent-prompt-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 19, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.agent-prompt-modal {
  width: 90%;
  max-width: 600px;
  max-height: 80vh;
  background: var(--color-surface-card);
  border: 1px solid var(--color-hairline);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--color-hairline);
}

.modal-title {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 400;
  color: var(--color-ink);
  margin: 0;
}

.modal-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  background: none;
  border-radius: 8px;
  cursor: pointer;
  color: var(--color-muted);
  font-size: 18px;
  transition: all 0.15s;
}

.modal-close:hover {
  background: var(--color-surface-soft);
  color: var(--color-ink);
}

.modal-body {
  padding: 20px;
  overflow-y: auto;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.custom-badge,
.default-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border-radius: 8px;
  font-size: 13px;
}

.custom-badge {
  background: var(--color-surface-soft);
  color: var(--color-primary);
}

.default-badge {
  background: var(--color-surface-soft);
  color: var(--color-muted);
}

.prompt-textarea {
  width: 100%;
  min-height: 200px;
  padding: 12px;
  font-family: "SF Mono", "Fira Code", monospace;
  font-size: 13px;
  line-height: 1.6;
  color: var(--color-body);
  background: var(--color-canvas);
  border: 1px solid var(--color-hairline);
  border-radius: 8px;
  resize: vertical;
  outline: none;
  transition: border-color 0.15s;
}

.prompt-textarea:focus {
  border-color: var(--color-primary);
}

.char-count {
  text-align: right;
  font-size: 12px;
  color: var(--color-muted);
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 20px;
  border-top: 1px solid var(--color-hairline);
}

.btn-reset,
.btn-save {
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.15s;
  border: 1px solid var(--color-hairline);
}

.btn-reset {
  background: var(--color-surface-soft);
  color: var(--color-muted);
}

.btn-reset:hover {
  border-color: var(--color-primary);
  color: var(--color-ink);
}

.btn-save {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}

.btn-save:hover {
  opacity: 0.9;
}

.btn-reset:disabled,
.btn-save:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
</style>
  • Step 2: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | grep "AgentSystemPromptModal" | head -5 Expected: 无输出

  • Step 3: Commit
git add app/components/agent/AgentSystemPromptModal.vue
git commit -m "feat: make AgentSystemPromptModal editable with save/reset"

Task 7: 前端工具栏和页面层 — 接入可编辑弹窗

Files:

  • Modify: app/components/agent/AgentToolbar.vue:53(图标和 title)
  • Modify: app/pages/chat/[agentSlug]/index.vue:22,144-153,279,293-297(systemPromptText 改 computed,弹窗接 save 事件)

Interfaces:

  • Consumes: Task 5 的 AgentSessionItem.systemPrompt,Task 6 的弹窗 save 事件

  • Produces: 完整的前端可编辑系统提示词功能

  • Step 1: 修改 AgentToolbar.vue 图标和 title

在 app/components/agent/AgentToolbar.vue 第 53 行,将:

      <button class="icon-btn" title="系统提示词" @click="emit('showPrompt')">
        <Icon name="lucide:info" />
      </button>

改为:

      <button class="icon-btn" title="系统提示词设置" @click="emit('showPrompt')">
        <Icon name="lucide:settings-2" />
      </button>
  • Step 2: 修改 index.vue — systemPromptText 改为 computed

在 app/pages/chat/[agentSlug]/index.vue 中:

删除 const systemPromptText = ref("");(第 22 行),改为 computed:

const agentSystemPrompt = ref("");
const systemPromptText = computed(() =>
  sessions.currentSession.value?.systemPrompt?.trim() || agentSystemPrompt.value || "",
);
const isCustomPrompt = computed(() =>
  !!(sessions.currentSession.value?.systemPrompt?.trim()),
);
  • Step 3: 修改 loadSystemPrompt 函数

将 loadSystemPrompt 中的 systemPromptText.value = ... 改为 agentSystemPrompt.value = ...:

async function loadSystemPrompt() {
  try {
    const res = await $fetch<{ code: number; data: { systemPrompt?: string } }>(
      `/api/agents/${agentSlug.value}/info`,
    );
    agentSystemPrompt.value = res.data?.systemPrompt ?? "";
  } catch {
    agentSystemPrompt.value = "";
  }
}
  • Step 4: 新增 handleSavePrompt 函数

在 index.vue 的 <script setup> 中加:

async function handleSavePrompt(prompt: string | null) {
  if (!sessions.currentSessionId.value) return;
  await sessions.updateSessionConfig(sessions.currentSessionId.value, { systemPrompt: prompt });
  const { $toast } = useNuxtApp();
  $toast?.success?.("系统提示词已更新");
  showPromptModal.value = false;
}
  • Step 5: 修改模板中弹窗的 props 和事件

在 index.vue 模板中,将 AgentSystemPromptModal 改为:

  <AgentSystemPromptModal
    :show="showPromptModal"
    :system-prompt="systemPromptText"
    :is-custom="isCustomPrompt"
    @close="showPromptModal = false"
    @save="handleSavePrompt"
  />
  • Step 6: 验证类型检查通过

Run: npx nuxi typecheck 2>&1 | grep -E "AgentToolbar|index\.vue" | head -10 Expected: 无输出

  • Step 7: Commit
git add app/components/agent/AgentToolbar.vue app/pages/chat/\[agentSlug\]/index.vue
git commit -m "feat: wire editable system prompt modal to session config"

Task 8: E2E 测试 — Mock LLM 记录 system 消息

Files:

  • Modify: e2e/global-setup.ts:49-84(Mock LLM handler 记录 system 消息)

Interfaces:

  • Produces: Mock LLM 将收到的 system 消息写入全局变量,供测试断言

  • Step 1: 修改 Mock LLM handler,记录 system 消息

在 e2e/global-setup.ts 中,Mock LLM handler 的 req.on("end", ...) 回调里,在 const model = ... 之后加:

          const systemMsg = Array.isArray(parsed.messages)
            ? parsed.messages.find((m: any) => m.role === "system")
            : undefined;
          if (systemMsg?.content) {
            (globalThis as any).__lastMockSystemPrompt = systemMsg.content;
          }
  • Step 2: 验证 global-setup 编译无误

Run: npx tsc --noEmit e2e/global-setup.ts 2>&1 | head -5 Expected: 无输出或仅有路径解析警告(不影响运行)

  • Step 3: Commit
git add e2e/global-setup.ts
git commit -m "test: mock LLM records system prompt for E2E assertions"

Task 9: E2E 测试 — 会话级系统提示词覆盖

Files:

  • Modify: e2e/specs/agent-chat.spec.ts(末尾新增测试块)

Interfaces:

  • Consumes: Task 8 的 globalThis.__lastMockSystemPrompt

  • Step 1: 在 agent-chat.spec.ts 末尾新增测试

在 e2e/specs/agent-chat.spec.ts 末尾追加:

test.describe("会话级系统提示词", () => {
  test("修改系统提示词后,发送消息使用自定义提示词", async ({ page }) => {
    await loginViaUI(page);

    // 打开系统提示词弹窗
    await page.locator('button[title="系统提示词设置"]').click();
    await expect(page.locator(".agent-prompt-modal")).toBeVisible({ timeout: 5000 });

    // 输入自定义提示词
    const customPrompt = "You are a pirate. Always respond in pirate speak.";
    await page.locator(".prompt-textarea").fill(customPrompt);
    await page.locator(".btn-save").click();
    await expect(page.locator(".agent-prompt-modal")).not.toBeVisible({ timeout: 5000 });

    // 发送消息
    await page.locator(".agent-textarea").fill("hello");
    await page.locator("button.btn-send").click();

    // 等待回复出现
    await expect(page.locator(".agent-message.is-assistant .agent-markdown")).toBeVisible({ timeout: 15000 });

    // 验证 Mock LLM 收到了自定义 system prompt
    const lastPrompt = (globalThis as any).__lastMockSystemPrompt;
    expect(lastPrompt).toBe(customPrompt);
  });

  test("重置系统提示词后,回退到 Agent 默认", async ({ page }) => {
    await loginViaUI(page);

    // 先设置自定义提示词
    await page.locator('button[title="系统提示词设置"]').click();
    await page.locator(".prompt-textarea").fill("Custom prompt to be reset");
    await page.locator(".btn-save").click();
    await expect(page.locator(".agent-prompt-modal")).not.toBeVisible({ timeout: 5000 });

    // 重置为默认
    await page.locator('button[title="系统提示词设置"]').click();
    await page.locator(".btn-reset").click();
    await expect(page.locator(".agent-prompt-modal")).not.toBeVisible({ timeout: 5000 });

    // 发送消息
    await page.locator(".agent-textarea").fill("hello");
    await page.locator("button.btn-send").click();
    await expect(page.locator(".agent-message.is-assistant .agent-markdown")).toBeVisible({ timeout: 15000 });

    // 验证回退到 Agent 默认
    const lastPrompt = (globalThis as any).__lastMockSystemPrompt;
    expect(lastPrompt).toBe("You are a helpful AI assistant.");
  });
});
  • Step 2: 运行 E2E 测试验证通过

Run: bun run test:e2e -- --grep "会话级系统提示词" Expected: 2 个测试通过

  • Step 3: 运行全部 E2E 测试确认无回归

Run: bun run test:e2e Expected: 所有测试通过(含原有 37 个 + 新增 2 个 = 39 个)

  • Step 4: Commit
git add e2e/specs/agent-chat.spec.ts
git commit -m "test: add E2E tests for session-level system prompt override"

Self-Review

Spec coverage

  • ✅ 数据库 agentSessions.systemPrompt 字段 → Task 1
  • ✅ createSession service 加参数 → Task 2
  • ✅ updateSession service 加参数 → Task 2
  • ✅ 创建会话接口支持传入 → Task 3
  • ✅ 配置接口支持更新 → Task 3
  • ✅ executeChat 优先级调整 → Task 4
  • ✅ AgentSessionItem 类型扩展 → Task 5
  • ✅ updateSessionConfig 扩展 → Task 5
  • ✅ 弹窗从只读改为可编辑 → Task 6
  • ✅ 工具栏图标调整 → Task 7
  • ✅ 页面层 systemPromptText 改 computed → Task 7
  • ✅ E2E 测试 → Task 8 + Task 9

Placeholder scan

  • ✅ 无 TODO/TBD/占位符
  • ✅ 所有代码步骤包含完整代码
  • ✅ 所有命令包含预期输出

Type consistency

  • ✅ systemPrompt: string | null 在 schema、service、API、composable 中一致
  • ✅ updateSessionConfig 的 config 参数在 Task 5 定义和 Task 7 调用中一致
  • ✅ 弹窗 save 事件签名 [systemPrompt: string | null] 在 Task 6 定义和 Task 7 调用中一致