feat: assistant workspace — persistent AI personality & memory via Markdown files (#165)
Add "Assistant Workspace" feature that lets users designate a directory (e.g. Obsidian vault) containing 4 Markdown files (soul.md, user.md, memory.md, claude.md) to define AI personality, user profile, long-term memory, and execution rules. These files are automatically injected into the system prompt for assistant project sessions. Core library (src/lib/assistant-workspace.ts): - Workspace validation, initialization with template files - File loading with 8000-char truncation, XML prompt assembly (40k limit) - State management via .assistant/state.json - Directory docs generation (README.ai.md + PATH.ai.md) - Case-insensitive file name fallback (soul.md → SOUL.md) System prompt injection (src/app/api/chat/route.ts): - Scoped to sessions where working_directory === workspace path - Workspace prompt prepended before session-level system prompt - Onboarding/check-in instructions injected as <assistant-project-task> - autoTrigger flag skips user message save and title update Chat-based onboarding (10 questions) & daily check-in (3 questions): - AI conducts conversation, outputs structured completion code blocks - Frontend detects onboarding-complete/checkin-complete blocks - Backend calls Claude API to generate file content from answers - Fallback to raw answers if API call fails Auto-trigger pipeline: - Mount trigger (500ms delay) + focus fallback in MessageInput - assistantTriggerFiredRef prevents duplicate triggers per mount - hookTriggeredSessionId in state.json prevents cross-reload duplicates - Daily check-in only triggers in the most recent workspace session - hookTriggeredSessionId cleared via __clear__ sentinel after completion API endpoints: - GET/PUT /api/settings/workspace — path CRUD + file status - POST /api/workspace/session — create (onboarding) or reuse (check-in) - POST /api/workspace/onboarding — AI-generate soul.md + user.md - POST /api/workspace/checkin — AI-update memory.md + user.md - POST /api/workspace/hook-triggered — set/clear trigger guard - GET /api/workspace/latest-session — latest session lookup - POST /api/workspace/docs — refresh directory documentation UI: - Settings "Assistant" tab with workspace path, file status, controls - Violet badge on workspace project group in ChatListPanel - useAssistantWorkspace hook for state queries Also includes: - 102 passing tests (16 new regression tests for workspace logic) - i18n keys for en.ts and zh.ts - Handover documentation and execution plan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f19290c8d
commit
19aba25c8f
24 files changed
+1812
-24
No files matched your search
@@ -0,0 +1,118 @@
|
||||
# 助理工作区(Assistant Workspace)
|
||||
|
||||
> 创建时间:2026-03-06
|
||||
> 最后更新:2026-03-06
|
||||
|
||||
## 状态
|
||||
|
||||
| Phase | 内容 | 状态 |
|
||||
|-------|------|------|
|
||||
| 1 | 类型定义 + 核心工作区库 | ✅ 已完成 |
|
||||
| 2 | 系统提示词注入 | ✅ 已完成 |
|
||||
| 3 | API 端点(workspace / onboarding / checkin / session / hook-triggered) | ✅ 已完成 |
|
||||
| 4 | i18n(~48 个翻译键) | ✅ 已完成 |
|
||||
| 5 | Settings UI — 助理标签页 | ✅ 已完成 |
|
||||
| 6 | 对话式引导 + 自动触发 + 兜底触发 | ✅ 已完成 |
|
||||
| 7 | 目录文档生成(README.ai.md + PATH.ai.md) | ✅ 已完成 |
|
||||
| 8 | 回归测试(16 case) | ✅ 已完成 |
|
||||
|
||||
## 功能概述
|
||||
|
||||
用户指定一个目录(如 Obsidian vault)作为助理工作区,存放 4 份 Markdown 文件定义 AI 人格和记忆:
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `claude.md` | 执行规则 |
|
||||
| `soul.md` | 人格风格 |
|
||||
| `user.md` | 用户画像 |
|
||||
| `memory.md` | 长期记忆 |
|
||||
|
||||
状态持久化在 `.assistant/state.json`(与目录一起可移植)。
|
||||
|
||||
## 架构决策
|
||||
|
||||
### 对话式引导(非 Dialog UI)
|
||||
|
||||
最初实现用 Dialog 弹窗完成引导问卷,后改为通过自然对话完成:
|
||||
- 点击"开始引导"→ 创建助理项目会话 → 自动发送触发消息 → AI 逐题提问
|
||||
- 输入框 focus 作为兜底触发器(防止 mount 时因各种原因未触发)
|
||||
- `hookTriggeredSessionId` 状态位防止同一会话重复触发
|
||||
|
||||
### 确定性落盘
|
||||
|
||||
AI 在对话中收集完答案后输出结构化完成信号(`onboarding-complete` / `checkin-complete` 代码块),前端检测到后调用后端 API 确定性写入文件,不依赖模型自行操作文件系统。
|
||||
|
||||
### Prompt 作用域隔离
|
||||
|
||||
工作区 prompt(soul/user/memory/claude.md)仅注入 `working_directory === assistant_workspace_path` 的会话,普通项目不受影响。
|
||||
|
||||
### 引导与问询互斥
|
||||
|
||||
- `needsDailyCheckIn()` 在 `onboardingComplete === false` 时返回 false
|
||||
- onboarding 完成时同时设置 `lastCheckInDate = today`,当天不再触发每日问询
|
||||
|
||||
### 每日问询复用会话
|
||||
|
||||
每日问询优先打开该工作区最后一条会话继续提问(`getLatestSessionByWorkingDirectory`),不新建会话。仅引导问卷新建会话。
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 新增文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/lib/assistant-workspace.ts` | 核心库:文件加载/截断/拼接/状态管理/目录文档生成 |
|
||||
| `src/app/api/settings/workspace/route.ts` | 工作区路径 CRUD |
|
||||
| `src/app/api/workspace/onboarding/route.ts` | 引导问卷 API |
|
||||
| `src/app/api/workspace/checkin/route.ts` | 每日问询 API |
|
||||
| `src/app/api/workspace/session/route.ts` | 会话创建/复用(onboarding 新建,checkin 复用) |
|
||||
| `src/app/api/workspace/hook-triggered/route.ts` | 防重复触发状态 API |
|
||||
| `src/app/api/workspace/docs/route.ts` | 目录文档生成 API |
|
||||
| `src/components/settings/AssistantWorkspaceSection.tsx` | 设置面板主组件 |
|
||||
| `src/hooks/useAssistantWorkspace.ts` | 工作区状态 Hook |
|
||||
| `src/__tests__/unit/assistant-workspace.test.ts` | 16 个回归测试 |
|
||||
|
||||
### 修改文件
|
||||
|
||||
| 文件 | 改动 |
|
||||
|------|------|
|
||||
| `src/types/index.ts` | `AssistantWorkspaceState`、`AssistantWorkspaceFiles` 接口,`SETTING_KEYS` |
|
||||
| `src/app/api/chat/route.ts` | 工作区 prompt 加载(限助理项目)+ 引导/问询指令注入 |
|
||||
| `src/lib/db.ts` | `getLatestSessionByWorkingDirectory()` |
|
||||
| `src/components/chat/ChatView.tsx` | 自动触发 + 完成信号检测 |
|
||||
| `src/components/chat/MessageInput.tsx` | focus 兜底触发 |
|
||||
| `src/components/settings/SettingsLayout.tsx` | 助理标签 |
|
||||
| `src/components/layout/ChatListPanel.tsx` | 助理项目徽标 |
|
||||
| `src/i18n/en.ts` / `src/i18n/zh.ts` | ~48 个翻译键 |
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
用户点击"开始引导"
|
||||
→ POST /api/workspace/session { mode: 'onboarding' }
|
||||
→ 创建新会话,跳转 /chat/{id}
|
||||
→ ChatView mount → checkAssistantTrigger()
|
||||
→ 检测 workingDirectory === workspacePath && !onboardingComplete
|
||||
→ POST /api/workspace/hook-triggered { sessionId }
|
||||
→ sendMessage("请开始助理引导设置。")
|
||||
→ chat/route.ts 检测 isAssistantProject + !state.onboardingComplete
|
||||
→ 注入 onboarding 系统提示
|
||||
→ AI 逐题提问,用户逐题回答
|
||||
→ AI 输出 ```onboarding-complete {...}```
|
||||
→ ChatView.detectAssistantCompletion() 解析 JSON
|
||||
→ POST /api/workspace/onboarding { answers }
|
||||
→ 后端 AI 生成 soul.md + user.md → 写入文件
|
||||
→ state.onboardingComplete = true, lastCheckInDate = today
|
||||
```
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
| 测试 | 验证行为 |
|
||||
|------|---------|
|
||||
| initializeWorkspace creates state.json | 初始化写入 state.json + 4 个模板文件 |
|
||||
| onboarding auto-trigger detection | 新工作区检测到需要引导 |
|
||||
| hookTriggeredSessionId prevents repeat | 同一会话不重复触发 |
|
||||
| daily check-in respects onboarding state | 未完成引导不触发问询;完成当天不触发 |
|
||||
| workspace prompt scoping | 有内容时生成 prompt,空目录返回空 |
|
||||
| session reuse for daily check-in | `getLatestSessionByWorkingDirectory` 正确查询 |
|
||||
| generateDirectoryDocs | 同时生成 README.ai.md 和 PATH.ai.md |
|
||||
@@ -10,3 +10,4 @@
|
||||
|------|------|
|
||||
| agent-tooling-todo-bridge.md | SDK → SSE → DB 事件流、TodoWrite 字段映射、去重策略 |
|
||||
| bridge-system.md | 多 IM 远程桥接系统架构(目录结构、数据流、设计决策) |
|
||||
| assistant-workspace.md | 助理工作区:人格/记忆文件、对话式引导、自动触发、确定性落盘 |
|
||||
@@ -0,0 +1,87 @@
|
||||
# 助理工作区 — 交接文档
|
||||
|
||||
## 概述
|
||||
|
||||
助理工作区让用户指定一个目录存放 AI 人格/记忆文件(soul.md、user.md、memory.md、claude.md),在助理项目会话中自动注入系统提示词。附带对话式引导问卷(10 题)和每日轻量问询(3 题)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
workspace/
|
||||
├── claude.md # 执行规则
|
||||
├── soul.md # 人格风格
|
||||
├── user.md # 用户画像
|
||||
├── memory.md # 长期记忆
|
||||
└── .assistant/
|
||||
└── state.json # 状态持久化
|
||||
```
|
||||
|
||||
## 核心模块
|
||||
|
||||
### `src/lib/assistant-workspace.ts`
|
||||
|
||||
| 函数 | 职责 |
|
||||
|------|------|
|
||||
| `validateWorkspace(dir)` | 检查目录和文件状态 |
|
||||
| `initializeWorkspace(dir)` | 补建缺失模板 + state.json |
|
||||
| `loadWorkspaceFiles(dir)` | 读 4 文件,每文件上限 8000 chars |
|
||||
| `assembleWorkspacePrompt(files)` | XML 标签拼接,总上限 40000 chars |
|
||||
| `loadState(dir)` / `saveState(dir, state)` | 读写 `.assistant/state.json` |
|
||||
| `needsDailyCheckIn(state)` | 判断是否需要每日问询(前置检查 onboarding 完成) |
|
||||
| `generateDirectoryDocs(dir)` | 扫描子目录生成 README.ai.md + PATH.ai.md |
|
||||
|
||||
文件兼容映射:lowercase canonical → uppercase fallback(如 `soul.md` → `Soul.md` → `SOUL.md`)。
|
||||
|
||||
### Prompt 注入(`src/app/api/chat/route.ts`)
|
||||
|
||||
仅当 `session.working_directory === getSetting('assistant_workspace_path')` 时注入:
|
||||
1. 工作区文件内容(`<assistant-workspace>` XML)
|
||||
2. 引导/问询指令(`<assistant-project-task>`)
|
||||
|
||||
普通项目聊天完全不受影响。
|
||||
|
||||
### 自动触发链路
|
||||
|
||||
1. **Mount 触发**:`ChatView` mount 时 500ms 延迟检测,使用 `startStream()` 直接发送(非 `sendMessage`)
|
||||
2. **Focus 兜底**:`MessageInput` onFocus 首次触发,防 mount 未生效
|
||||
3. **防重复**:`assistantTriggerFiredRef`(组件级 ref)防止同一 mount 周期内重复触发;`state.hookTriggeredSessionId` 按 session 粒度防止跨页面重复
|
||||
4. **autoTrigger 标志**:触发时携带 `autoTrigger: true`,后端跳过保存用户消息和标题更新,实现"AI 先说话"体验
|
||||
5. **hookTriggeredSessionId 清理**:引导/问询完成后前端调用 `POST /api/workspace/hook-triggered` 发送 `{ sessionId: '__clear__' }` 清除标记,确保下次进入可再次触发
|
||||
6. **最新会话校验**:每日问询仅在该工作区最新会话中触发,旧会话打开不会劫持问询(通过 `GET /api/workspace/latest-session` 校验)
|
||||
|
||||
### 确定性落盘
|
||||
|
||||
AI 在对话中输出 `onboarding-complete` / `checkin-complete` 代码块 → 前端 `detectAssistantCompletion()` 解析 → 调 POST `/api/workspace/onboarding` 或 `/api/workspace/checkin` → 后端 AI 生成文件内容(失败回退原始答案)→ 清除 `hookTriggeredSessionId`。
|
||||
|
||||
## API 端点
|
||||
|
||||
| 路由 | 方法 | 职责 |
|
||||
|------|------|------|
|
||||
| `/api/settings/workspace` | GET | 返回路径 + 文件状态 + state |
|
||||
| `/api/settings/workspace` | PUT | 保存路径,可选初始化 |
|
||||
| `/api/workspace/session` | POST | 创建或复用助理会话(onboarding 新建,checkin 复用) |
|
||||
| `/api/workspace/onboarding` | POST | 接收答案 → AI 生成 soul.md + user.md |
|
||||
| `/api/workspace/checkin` | POST | 接收答案 → AI 更新 memory.md + user.md |
|
||||
| `/api/workspace/hook-triggered` | POST | 设置/清除 hookTriggeredSessionId(`__clear__` 哨兵值清除) |
|
||||
| `/api/workspace/latest-session` | GET | 返回指定 workingDirectory 的最新会话 ID |
|
||||
| `/api/workspace/docs` | POST | 刷新目录文档 |
|
||||
|
||||
## 状态字段(`.assistant/state.json`)
|
||||
|
||||
```typescript
|
||||
interface AssistantWorkspaceState {
|
||||
onboardingComplete: boolean;
|
||||
lastCheckInDate: string | null; // "YYYY-MM-DD"
|
||||
schemaVersion: number; // 当前 1
|
||||
hookTriggeredSessionId?: string; // 防重复触发
|
||||
}
|
||||
```
|
||||
|
||||
## 关键约束
|
||||
|
||||
- 引导与问询互斥:onboarding 完成当天不触发 daily check-in
|
||||
- 每日问询复用最后一条会话,不新建
|
||||
- `hookTriggeredSessionId` 按 session 粒度防重复,换 session 可重新触发;完成后自动清除
|
||||
- `autoTrigger` 贯穿 ChatView → stream-session-manager → chat/route.ts,跳过用户消息保存和标题更新
|
||||
- 每日问询不受 `messages.length > 0` 限制,可在有历史消息的复用会话中触发
|
||||
- 文件截断策略:每文件 8000 chars(head 6000 + truncated marker + tail 1800)
|
||||
Reference in new issue
Block a user