Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
6.8 KiB
用户隔离与会话存储
Agent 支持个人会话和项目会话。会话始终属于登录用户,项目归属可选;同一项目中的其他用户不会因此获得聊天记录或计算文件的访问权。
身份与范围
| 范围 | 请求凭据 | Server 验证 | 工作空间 |
|---|---|---|---|
| 个人会话 | Bearer | /api/v1/access-context 验证用户 |
用户目录中的 personal/ |
| 项目会话 | Bearer、X-Project-Id |
/api/v1/agent-auth-context 验证用户与项目权限 |
用户目录中的项目键目录 |
无项目头不等于匿名请求。生产环境始终验证 Bearer,验证失败不回退为本地用户。开发环境的无凭据请求使用同一个本地身份,不能用于多用户部署。
个人会话不取得项目业务凭据,调用依赖这些凭据的 TJWater API 会返回 PROJECT_CONTEXT_REQUIRED。任务执行和任务协作仍要求项目权限。个人记忆范围的键包含用户身份,避免所有个人会话共用一个记忆桶。
前端面板的“对话归属”支持个人会话和当前项目。切换会开启新对话,保留旧会话及其原归属,历史缓存按认证来源和范围区分。当前没有把已有会话搬入或移出项目的接口。
文件结构
根目录由 CONVERSATION_WORKSPACE_ROOT 管理,默认是 data/session-workspaces/:
actor-<用户键>/
├── personal/
│ └── session-<会话 ID 的 SHA-256>/
└── project-<项目键>/
└── session-<会话 ID 的 SHA-256>/
├── chat/
│ ├── owner.json
│ ├── messages.json
│ ├── transcript.json
│ └── learning.json
└── workspace/
├── files/
├── tmp/
└── .cache/
个人和项目会话具有相同的内部结构。用户和项目键来自经过验证的身份,不从工具参数或浏览器路径直接取值。会话 ID 使用完整 SHA-256 形成目录名,原 ID 保存在 chat/owner.json 中。
messages.json 保留前端聊天状态,包括消息、工具展示和执行计划;transcript.json 是经过持久化策略处理的问答摘要,供历史搜索和学习使用,最多保留配置的轮数。摘要不是完整原生执行轨迹。
learning.json 保存该会话的学习检查、复盘进度。它与聊天记录共用归属校验和串行写入保护,位于宿主管理的 chat/,不向模型开放。
data/session-metadata/ 是归属、标题、父会话、用途和任务身份的索引。它为会话 ID 冲突检查、列表及所有会话接口提供同一归属来源。新会话创建也会初始化对应的聊天目录。
历史索引在服务进程内按用户和范围缓存,创建、更新和删除同步维护缓存。同一数据根目录由一个 Agent 进程写入;修改底层索引文件后需要重启,当前不支持多个 Agent 进程并发写入同一目录。
隔离边界
| 对象 | 当前边界 |
|---|---|
| 会话接口 | 先校验用户、个人或项目范围,再读取状态;无权访问统一返回未找到 |
| 聊天目录 | 校验归属文件和规范路径;拒绝目录、记录文件中的符号链接 |
| 聊天搜索 | 只扫描当前用户与范围的摘要目录 |
| 原生文件工具 | 只允许当前原生会话的 workspace/;会话哈希不匹配时拒绝读取 |
| Shell | 在 workspace/files/ 运行,Landlock/seccomp 限制文件、网络和进程访问 |
| 记忆 | 用户记忆按用户隔离;项目记忆在项目内共享;个人工作空间记忆按用户隔离 |
| Skill | 按部署实例共享,不能当作用户私人文件夹 |
| MCP | 连接配置与上游账号按实例管理,工具权限按会话应用;上游数据授权由 MCP 服务负责 |
| OpenCode 数据库 | 仍是实例共享的原生数据库,访问依赖服务密码和可信 Agent 路由 |
chat/ 是计算目录的同级目录,目录权限为 0700,记录文件为 0600。模型和 Shell 不能直接读写聊天记录;需要查询历史时使用受范围约束的 session_search。
这是产品用户隔离和会话沙箱,不是为每个用户启动一个 Linux 账号、容器或独立 OpenCode 实例。服务宿主账号和管理员仍可管理全部持久化数据。原生数据库默认位置为 ~/.local/share/opencode/opencode.db,工作空间不会取代它,部署仍需保留该数据卷。见 OpenCode 存储说明。
旧记录与保留
启动时依据会话索引迁移旧 UI 状态和学习进度,并依据摘要的用户、范围与会话身份迁移旧摘要。旧文件经逐字节备份核对后归入 PERSISTENCE_BACKUP_DIR/legacy-chat/actor-*/<范围>/session-<sha256>/chat/,文件名包含内容哈希;源文件只在备份核对成功后移除。重复迁移不会覆盖已存在的新记录。legacy-chat/migration.json 保存版本、来源目录时间戳与迁移结果,来源未变化时跳过重复导入。缺少归属索引的 UI 状态和学习进度保留在旧目录,不猜测归属。历史匿名记录保留原本的本地用户范围,不自动划归某个登录用户。
旧计算目录在该会话继续执行前迁入对应工作空间,使用原生 Session API 更新目录绑定。文件保留,目标冲突停止迁移,绑定更新失败可重试。聊天目录的迁移不需要模型推理。
迁移核对完成后,已迁入工作空间的旧记录备份可以清理,保留 legacy-chat/migration.json 作为完成记录。当前本机的 139 份迁移备份及三个空迁移目录已清理,现行聊天和学习记录仍在对应的 chat/ 中。使用工作空间存储时,初始化不再创建旧迁移目录。
归档会话保留记录。删除会话先写入索引删除状态和 chat/deleted.json,会话立即从列表、读取和搜索中隐藏;随后停止聊天与学习任务,移除该原生会话、消息、摘要、学习进度、计算目录及其旧记录和迁移备份。保留最小归属和删除标记,拒绝迟到写入、会话 ID 再使用以及旧数据重新导入。清理未完成时返回 503 SESSION_CLEANUP_PENDING,重复同一用户、范围的删除请求可继续清理。
审计日志、已经形成的用户或项目记忆、共享 Skills、Server 业务报告及部署级离线备份有各自的保留职责,不随会话删除。工作空间仍没有自动过期清理,不依据目录为空就删除历史目录。
标题生成、学习检查和复盘在成功、失败及取消后释放自己的原生辅助会话和计算目录;清理失败会记录日志。它们不作为用户聊天长期保留。
持久化默认 data/ 可同时保存 Agent 索引、工作空间和记忆;Skill 目录与 OpenCode 数据库仍需各自的数据卷。自定义工作空间根目录也需要单独挂载。