Files
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
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.
2026-10-19 18:46:58 +08:00

6.8 KiB
Raw Permalink Blame History

用户隔离与会话存储

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 数据库仍需各自的数据卷。自定义工作空间根目录也需要单独挂载。