OPENPROGRAM / 项目管理设计

项目移动,历史不跟着丢失

统一保存在用户目录,按稳定项目 ID 分组。下面直接看实际使用时会出现的界面;后台规则折叠在末尾。

当前实现 · 代码与自动化检查已完成;安装版 App 验收单独记录。

01

对话放在哪里

第一次打开项目

OpenProgram拟议界面
Projects
论文整理
/Projects/paper
开始新对话
项目选择器和管理页保留登记。

发起对话

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

再次打开

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

本机用户目录统一保存。每个项目有独立目录,每个会话保留自己的 Git 历史;工作文件夹里不再新增聊天记录。

02

文件夹移动或改名

移动前

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

文件管理器中移动

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

回到原来的对话

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

能确认是同一目录时自动更新位置。项目 ID、名称、会话和归档状态保持不变,聊天文件不搬动。不能确认时显示定位入口。

03

关闭 App 后再移动

关闭前

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

关闭期间移动

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

重新打开

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

启动或访问时,在限定范围内查找可移植项目标识。新位置在范围之外时,重新打开或定位一次。历史保持可读,不进行空闲轮询。

04

硬盘断开与重连

连接时

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Volumes/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

断开后

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Volumes/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

接回后

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Volumes/Research/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

有未归档对话的项目仍显示在侧边栏,历史仍可读。工作文件、依赖它们的附件和新任务暂不可用;重连并验证目录身份后恢复。用户目录本身也不可用时,本机历史同样不可读取。

05

复制目录,或跨硬盘移动

原项目

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

复制到另一处

OpenProgram拟议界面
Projects
论文整理
/Volumes/Other/paper
开始新对话
项目选择器和管理页保留登记。

明确关联新位置

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Volumes/Other/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

原位置仍存在时,打开副本会创建独立项目。复制后删除原目录的移动保留标识,可以重新关联原项目。发现多个副本时需要明确定位;仅有一个副本且原目录已删除时,与移动无法区分。

06

旧路径出现另一个文件夹

原项目

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

目录被替换

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

修复之前

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

路径字符串相同不等于同一个项目。历史保留,新任务暂停;定位原目录或明确确认替换。已登记给其他项目的位置不能被直接接管。

07

空项目与归档

没有对话

OpenProgram拟议界面
Projects
论文整理
/Projects/paper
开始新对话
项目选择器和管理页保留登记。

存在对话

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

全部归档

OpenProgram拟议界面
Projects
论文整理
/Projects/paper
归档对话 · 12
项目选择器和管理页保留登记。

侧边栏只显示有未归档对话的项目。选择器和管理页保留所有登记;管理页可以查看归档对话。归档不删除记录,彻底删除会话才删除它的存储。

08

已有数据怎么迁移

升级前

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

迁移时

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
帮我整理这篇论文的要点
已保存的对话与历史仍在这里。

旧硬盘不在

OpenProgram拟议界面
Projects
论文整理
帮我整理这篇论文的要点
论文整理
/Projects/paper
历史内容暂不可用
此处仅显示登记信息。

正文、Git 历史、分支、撤回备份和附件存储一起核对。迁移失败保留来源,重启可以继续;离线来源保留登记,不假装已有本地副本,不创建空历史替代。

试一下操作后的状态

只操作本页示意,不改变真实项目。

保存布局、移动规则、迁移与验收(展开)

采用什么,不照搬什么

采用三家的共同点:应用拥有会话存储,工作目录不是聊天保存位置。采用 Claude Code、DeepSeek Harness 的按项目分目录组织。项目目录使用稳定 ID,不用路径编码,避免改名后存储目录变化。保留 OpenProgram 已有 Git 会话格式、分支和撤回能力,不为统一布局改写成 JSONL。

Codex 的当前目录筛选比较记录中的 cwd,也检查最新 turn context。DeepSeek 的 workspace 关联验证会话 header.cwd 对应的真实目录,目录不存在或不匹配时拒绝。这些源码证明了路径参与筛选和关联,不能据此声称它们会自动追踪任意文件夹移动,也不能声称移动会删除其聊天。

进一步核实:Codex 的 Project 独立保存 id 和 roots,update_project 按 id 更新根目录,线程通过 project_id 关联。我们采用这一点。DeepSeek Workspace 同样有独立 ID,但当前 path 明确不可变;不照搬这一限制。

保存位置与身份

