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接受 bodysystemPrompt,PUT /api/agents/:slug/sessions/:id/config接受 bodysystemPrompt -
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 - ✅
createSessionservice 加参数 → Task 2 - ✅
updateSessionservice 加参数 → 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 调用中一致