# 机制总览 ## 目标 / 非目标 **目标** 1. 归档记录的"宽限期"语义:归档(页面归档或物理归档)后保留 N 天(默认 7,页面可配,天/小时),到期自动物理删除 2. 孤儿子 agent 跟随语义:父会话目录消失后,其子 agent 目录在下一轮清扫随保留期一起删除 3. 页面可配置:设置页插件区一张卡片,总开关/保留期(数值+单位)/三个范围开关 4. 每次删除落审计清单,进程日志留痕 **非目标** - 不清理未归档的**主会话**(自动归档化冷主会话是另一个待做项目;子 agent 盲区由 scope ③ 覆盖) - 不做模型可见工具(纯后台定时;手动触发等有真实需求再加) - 不改 workspace 注册表(页面归档名单里的悬空 ID 被 UI 容忍) ## 工作原理 ``` cron(30s tick 检查到期) → 读设置命名空间快照 → 三个范围扫描 → planRetention / planOrphans 纯函数判据 → 动作前复查 mtime → rm → manifest + 日志 ``` - **设置命名空间**:宿主半部 `installSettingsSection(ctx, 'archive-retention', Config, config, …)` 把插件 Config 注册进设置系统——组合层(patch)做默认值,页面改动存设置存储的用户层,`scope.watch` 回流后下一次清扫即用新值。页面端由客户端半部在 `settings.plugin.item` 坐席注册"归档自动清理"卡片(迷你暂存表单:本地草稿 → 保存走 `scope.set`,放弃即丢)。 - **客户端形态**:与 dsh-workspace-files 同款拼接式 bundle——纯 JS、`window.__ModuleLoader__.load({ factory(require) })` 包装、react 等外部依赖走平台模块表 `require`,构建门禁在沙箱里按 loader 消费方式演练一遍。 ## 安全边界 1. **attached 不删**:`ctx.sessions.get(id) !== undefined`(记录在内存)的页面归档会话一律跳过——归档不等于会话结束,活会话可能还挂内存;重启后下一轮清扫补删 2. **动作前复查**:删除前重新 stat `session.jsonl.zstd`,mtime 晚于规划探测值即跳过(竞态防护,沿用 2026-08-26 505 目录手工验证的流程) 3. **范围锁死**:物理归档堆只扫 `sessions-archive/`;页面归档只扫注册表名单 ∩ 磁盘存在;孤儿子 agent 只删"头部里 parentSession 指向的父目录确实不存在"且超期的目录;`_manifests` 永不触碰;主会话不在名单里就永远不碰 4. **审计**:每轮有删除才落 `retention-*.json`(id、来源、字节数) 5. **孤儿判据保守**:头部读取失败 = 无法确认父子关系 = 不动;父目录仍在宽限期内(未物理删除)时子目录跟随等待,不提前 ## 边界与限制 - 页面归档会话删除后,归档名单里的 ID 悬空——UI 容忍(tree 只遍历列表条目),无需回写注册表 - 小时级保留期的实际粒度受清扫节奏限制(默认每小时一查,±1 周期内) - `ctx.sessions.get` 只能识别本进程内存中的会话;跨进程(另一 dsh 实例)场景由保留期本身兜底 - 孤儿判定依赖"目录名 = 会话 id"与头部 `parentSession` 字段(指向父会话 id);跨 workspace 的父子关系按全局目录名索引解析 ## 验证基准 - DSH `0.1.1-rc.2`,Windows,web profile - 判据引擎与 2026-08-26/27/28 三轮手工清理(505+57+46+76+13+54+75+33 个目录,零事故)同源同界