实际根目录由现有 profile/state 设置决定,默认 ~/.openprogram。沿用已登记项目 ID;新项目使用无路径含义的唯一 ID。会话 ID 全局唯一,项目名称仅用于显示。项目位置记录包含路径、位置版本和可移植目录标识。已有默认会话保持兼容。

~/.openprogram/
  projects/
    projects.json                    # registry / location / identity
    <project-id>/                    # existing project settings
  sessions/
    index.json                       # rebuildable listing
    locations.json                   # legacy compatibility
    projects/
      <project-id>/
        <session-id>/                # complete session Git repository
        .file-recovery/<session-id>/ # external rewind data
    <legacy-session-id>/             # existing default sessions
  memory/                            # existing memory store, unchanged

跨平台项目身份

工作文件夹保存 .openprogram/project.json,内容是带版本的随机身份标识。注册表将该标识关联到原有项目 ID。路径是可更新的位置数据;会话 Git 仓库保留在应用状态目录。文件夹改名、父目录移动以及复制后删除原目录的移动方式都保留该标识。

启动、请求项目列表、访问文件或执行任务时检查失效位置。检查已登记项目的父目录;父目录已移动时使用最近的仍存在的上级目录,最多向下三级、检查 4,000 个条目;排除用户主目录及其祖先、隐藏目录、依赖目录和目录符号链接。检查不完整或发现多个匹配目录时不自动重新关联。无论移动到哪里,重新打开该目录都会直接检查标识;原位置不再持有该身份时恢复原项目。移动到未知位置需要打开或定位一次。没有递归扫描主目录的后台任务,也没有系统专用事件监听。

原位置仍持有该标识时,打开复制品会创建独立项目,并只给复制品写入新标识。原位置已不存在且只找到一个候选时保留原项目,包括跨磁盘复制后删除原目录的移动。此跨平台模型无法区分这种移动和复制后删除原件。多个候选保留明确的定位入口。标识文件用于识别,不是授权凭据;新路径重新验证文件权限与执行授权。

普通元数据编辑和已采用可移植标识的位置检查不创建或覆盖标识文件。首次登记和明确定位会原子创建文件,拒绝符号链接元数据、损坏文件和其他已登记项目拥有的标识。旧记录在原路径且标准库设备号与 inode 完全一致时可执行一次升级;其他无法核实的旧记录需要定位一次。升级后不再使用设备号、inode 或原生书签维持身份。原项目 ID 与会话历史保留。

采用已检查的 Codex/OpenCode 的身份与路径分离,以及 GitHub Desktop 的明确定位入口,保留会话集中存储。系统专用书签和事件监听不作为识别依赖;Git 远端相同也不等于同一个工作目录。下方平台资料保留作对照参考。

验收覆盖公开登记入口后的改名与重启、父目录移动、复制后删除原目录、重新打开检查范围之外的新位置、同时存在的复制品、标识冲突、不完整查找、只读或符号链接元数据、目录失效及旧记录重新定位。检查项目和会话 ID、已存历史以及错误目录上的执行拒绝。项目识别不调用平台框架或系统事件 API。

执行与引用

聊天读取不依赖工作目录。工作目录不可用时禁止启动该项目的新任务,不能回退到 HOME、服务器 cwd 或会话 workdir。已启动的子进程不重定向、不自动重放;后续文件操作重新验证位置。独立 worktree 单独检查自己的目录,不随主项目盲目改写。

不全文替换历史消息和 checkpoint manifest 中的绝对路径。新增项目文件引用使用项目 ID 与相对路径;旧附件引用在原路径不可用时明确提示,不冒充本地副本。已有上传附件、会话内部资源和外部撤回备份必须逐项审计物理存储位置,纳入迁移清单。记忆正文与历史保留;彻底删除来源会话时移除可点击来源或标注已删除,不额外复制来源文档。归档来源仍可访问。

旧数据迁移与删除

迁移前先停止该会话的一切写入,包括模型任务、fork 子进程、jobs、checkpoint 和附件写入。仅持有 Python 锁或单 worker 锁不足以证明安全。首批迁移在执行服务启动前完成;离线旧数据重新出现后也必须先进入同一静止写入边界才能迁移,不在普通读取中偷偷搬运。

迁移日志记录来源、目标、会话和项目身份及完整清单。先复制到私有暂存目录,校验内容和来源未变化,持久化正文与外部恢复数据,再原子发布目标与权威位置;最后清理旧位置。映射失败不得继续删除来源。每一步可中断、重启后恢复;目标冲突不覆盖。删除操作与迁移互斥,以持久化删除记录防止恢复过程重新生成已删除会话。只剩一个权威可写位置。

