# 会话级系统提示词覆盖 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` 字段前插入: ```ts systemPrompt: text("system_prompt"), ``` 完整上下文(修改后 `agentSessions` 的字段部分): ```ts 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** ```bash 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,`。 修改后的完整函数: ```ts 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 { 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;`。 修改后的完整函数: ```ts 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 { const updates: Record = {}; 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** ```bash 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: ```ts 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`: ```ts 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 逻辑: ```ts 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** ```bash 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 行,将: ```ts const systemPrompt = agent.systemPrompt || undefined; ``` 改为: ```ts 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** ```bash 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` 后加: ```ts systemPrompt: string | null; ``` 修改后的完整接口: ```ts 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 同步该字段。 修改后的完整函数: ```ts 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** ```bash 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` 为可编辑弹窗** 完整替换文件内容: ```vue