助理工作区让用户指定一个目录存放 AI 人格/记忆文件(soul.md、user.md、memory.md、claude.md),在助理项目会话中自动注入系统提示词。附带对话式引导问卷(13 题)和每日轻量问询(3 题)。V2 新增索引/检索/分类/组织能力。
workspace/
├── claude.md # 执行规则
├── soul.md # 人格风格
├── user.md # 用户画像
├── memory.md # 长期记忆(append-only,去重保护)
├── README.ai.md # 自动生成的目录概览
├── PATH.ai.md # 自动生成的路径索引
├── Inbox/ # 默认捕获目录
├── memory/
│ └── daily/ # 每日记忆(episodic,按日期命名)
│ ├── 2026-03-05.md
│ └── archive.md # 过期日记的摘要归档
└── .assistant/
├── state.json # 状态持久化(schemaVersion: 2)
├── config.json # 工作区配置(组织方式、归档策略、索引参数)
├── taxonomy.json # 分类体系(目录角色推断 + 演化建议)
└── index/
├── manifest.jsonl # 文件级元数据索引
├── chunks.jsonl # Markdown 分块索引
└── hotset.json # 高频访问文件(pinned + frequent)
| 函数 | 职责 |
|---|---|
validateWorkspace(dir) |
检查目录和文件状态 |
initializeWorkspace(dir) |
补建缺失模板 + state.json + V2 目录 |
loadWorkspaceFiles(dir) |
读核心文件 + 每日记忆 + 根文档 |
assembleWorkspacePrompt(files, retrievalResults?) |
XML 标签拼接,按预算分配(总上限 40000 chars) |
loadState(dir) / saveState(dir, state) |
读写 .assistant/state.json |
needsDailyCheckIn(state) |
判断是否需要每日问询 |
generateDirectoryDocs(dir) |
扫描子目录生成 README.ai.md + PATH.ai.md |
generateRootDocs(dir) |
生成根级 README.ai.md + PATH.ai.md |
ensureDailyDir(dir) |
确保 memory/daily/ 目录存在 |
writeDailyMemory(dir, date, content) |
写每日记忆文件 |
loadDailyMemories(dir, count) |
加载最近 N 天的每日记忆 |
migrateStateV1ToV2(dir) |
V1→V2 自动迁移 |
工作区配置管理。loadConfig() 读取 .assistant/config.json,与 DEFAULT_CONFIG 深合并。配置项包括:
organizationStyle:project / time / topic / mixedcaptureDefault:新信息默认存放目录archivePolicy:归档天数、是否自动归档ignore:索引忽略的 glob 模式index:分块大小、最大深度、文件大小上限
分类体系推断。从目录名映射已知角色(notes/project/journal/archive/inbox 等),按 confidence 评分。classifyPath() 按最长路径前缀匹配分类。
Markdown 索引引擎。
| 函数 | 职责 |
|---|---|
indexWorkspace(dir, { force? }) |
增量索引:比较 mtime 跳过未变文件,仅重索引 stale 文件 |
indexFile(dir, filePath) |
单文件索引:frontmatter 解析 + 分块 + 分类 |
chunkMarkdown(content, size, overlap) |
按 heading 切 section,大 section 滑窗分块 |
extractMarkdownMeta(content) |
提取 title/tags/headings/aliases |
loadManifest(dir) / loadChunks(dir) |
读取 JSONL 索引文件 |
getIndexStats(dir) |
统计文件数/分块数/stale 数 |
增量索引机制:每次聊天开始时自动调用(在检索之前),对未变文件复用已有 manifest/chunks 条目,仅写入有变化时才更新文件。UI "重新索引"按钮走 force: true 全量模式。
关键词检索引擎。
| 函数 | 职责 |
|---|---|
parseQuery(query) |
分词:英文按空白/标点 + 停用词过滤;CJK 文本按 bigram + unigram |
scoreManifest(entry, keywords) |
标题 10 / 标签 8 / 别名 7 / 标题 5 / 路径 3 |
scoreChunk(chunk, keywords) |
正文出现次数 + heading 2x |
searchWorkspace(dir, query, { limit }) |
综合评分 + hotset 加权(pinned +5,frequent +log2(count)) |
loadHotset(dir) / updateHotset(dir, paths) |
高频文件管理 |
CJK 支持:parseQuery() 检测 CJK 字符范围(U+4E00–U+9FFF 等),CJK 连续 run 生成 unigram + bigram,英文 run 按空白切分 + 停用词过滤。确保中文查询能命中标题/标签/正文中的中文内容。
| 函数 | 职责 |
|---|---|
assertContained(dir, relativePath) |
路径边界校验:拒绝绝对路径、~、../ 穿越、符号链接逃逸 |
captureNote(dir, title, content) |
将笔记写入默认捕获目录(经 assertContained 校验) |
classifyAndSuggest(dir, filePath) |
基于 taxonomy 建议分类和目标路径 |
moveFile(dir, from, to) |
移动文件(经 assertContained 校验) |
archiveDailyMemories(dir) |
超过 retention 天数的日记摘要归档 + 删除原文件 |
promoteDailyToLongTerm(dir, date) |
提取 "Candidate Long-Term Memory" 追加到 memory.md(幂等) |
suggestTaxonomyEvolution(dir) |
分析文件分布,建议新建/归档分类 |
memory.md 去重保护:
promoteDailyToLongTerm()检查日记文件的<!-- promoted -->标记 + memory.md 的## Promoted from {date}标题,双重防重复- check-in 路由的 inline promotion 也检查 memory.md 中是否已存在同日标题
仅当 session.working_directory === getSetting('assistant_workspace_path') 时注入:
- 增量索引(在检索前执行,确保当前轮拿到最新内容)
- 检索:对用户消息调用
searchWorkspace()取相关上下文 + 更新 hotset - 工作区文件内容(
<assistant-workspace>XML,含核心文件 + 每日记忆 + 检索结果) - 引导/问询指令(
<assistant-project-task>)
普通项目聊天完全不受影响。
- Mount 触发:
ChatViewmount 时 500ms 延迟检测,使用startStream()直接发送(非sendMessage) - Focus 兜底:
MessageInputonFocus 首次触发,防 mount 未生效 - 防重复:
assistantTriggerFiredRef(组件级 ref)防止同一 mount 周期内重复触发;state.hookTriggeredSessionId按 session 粒度防止跨页面重复(但若 session 无消息则视为上次失败,允许重试) - autoTrigger 标志:触发时携带
autoTrigger: true,后端跳过保存用户消息和标题更新,实现"AI 先说话"体验 - hookTriggeredSessionId 清理:引导/问询完成后前端调用
POST /api/workspace/hook-triggered发送{ sessionId: '__clear__' }清除标记,确保下次进入可再次触发 - 最新会话校验:每日问询仅在该工作区最新会话中触发,旧会话打开不会劫持问询(通过
GET /api/workspace/latest-session校验)
AI 在对话中输出 onboarding-complete / checkin-complete 代码块 → 前端 detectAssistantCompletion() 解析 → 调 POST 对应端点 → 后端 AI 生成文件内容(失败回退原始答案)→ 清除 hookTriggeredSessionId。
安全绑定:detectAssistantCompletion() 在处理 fence 前先校验 workingDirectory === assistant_workspace_path,确保普通项目聊天或旧助理会话即使输出同样的 fence 也不会写入工作区文件。前端同时把 sessionId 传给后端,后端再次校验 session.working_directory === workspacePath(双重校验)。
注意:onboarding-complete / checkin-complete 是自定义 fence 语言标记,不是真正的编程语言。Shiki / Streamdown 渲染时会降级为纯文本(已通过 supportsLanguage() 前置检查静默处理)。
- 用户回答 3 个问题 → 前端解析
checkin-completeJSON - 后端并行调 AI 生成:每日记忆条目 + 长期记忆提升候选 + user.md 更新
- 每日记忆写入
memory/daily/{date}.md(episodic,不破坏长期记忆) - 长期记忆提升:仅追加新的稳定事实到 memory.md(日期去重)
- 自动归档:过期日记摘要 →
archive.md,7 天前的日记尝试 promote(幂等)
| 路由 | 方法 | 职责 |
|---|---|---|
/api/settings/workspace |
GET | 返回路径 + 文件状态 + state + taxonomy;路径无效时返回 valid: false + reason(path_not_found / not_a_directory / not_readable / not_writable) |
/api/settings/workspace |
PUT | 原子保存路径:先执行 initialize / resetOnboarding 副作用,全部成功后才 setSetting();支持 { path, initialize?, resetOnboarding? } |
/api/workspace/inspect |
GET | 切换前预检:?path=xxx → 返回 exists / isDirectory / readable / writable / hasAssistantData / workspaceStatus(empty / normal_directory / existing_workspace / partial_workspace / invalid)+ summary |
/api/workspace/session |
POST | 创建或复用助理会话(onboarding 新建,checkin 复用);创建前校验路径存在性 + R_OK + W_OK |
/api/workspace/onboarding |
POST | 接收 13 题答案 + sessionId → AI 生成 soul.md + user.md + claude.md + memory.md + config.json + taxonomy.json;校验 session 归属 |
/api/workspace/checkin |
POST | 接收答案 + sessionId → 写每日记忆 + 增量更新 memory.md + user.md;校验 session 归属 |
/api/workspace/hook-triggered |
POST | 设置/清除 hookTriggeredSessionId |
/api/workspace/latest-session |
GET | 返回指定 workingDirectory 的最新会话 ID |
/api/workspace/docs |
POST | 刷新根目录 + 子目录文档(generateRootDocs + generateDirectoryDocs) |
/api/workspace/index |
GET | 返回索引统计(文件数/分块数/stale 数) |
/api/workspace/index |
POST | 强制全量重建索引 |
/api/workspace/search |
GET | 关键词检索(?q=xxx&limit=5) |
/api/workspace/organize |
POST | 组织操作:capture / classify / move / archive / suggest-evolution;路径参数强制相对路径 + containment 校验 |
interface AssistantWorkspaceState {
onboardingComplete: boolean;
lastCheckInDate: string | null; // "YYYY-MM-DD"
schemaVersion: number; // 当前 2
hookTriggeredSessionId?: string; // 防重复触发
}切换助理工作区路径是一个多步流程,确保数据安全和用户确认:
- 输入框 debounce 500ms 后触发路径格式校验(绿 ✓ / 红 ✕ / 加载 spinner)
- 不存在的路径视为"可创建",不阻断保存
点击保存时,前端先调 GET /api/workspace/inspect?path=xxx,获取目标路径的 workspaceStatus:
| 状态 | 含义 | 确认弹窗行为 |
|---|---|---|
not_found |
路径不存在 | 提示"将创建目录并初始化"→ executeSave(initialize=true) |
empty |
空目录 | 提示"将初始化为工作区"→ executeSave(initialize=true) |
normal_directory |
非空普通目录 | 提示"将在此初始化工作区"→ executeSave(initialize=true) |
existing_workspace |
已有完整工作区 | 提供"继续接管"(reuse 模式)或"重新引导"(reset onboarding) |
partial_workspace |
有 .assistant 但缺文件 | 提供"继续接管"或"重新初始化" |
PUT /api/settings/workspace 先执行所有副作用(initializeWorkspace() / saveState(onboardingComplete: false)),全部成功后才调 setSetting() 持久化路径。任一步骤失败则路径设置不变。
保存成功后:
- 前端根据
navigateMode创建会话('new'→ onboarding 模式新建,'reuse'→ checkin 模式复用最新会话) - 派发
assistant-workspace-switched自定义事件 - 自动跳转到新会话
ChatView 监听 assistant-workspace-switched 事件,并检测当前会话的 workingDirectory 是否与 assistant_workspace_path 一致。不一致时显示黄色横幅,提供"打开新助理项目"按钮。
GET settings 返回 valid: false + reason 时,设置页面隐藏引导/问询/tab 面板,显示红色警告横幅。用户需修改路径后才能继续。
底部 tab 面板(仅当 workspace.valid !== false 时显示):
| Tab | 内容 |
|---|---|
| 文件状态 | 4 个核心文件存在/大小 + 刷新目录文档按钮 |
| 分类体系 | 从 taxonomy.json 读取的分类列表 |
| 文件索引 | 索引统计(文件数/分块数/过期数/上次索引时间)+ 重新索引按钮 |
| 组织管理 | 归档旧记忆按钮 |
- 引导与问询互斥:onboarding 完成当天不触发 daily check-in
- 每日问询复用最后一条会话,不新建
hookTriggeredSessionId按 session 粒度防重复,换 session 可重新触发;完成后自动清除;若 session 无消息(上次触发失败)则允许重试autoTrigger贯穿 ChatView → stream-session-manager → chat/route.ts,跳过用户消息保存和标题更新- 每日问询不受
messages.length > 0限制,可在有历史消息的复用会话中触发 - 文件截断策略:每文件 8000 chars(head 6000 + truncated marker + tail 1800)
- 索引在检索前执行:确保当前轮对话能看到最新文件内容
- 增量索引:聊天入口的索引调用仅处理 stale 文件,大文库不会拖慢首轮响应
- memory.md 不可被覆写:所有写入都是 append,promotion 有日期去重保护
- hotset 影响排序:pinned 文件 +5 分,frequent 文件 +log2(count) 分(上限 +4)
- 路径校验 R_OK + W_OK:GET settings、session 创建均要求读写权限,只读目录返回
not_writable错误 - 切换原子性:PUT settings 先执行副作用再持久化,失败不留脏状态
- 切换横幅:
ChatView通过assistant-workspace-switched事件 + workingDirectory 比对检测不匹配;仅对旧助理会话显示(普通项目聊天不受影响);事件监听器匹配oldPath,挂载时通过 inspect 检查hasAssistantData - Completion fence 安全绑定:前端校验
workingDirectory === assistant_workspace_path+ 后端校验session.working_directory === workspacePath,双重防止非助理会话写入工作区 - Organize 路径边界:
assertContained()拒绝绝对路径 /~/..// 符号链接逃逸;API 入口validateRelativePath()预检;captureDefault写入 config 时强制相对路径 - 每日问询入口:设置页中仅当
onboardingComplete === true时显示每日问询卡片 - 刷新文档:
/api/workspace/docs同时调用generateRootDocs()+generateDirectoryDocs()
| 维度 | V1 | V2 |
|---|---|---|
| Schema | schemaVersion: 1 |
schemaVersion: 2,自动迁移 |
| 引导问卷 | 10 题 | 13 题(新增组织方式、默认收件箱、归档策略) |
| 引导产物 | soul.md + user.md | + claude.md + memory.md + config.json + taxonomy.json |
| 记忆模型 | 每日 check-in 覆写 memory.md | 每日记忆写入 memory/daily/ + memory.md 仅追加 |
| 索引 | 无 | Markdown 分块索引(manifest + chunks JSONL) |
| 检索 | 无 | 关键词检索 + CJK bigram + hotset 加权 |
| 分类 | 无 | 目录角色推断 + 演化建议 |
| 组织 | 无 | 捕获/分类/移动/归档/长期记忆晋升 |
| 设置 UI | 文件状态 | + 分类体系 / 文件索引 / 组织管理 tab |