索引可由新布局与兼容旧布局重建,不能把迁移中的暂不可达会话清为空壳。项目移动不再改聊天位置索引;只更新项目位置版本并在验证后通知界面。项目管理删除登记、归档会话、永久删除会话和删除工作文件夹必须保持不同操作,不连带删除用户工作文件。

实施顺序与验收

  1. 先完成目录事件与持久引用的临时文件夹实测,确认平台能力边界。
  2. 新增用户目录内项目会话布局及兼容读取、索引重建、正确删除;保留所有已有 ID 和 Git 数据。
  3. 完成有静止写入边界的可恢复迁移,验证复制、发布、清理失败及重启、并发删除。
  4. 使用统一的位置解析、身份检查、执行拒绝入口和可视状态,响应启动、访问失败、用户定位及重新打开文件夹。
  5. 独立规格审查通过后进行质量审查;运行相关与完整检查,再更新默认 App 并实际操作验证。

验收:移动后重启仍读到同一对话;外接盘断开仍读已迁移历史;新任务不在错误目录执行;同名副本和原路径复用不串项目;归档不影响存储;每一个迁移阶段失败后消息、分支、Git 提交与撤回备份完整;空闲期间没有目录遍历或定时定位;观察器关闭后无残留资源。

兼容与操作的精确定义

页面中“移动后聊天文件不搬动”和“离线可读”均指新建或迁移完成的会话;首次从项目内迁入用户目录会复制物理数据。位置检查由启动、项目列表请求、访问失败、用户定位和打开目录触发,空闲时不轮询。

已有路径派生 ID 原样保留,不重新计算。新版本注册新项目时生成 UUID;先按经过身份验证的规范路径查已有登记,再生成新 ID,冲突时重新生成,不合并会话。迁移前后的同一项目继续使用原 ID。

自动定位要求可移植标识匹配且发现的位置无歧义;手动定位允许用户在展示旧、新路径后明确选择替换,不要求系统专用文件身份一致。两者都在提交前重新检查位置版本、权限及目标占用;目标属于其他项目时均拒绝。对旧布局,定位必须把兼容 locations 映射纳入持久操作日志,未修复完成标为“位置正在更新”,不返回完全成功、不开放新任务;恢复成功后发布通知。新布局只更新项目位置。

迁移期间所有更新版本的会话写入入口(包括 CLI、worker、fork 子进程和撤回)共用用户目录下按会话 ID 定位的进程间锁,并在取得锁后重新读取存储版本与位置。停止接收新任务,等待已登记写入任务结束;超时则延期,不强制杀死任务或继续复制。复制与切换持有独占迁移权。升级前旧版本进程必须通过现有生命周期管理停止;不支持旧、新版本同时写同一 profile。不得用“进程内无任务”替代这个条件。

主项目工作路径以项目位置记录为准,不信任会话中旧 work_dir。属于主项目内部的已登记额外目录与 source_folders,以确认过的旧根和相对路径派生新位置,再验证身份与权限;外部额外目录保留绝对路径,独立检查,不随主项目替换。无法访问的必需目录阻止相应文件操作并显示修复入口。历史消息、历史授权快照和撤回 manifest 不改写;移动后撤回如仍指向旧位置,必须明确提示不可用,不直接在旧路径创建或覆盖文件。新的位置需要重新验证执行权限,不能继承另一目录的授权。

侧边栏判定来自当前会话集合中过滤 archived 后的有效会话;管理页的总数包含归档,另显示归档数。不能拿总 session_count 作为侧边栏非空依据。归档/恢复/删除后统一刷新该派生集合。注册记录移除或隐藏不改变工作文件。

实现状态

代码入口为 store/project/identity.py、discovery.py、location.py 和 agent/internals/_workdir.py 中的执行检查。公开入口验收位于 tests/component/store/test_project_portable_identity.py,并配合项目、文件、迁移和执行回归测试。发布验收要求独立规格与质量审查、完整的受影响检查清单和默认 App 刷新。审查报告与发布日志注明经过验证的确切版本;本设计不会仅凭源码状态宣称安装版 App 已验收。

源码与平台参考

Claude Code 官方原生分发包已检查内嵌 JavaScript:默认 ~/.claude/projects/路径编码/会话ID.jsonl。本项证据来自本地分发包检查,不声称公开了原始 TypeScript 源码。