Merge remote-tracking branch 'gitea/master'

This commit is contained in:
xiaohan.yuan committed 2026-10-21 20:27:56 +08:00
commit ce8bc58d5d
1 file changed
+328
@@ -0,0 +1,328 @@
# 项目、工作空间与后台活动实施方案 01
日期:2026-10-21。代码核对基线:`590d7ae2fafc2c6b378e316be2f3daab5a98831f`。
状态:**方案已落地,尚未实施;足以进入技术验证和分阶段开发,不代表 SDK 能力或产品验收已经通过。** 本文收敛已讨论的《YCode 项目与工作空间生命周期方案 v2》。不复用 B05 编号,不替代既有协作任务;后续实施编号由协作计划分配。
## 1. 已确认的产品规则
1. 一个任务保留同一段主会话,可以从主空间切到新 worktree,再回到主空间。用户不需要另外添加工作区、另开对话或手动结束 Run。
2. **当前绑定可以变,历史执行归属不能变。** 每次 Run 保存实际空间、环境、cwd、模型和权限快照;旧消息、工具记录和后台活动不会随当前绑定迁移。
3. worktree 是工作空间的来源类型,不是另一层导航。主工作副本与 linked worktree 对应空间在执行层面平级,不要求目录物理嵌套。
4. 侧栏只做项目折叠、任务平铺,不展开 Session、Run 和进程树。空间管理放在项目菜单或独立入口。
5. 用户明确要求“创建 worktree 并在里面继续”时,宿主负责创建、登记、切换;仅要求创建时不自动切换。
6. 只管理 YCode 启动并纳入管理的命令、子 agent 及其受管后代,不扫描、接管或结束外部命令。
7. 子 agent 与后台进程共用输入区域上方的独立活动条,用户和 agent 都能查询状态、查看日志、请求结束。
8. 切换不迁移、不自动结束旧活动;清理空间前,才必须核实活动退出、成果保全和其他引用。
9. 外部删除 worktree 经确认后,原会话自动回到已验证的主空间。离线、权限错误、超时不等于删除;主空间也不可用则暂停。
10. 自动回退不意味着自动重放工具、自动合并或认定交付完成。合并、删除工作副本、删除分支、任务归档是独立动作。
**切换闭环**解决后续调用确实进入新空间、旧活动可管、历史不漂移;**清理闭环**解决成果已处理、受管占用已结束、引用已处置,再安全删除。二者不能合成一个“完成”按钮而省略检查。
## 2. 概念与生命周期
| 实体 | 含义 | 关系与限制 |
|---|---|---|
| Project 项目 | 长期目标、资料、默认配置与任务集合 | 可关联多个空间,普通办公项目可以无 Git |
| Workspace 工作空间 | 某环境中的一份文件工作副本 | 稳定 ID、单根目录、单环境;根目录不是 OS 安全沙箱 |
| Environment 执行环境 | 机器、连接状态、运行时与能力 | 首期只登记本机;手机/网页未来可只是控制入口 |
| Task 任务 | 目标、验收与交付 | 首期一个任务一段主会话;完成回复不等于目标验收 |
| Session 会话 | 消息、工具记录与执行历史 | 当前绑定可变;切换仍打开原对话 |
| Run 执行 | 一次受控执行与配置快照 | 首期每个 Run 的空间不可变;允许同一会话透明续接 |
| Activity 后台活动 | 受管子 agent、进程或 shell 的生命周期 | 永远绑定启动时的 Run、环境和空间 |
```text
项目
├─ 关联空间:主空间 / worktree A / worktree B
└─ 任务(侧栏平铺)
└─ 主会话
├─ 当前绑定 → 一个空间,带修订号
├─ Run 1 → 主空间的不可变快照
├─ Run 2 → worktree A 的不可变快照
└─ 后台活动 → 各自启动时的空间,可跨越绑定切换继续运行
```
空间可以被多个项目引用,共享的是实际文件,不产生隔离;同一路径在不同环境中不是同一空间。空间创建者、当前引用者、控制活动的权限必须分别建模,创建者不因此取得删除其他会话占用空间的权利。
正常链路:讨论 → 创建并切换 worktree → 执行和管理后台活动 → 交付/验收 → 明确完成合并、PR 或补丁等交付 → 返回主空间 → 停止并核实旧活动 → 保全检查 → 清理副本 → 保留历史。验收退回沿原任务继续新 Run;Run 失败不回滚文件;任务归档不自动杀进程或删空间。普通聊天不强制走验收流。
## 3. 当前代码事实与待验证项
以下为基线源码/本地声明核对,不是新功能交付证明;路径均相对仓库根目录。
| 位置 | 当前事实 | 本次需要补齐 |
|---|---|---|
| `src/shared/workspaces.ts`、`src/shared/projects.ts` | 空间记录以 id/name/root 为中心,项目可关联空间 | 来源、环境、仓库身份、主空间关系与可用性 |
| `src/main/` 的项目、空间、会话存储及 `src/main/session-factory.ts` | 现有会话保存 projectId/workspaceId/cwd 等上下文,session-factory 拒绝项目与上下文空间不一致 | 可变当前绑定和不可变 Run 快照分离;迁移不可只取全局选中空间 |
| `src/main/git.ts` | 现有 Git 分支/HEAD 查询不是 worktree 生命周期管理器 | 受管创建、验证、清理与成果检查 |
| `src/main/agent/claude/claude-kernel.ts` | Bash/PowerShell 交给 SDK;tool_use/tool_result 形成工具记录 | 工具返回不等于后台进程退出;接入受管任务生命周期 |
| `src/shared/types.ts` 中 ToolCallRecord | running/ok/error 等描述工具调用 | 不能直接充当进程注册表 |
| `src/main/terminal-host.ts` | 内存 terminalsMap,PTY 输出、shell 退出、原始输入与 kill | shell 生命周期不覆盖每条命令或脱离 shell 的后代 |
| `src/renderer/src/components/ChatPane.tsx` | composeComposer 未传入任务活动,已有 floatingSurface/queuePanel | 独立 activitySlot,不挤入互斥授权或队列浮层 |
| `src/renderer/src/workbuddy/composer/input-composition.ts` | 有任务位置接口,尚未形成持久活动接线 | 复用位置思路而不是宣称已有完整活动栏 |
| `src/renderer/src/workbuddy/composer/ComposerControls.tsx` | 授权/浮层/队列布局,onStop 对应整个 Run | 单活动停止不能复用整轮取消 |
| `src/renderer/src/workbuddy/conversation/SpecializedBlocks.tsx` 的 TaskBlock | 消息内子 agent 工具记录 | 不是独立、持续可控的后台活动栏 |
本地安装的 Claude Agent SDK **0.3.304** 类型声明(`node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts`)包含 `task_started`、`task_progress`、`task_notification`、`background_tasks_changed` 及 Query 的 `stopTask`、`backgroundTasks`。参考声明区域约 6181–6288、3769、3273 行;版本变化应以实际类型名定位。
注意:Query 的 `backgroundTasks(toolUseId?)` 是将前台任务转为后台的操作,不是列表查询;任务列表观测来自 `background_tasks_changed` 等事件,不能因名称相似误调用。
这些声明只证明接口存在,**不证明**当前内核已消费它们,不证明所有 CLI 版本覆盖一致,也不证明任务快照跨 SDK 重启持久化。SDK taskId 是 SDK 任务身份,不是 OS PID,更不保证其全部后代退出。cwd 重绑定、会话 resume、工具边界续接、旧 controller 的后台任务可控性必须做真实 SDK 技术验证。
本方案没有执行原版 WorkBuddy 采集,也不形成新的原版行为结论。后续原版视觉/行为核验仍遵守 AGENTS.md 的 REA 要求。
## 4. 最小数据契约(拟议,非已有接口)
先使用现有本地存储风格和宿主服务,不预先建设远程调度平台。以下类型表达字段职责,落地时以实际序列化与项目类型约定调整,不把路径或类型草案冒充现有实现。
```ts
type ID = string;
type WorkspaceAvailability = 'available' | 'missing' | 'unavailable' | 'unknown';
interface WorkspaceBinding {
workspaceId: ID;
revision: number; // 每次成功变更递增,队列/工具校验使用
}
interface WorkspaceSpec {
id: ID;
environmentId: ID;
root: string;
source: 'directory' | 'git-primary' | 'git-worktree';
repositoryIdentity?: string; // 宿主核实,不能仅依赖 remote URL
primaryWorkspaceId?: ID;
creatorSessionId?: ID;
availability: WorkspaceAvailability;
}
interface SessionWorkspaceState {
sessionId: ID;
projectId: ID;
binding: WorkspaceBinding;
}
interface ExecutionSnapshot {
workspaceId: ID;
bindingRevision: number;
environmentId: ID;
cwd: string;
workspaceIdentity: string;
modelSnapshotRef: ID;
permissionSnapshotRef: ID;
}
interface RunWorkspaceRecord {
runId: ID;
sessionId: ID;
execution: Readonly<ExecutionSnapshot>;
continuationOfRunId?: ID;
switchOperationId?: ID;
}
interface QueuedWorkspaceRef {
queueItemId: ID;
executionTarget: WorkspaceBinding; // 入队捕获,不是发送时随意取最新空间
status: 'queued' | 'blocked-workspace-change' | 'dispatched' | 'cancelled';
// 权限快照沿用原规则,不因重新绑定空间而放宽
}
type ActivityState = 'starting' | 'running' | 'stopping' |
'exited' | 'stopped' | 'failed' | 'unknown';
interface ManagedActivity {
id: ID;
kind: 'agent' | 'process' | 'shell';
ownerSessionId: ID;
taskId: ID;
runId: ID;
execution: Readonly<ExecutionSnapshot>;
parentActivityId?: ID;
state: ActivityState;
readiness: 'not-applicable' | 'pending' | 'ready' | 'failed' | 'unknown';
trackingCoverage: 'owned-tree' | 'sdk-task-only' | 'shell-only' | 'unknown';
controllerId: ID;
sdkTaskId?: string;
sdkTaskRunId?: string; // SDK 提供的本次任务执行身份,区分 resume/复用
instanceGeneration: number; // controller/activity 实例代次
processIdentity?: { pid: number; startedAt: string; instanceId: string };
logRef?: ID;
updatedAt: string;
exit?: { code?: number; signal?: string; reason: string; observedAt: string };
}
interface WorkspaceOperation {
id: ID; // 幂等键;结果和恢复日志按此关联
sessionId: ID;
from: WorkspaceBinding;
targetWorkspaceId: ID;
stage: 'preparing' | 'prepared' | 'committed' | 'completed' |
'failed' | 'reconcile-required';
continuationRunId?: ID;
errorCode?: string;
}
```
`readiness` 与存活状态分开:running+pending 不能显示“已就绪”;已退出不能残留绿色 ready。退出需要可信证据。SDK 任务没有 OS 身份时不填造 PID;其 coverage 只声明 SDK 所能证明的范围。持久化 PID/时间仅辅助核验,不替代可靠句柄、控制器身份或运行时证明。
绑定的单一事实来源由宿主管理,会话/任务展示引用它,不各存一套会互相覆盖的 currentWorkspaceId。历史快照与活动执行快照只追加,不因重命名、切换或清理重写。日志不保存凭据或无关环境秘密。
## 5. 空间操作工具与返回契约
建议统一宿主服务,UI 和 agent 共用;工具名称是暂定职责,不是新增已发布 API。
| 拟议操作 | 主要输入 | 成功结果与限制 |
|---|---|---|
| `workspace.createWorktree` | sessionId、expectedRevision、基准引用、分支、目标路径、continueHere、operationId | 已验证空间 ID、创建事实;continueHere=true 才进入切换事务 |
| `workspace.switch` | sessionId、targetWorkspaceId、expectedRevision、operationId | 提交后绑定、续接 Run ID;不能只更新 UI |
| `workspace.returnToPrimary` | sessionId、expectedRevision、operationId | 核实主空间后走同一 switch;不猜测 cwd 或默认分支 |
| `workspace.inspectCleanup` | workspaceId | 阻塞项、成果/引用/活动检查摘要和短期检查版本;不是删除授权 |
| `workspace.cleanup` | workspaceId、检查版本、operationId、明确清理意图 | 删除副本的实际结果;执行前重新核实,不默认删分支 |
| `activity.list/status` | 当前会话范围、可选 activityId | 受授权活动、状态来源、观测时间与覆盖范围 |
| `activity.readLogs` | activityId、游标、大小上限 | 分页日志,不借路径参数读任意文件 |
| `activity.stop` | activityId、operationId | 停止已受理或终态;受理不等于已结束 |
工具由宿主重新校验会话、项目关联、环境能力、空间身份、路径冲突与权限。工具请求不能通过伪造 sessionId 或任意 PID 扩大范围;同一项目不自动获得其他会话活动的控制权。已明确授权管理的共享活动才可访问。
错误至少区分:`STALE_BINDING`、`TARGET_UNAVAILABLE`、`WORKSPACE_IDENTITY_CHANGED`、`FOREGROUND_BUSY`、`PENDING_APPROVAL`、`CONTROLLER_RETENTION_REQUIRED`、`TRACKING_UNKNOWN`、`UNRESOLVED_CHANGES`、`OTHER_REFERENCES`、`RECONCILE_REQUIRED`。错误带真实阶段、可恢复动作和已产生副作用,不以“操作失败”掩盖已创建目录,也不自动重试未知副作用。
重复 operationId 查询/返回原操作结果,不重复创建 worktree、发消息或停止无关活动。未完成记录先对账;不同参数复用同一 ID 应拒绝。
## 6. 切换状态机与 Run 边界
### 6.1 首期保守路径
**首期选用同一会话内透明续接新 Run,不假定 SDK 在现有 Run 内可靠改变 cwd。** SDK 技术验证若证明安全的执行片段方案,再单独评审;不把它作为首期交付的隐藏前提。
```text
请求切换
→ 锁定会话调度并核对 binding revision
→ 等待前台工具到安全边界 / 明确取消且确认终止
→ 核实目标、权限、旧 controller 可保留性
→ 准备新执行上下文与操作日志
→ 原子提交绑定和续接意图
→ 在原会话建立引用新快照的新 Run
→ 核实实际执行目录后放行后续工具
```
agent 调用切换工具时,该控制调用本身不能被纳入“等待所有前台工具结束”,否则会等待自己形成死锁。宿主先登记切换请求和阻止其他新派发;控制调用在旧上下文中恰好返回一次“已受理、待边界提交”,随后在可验证的工具边界执行提交与续接。受理不冒充切换成功,最终结果由 operationId 关联的宿主事件报告。旧 SDK 在控制工具响应入账前不得销毁;如何捕获这个边界属于阶段 A 的必验项。
用户无需点“结束 Run”。宿主负责边界、旧调用结果入账和透明续接;技术上的旧 Run 收敛不等于结束产品任务。不能假定所有 SDK 工具绝对串行:前台调用、已知并发调用、待处理授权都纳入调度检查,后台活动另行管理。
续接不得重复追加用户消息、重复工具结果或重新执行已完成工具。旧 Run 记为受控交接而不是伪造一次任务成功;新 Run 引用 continuationOfRunId,保留前后因果。新模型/权限的解析继续遵守现有消息快照规则,空间切换不变相提升权限。
关键门禁:如果关闭旧 SDK controller 会丢失仍运行的任务状态/stopTask 能力,**保留旧 controller 直到其受管任务结束,或阻止本次切换并说明需先停止哪些活动**。不能为“切换成功”关闭 controller,再把旧活动标记完成。宿主直接管理的进程可以独立于 Run/controller 生命周期,但必须验证这种独立性。
提交成功但新 Run 启动失败时,绑定保留已提交事实、会话暂停并报告失败,或通过新的受控操作回到原有效空间;不能后台偷偷回滚而让日志/UI 矛盾。只有新上下文已验证,才允许后续执行。
### 6.2 队列和待处理授权
- 每条队列消息入队捕获 workspaceId 和 bindingRevision,权限快照保持现有语义。
- 切换成功后,尚未派发且 revision 不匹配的消息进入 `blocked-workspace-change`;**不自动移到新空间、不悄悄留在旧空间执行**。
- 用户或拥有本会话管理能力的 agent 可明确选择重新绑定到已验证空间、保留旧目标待以后恢复,或取消。重新绑定产生审计事件和新的目标修订,不放宽原权限,不把旧已派发消息改成新消息。
- 外部删除触发自动回退也遵守上述规则:当前会话回主空间,不代表旧队列自动在主空间执行。
- 第一期有待处理授权时先阻断切换,允许用户完成/拒绝或显式取消旧请求;确认取消后再切换。不得把“允许”按钮的回调应用到另一个 Run/cwd。
- 异常删除或恢复使旧授权失去执行上下文时,旧请求失效,取消其尚未执行的调用。新 Run 真要执行相应动作,重新评估并重新请求授权,不能沿用一次性旧同意。
- 迟到的授权回复必须用 requestId、runId、workspaceId/revision 校验并拒绝过期结果;消息选择问答也不得驱动已失效的旧 Run。
### 6.3 原子性与故障恢复
不把 Git 创建目录、写存储和 SDK 启动假装成一笔可回滚数据库事务。使用最小持久化操作日志,加会话/空间锁和修订号校验;原子替换绑定记录时同时保留 operationId,跨文件结果通过日志对账。
| 中断位置 | 恢复规则 |
|---|---|
| 创建前失败 | 保留原绑定,记录失败 |
| Git 创建成功,登记或切换失败 | 保留新副本,报告其路径和未接管/待恢复状态,不擅自删除 |
| prepared 未 committed | 核实真实目录与 controller,再选择继续或终止;不能仅凭日志声称完成 |
| committed 后未确认新 Run | 保留已提交事实,禁止自动重放,恢复调度或标记 reconcile-required |
| 进程/任务状态无法核实 | 活动为 unknown,阻止相关清理;不重复启动可能仍活着的命令 |
| 清理时崩溃 | 对比 Git worktree 元数据、目录、登记与历史引用,记录实际残留,不自动 force/prune |
重启后未完成 Run、活动和操作先进入对账;原子 rename/日志仅改善一致性,不是跨进程无条件成功保证。任何恢复都不回滚已经发生的文件写入、发布或外部副作用。
## 7. 后台活动的真实生命周期
宿主统一 Activity Registry 作为 UI 和 agent 的事实来源。SDK 任务、宿主进程、PTY shell 使用各自 adapter,保留观测来源和覆盖范围,不把所有“工具调用成功”转换成“进程退出”。
```text
starting → running → exited / failed
└────→ failed
starting / running → stopping → stopped(有退出确认)
任意非终态 → unknown(断连、失去身份或跟踪)
unknown → 经可信对账恢复 running / stopping / 明确终态
```
自然退出与用户请求停止分开;收到停止请求只进入 stopping。停止超时保留 stopping 或 unknown,不移除活动。重复事件、乱序通知按 controllerId/taskId、SDK 的任务执行 run_id(如有)及实例代次、事件顺序/已核实终态归并,不能让旧 incarnation 的迟到进度覆盖新执行或退出结果。SDK 版本缺少足够执行身份、无法区分 resume/复用时转 unknown/对账,不能猜测合并。就绪探测是可选端口/健康检测,必须属于本活动且有明确目标,不因端口被其他程序占用就认定本服务 ready。
进程控制要求:
- 保存受管启动关系、句柄/启动身份,避免 PID 复用导致结束其他进程;代理端只接受 activityId。
- 停止 agent 不证明它的子进程退出;分别核实受管后代。宿主管理进程应验证 Windows 与其他目标平台的进程树策略。
- SDK background task 列表是该 controller 的任务快照,重启后空列表不能证明旧进程不存在。SDK-only coverage 不自动提升为 owned-tree。
- PTY shell 退出不证明其中所有命令退出。任意手输 shell 命令或脱离父进程的后台命令,要么通过受控启动协议纳管,要么明确其覆盖未知;不得靠日志猜测进程身份。
- 第一阶段覆盖宿主受管启动和经验证的 SDK task,禁止承诺“知道系统所有命令”。未覆盖的 YCode 启动方式会阻止其空间自动清理,而不是扫描外部命令补齐。
- 会话切换后旧活动继续显示来源空间;新空间启动同类服务仍可能有端口、数据库、凭据和共享 Git 状态冲突,worktree 不是完整运行隔离。
## 8. UI 落点与不变项
```text
▼ YCode
日常开发 主空间
开发登录功能 登录空间
修复加载问题 修复空间
后台活动 2(可折叠)
[子 agent] 审查登录实现 运行中 登录空间 [详情] [结束]
[进程] pnpm dev 已就绪 主空间 [日志] [结束]
─────────────────────────────────────────────
原输入区域 / 独占的授权或问答区域
```
新增独立 activitySlot,沿用原有间距、字体和样式变量,不新增复杂导航。活动多时折叠计数,详情和日志按需打开;退出活动保留可查记录,不无限占用输入区。
活动条与 floatingSurface、queueSlot、approvalSlot 分开:授权/选择卡独占输入区时,普通输入框仍隐藏;紧凑活动条可以保留,但不能遮挡选项或让输入框重新出现。发消息、整轮停止、队列和授权状态机不因活动条改变。单活动结束调用 activity.stop,不调用现有取消整个 Run 的 onStop。
侧栏任务 ID 与选中项不因切换改变,仅更新当前空间的轻量标记。活动显示启动空间,历史工具显示执行空间,不能都读取当前绑定渲染。正常切换无需额外长提示;阻塞、未知状态和失败必须在已有反馈位置明确说明。
## 9. 外部删除与清理门禁
打开会话、发消息、派发工具前校验空间,文件通知只做提前提示。确认删除需结合环境在线可访问、路径身份与 Git worktree 信息;目录暂时不可访问、权限错误、超时只能 unavailable/unknown。目录存在但被替换为另一仓库属于 identity-changed,不静默接管。
确认删除 → 阻止旧空间派发 → 收敛旧前台结果/停止状态 → 核实已登记主空间身份与权限 → 同一会话按切换事务自动回退 → 检查主空间现状再继续。旧后台活动仍保留可控;旧队列阻塞,旧授权失效;未知写入/发布不自动重放。主空间无效时暂停,不能随意选择全局空间。
清理只接受明确清理意图,使用空间级排他门禁阻止新受管执行,并在真正删除前重新校验:
1. 该空间的前台、后台、子 agent 及受管后代已确认终态,无 unknown 或覆盖缺口。
2. 未提交修改和未跟踪文件已有明确保全/处理结果;目录干净不是合并证明。
3. 未合并提交已有明确交付保全证据;合并路径须先确认目标分支合并成功,再清理,不能把清理操作变成隐式 merge。PR/补丁路径核实保全策略,不伪称已合并。
4. 其他任务/会话引用已显式处置;创建者不能越过其他使用者。历史引用保留,不是阻止所有清理的永久活动锁。
5. 需要返回的当前会话有有效主空间;当前绑定已迁移或在同一受控操作中完成迁移。
6. 删除前核对空间身份、检查版本和无新增调用,确保没有检查后又启动活动的竞态。
默认不 force 删除、不级联删分支、不删除历史。已有旧活动允许先切回主空间,但标记清理受阻。删除失败保留登记/残留状态,不宣称已完成。没有成果证据的外部删除只记异常与损失风险,不能记为成功交付。
## 10. 迁移与分阶段实施门禁
既有会话按已保存 workspaceId/cwd 迁移绑定,不能一律绑定当前全局空间。cwd 与登记不匹配时核对身份,无法核实则不可用;保留原始值供恢复。既有 Run 无可靠快照时标记历史来源未知,不回填一个“看起来正确”的空间。首期环境登记 local,不提前实现远程连接。备份/迁移版本与回退兼容策略随实际存储改动评审。
| 阶段 | 交付范围 | 进入下一阶段的门禁 |
|---|---|---|
| A 技术验证 | SDK 0.3.304 cwd/resume/工具边界;task 生命周期;controller 保留;Windows 受管进程身份与停止 | 记录真实可行路径及不支持项。若不能保留旧活动控制,采用切换阻断,不以 mock 通过替代 |
| B 核心绑定 | 最小类型、不可变快照、会话绑定、操作日志、队列/授权校验、创建/切换/返回 | 同一会话新 Run 真实 cwd 生效;故障与重复操作不重放、不误路由 |
| C 活动管理 | 注册表、SDK/进程 adapter、按活动查询/日志/停止、controller 生命周期 | 旧空间活动仍可管;终态可信;身份复用、重启未知、越权均正确处理 |
| D UI 集成 | 简单侧栏、独立活动条、轻量空间标记、单活动结束 | 授权/问答独占和原发消息/回合状态不回归,无额外导航树 |
| E 恢复与清理 | 确认删除回退、空间锁、成果/引用/覆盖门禁、迁移 | 故障注入和 Windows 真实验收通过,才开放受管自动清理 |
A 可用隔离 spike 验证而不改产品默认行为;后续分阶段交付不将未完成的能力暴露成成功按钮。新能力保留回退开关,关闭时不销毁已登记活动或历史;正在运行的活动仍须可观察和停止。
## 11. 验收清单与证据要求
| 场景 | 必须断言 | 验证层级 |
|---|---|---|
| 创建并继续/仅创建 | 前者登记并切换原会话,后者不改变绑定;不重复建目录 | 宿主集成 + 真实 Git |
| 当前绑定切换 | 新 Run 实际 cwd/仓库身份正确,旧 Run 快照不变 | 真实 SDK,不只 mock |
| 工具边界续接 | 旧工具仅执行一次,结果仅记一次,新工具进入新空间 | mock 故障注入 + 真实 SDK |
| 排队与授权 | 旧 revision 阻塞、重新绑定显式;迟到允许失效,新空间重新评估 | 状态测试 + UI |
| 旧后台活动 | 切换后仍显示旧空间、可查询/停止;关闭 controller 不丢管理 | SDK/宿主真实进程 |
| 存活/就绪/停止 | 活着未 ready、stop 请求未退出、超时 unknown 均不伪终态 | 受控长驻程序 + 平台测试 |
| PID/身份与权限 | PID 复用不误杀,任意 PID/其他会话活动操作被拒 | adapter 单测 + 平台集成 |
| 外部删除/不可用 | 确认删除才回退;离线/权限错误不回退;主空间无效暂停 | 文件/Git 集成 + 故障注入 |
| 崩溃恢复 | prepared/committed/删除中断均对账,不重放副作用、不假完成 | 持久化故障注入 |
| 清理门禁 | 脏文件、未跟踪文件、未保全提交、其他引用、unknown 均阻断 | 真实 Git fixture |
| 单活动结束 | 仅停止指定受管活动,不取消整轮或触及外部命令 | UI + 宿主集成 |
| 布局回归 | 授权/问答时不显示普通输入框,队列/活动互不遮挡,侧栏不增层 | YCode 截图/交互验收 |
| 迁移 | 原 cwd 保留,不匹配与旧历史未知如实呈现 | 存储 fixture |
真实 SDK、真实 Windows 进程与真实 UI 验收必须分别记录环境、版本、命令/操作和结果;mock 或类型检查不能证明 cwd 重绑定与进程树管理可靠。本文仅提交设计文档,未运行上述产品验收、未改动业务源码,后续交付必须逐项给出实际证据和仍未覆盖的范围。