2026/9/25 14:42:39

Session Sidebar 架构指南:OpenChamber 会话侧边栏的模块划分、数据同步与虚拟化实现

Session Sidebar 架构指南:OpenChamber 会话侧边栏的模块划分、数据同步与虚拟化实现 AI Agent人工智能代码智能体交互助手【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址https://gitcode.com/gh_mirrors/op/openchamber点击查看免费下载Session Sidebar会话侧边栏是 OpenChamber基于 OpenCode AI Agent 的 Agentic 开发环境桌面端Web / Electron与 VS Code 扩展中共用的会话管理核心组件负责 Chats、Recent、项目projects、分组groups、文件夹folders与会话行的投影、搜索、活动状态、拖拽与批量操作。本文基于仓库中 packages/ui/src/components/session/sidebar/DOCUMENTATION.md 的完整技术说明结合源码与测试展开帮助读者理解侧边栏的模块归属、全局会话缓存与目录引导bootstrap demand机制、Timeline 与 Search 模式、加载规则以及虚拟化与粘性头实现。读完本文你将能够定位任意侧边栏行为对应的源码模块并理解其底层数据流与约束。模块划分按业务对象归属的目录结构侧边栏代码按照“它拥有的业务对象”来组织共享契约放在根目录的 types.ts 与 utils.tsx 中。types.ts定义了SessionNode、SessionGroup、SessionSidebarRow、SessionSidebarRowModel等核心类型是理解全部子模块的入口。目录职责shell/侧边栏外壳chrome、导航、搜索、确认对话框与切换器switcher效果list/全局优先的会话集合、目录引导需求bootstrap demand、布局持有的同步、权威清理authoritative cleanup、邻近会话预取prefetchprojects/项目分区zones、分组、排序、滚动器行为、项目视图状态、仓库状态与 worktree 呈现sessions/会话行、行操作、展开、所有权ownership与活动指示器recent/Recent 与受管 Chats 的活动投影folders/文件夹拖拽、批量操作、归档文件夹与文件夹 UI其中有两个文件处于架构中心sessionSidebarRowModel.ts 持有“有序、模式无关”的行投影projection统一负责 Chats、Recent、项目、分组、文件夹、会话、状态通知、空状态与 reveal 控件。它导出SessionSidebarViewModeprojects | timeline与SessionSidebarRenderContextproject | recent | timeline | timeline-chat等类型见 types.ts。SessionSidebarRows.tsx 是桌面 Web、Electron 与 VS Code 共享的行渲染器。普通模式与已提交搜索committed-search模式使用同一个模型与同一个tanstack/react-virtual实例即搜索不会引入第二套虚拟化状态。// packages/ui/src/components/session/sidebar/types.ts#L46-L54 export type SessionSidebarRow | (RowBase { kind: activity-header; activityKey: SessionSidebarActivityKey; collapsed: boolean; forceExpanded: boolean }) | (RowBase { kind: project-header; section: ProjectSection; collapsed: boolean; forceExpanded: boolean }) | (RowBase { kind: group-header; group: SessionGroup; ... }) | (RowBase { kind: folder-header; group: SessionGroup; folder: SessionFolder; ... }) | (RowBase { kind: session; node: SessionNode; depth: number; ... }) | (RowBase { kind: empty; emptyKind: sidebar | search | group | archived; ... }) | (RowBase { kind: status; status: SessionSidebarGroupStatus; group: SessionGroup; groupKey: string }) | (RowBase { kind: show-control; control: more | fewer; containerKey: string; ... });该联合类型说明侧边栏渲染的每一种“行”都是模型里的一种判别式discriminated union成员activity-header、project-header、group-header、folder-header、session、empty、status、show-control。任何 UI 层的新行类型都必须先落在这个模型中渲染层才有对应的分支。活动指示器折叠组的单点汇总sessions/拥有会话行活动指示器折叠的分组或文件夹只显示一个指示器汇总其隐藏会话待定权限shield 待定问题question 运行中回合running turn 未读unread。优先级说明待定请求从跨目录的global-blocking-requests索引读取因此本次启动从未打开的项目也能显示它们运行中与未读来自全局状态索引与通知 store。实现上SessionActivityIndicator用于项目行、时间线timeline行、头部标签、切换器与折叠聚合见 collapsedActivityIndicator.tsx 与 collapsedActivityState.ts。运行中用 info 色未读用 success 色。本地 Appearance 偏好animatedActivityIndicators默认关闭开启后即使系统请求减少动画reduced motion运行中的圆点也会换成步进 spinner。权限/问题徽章与逐会话计时器保持原有优先级与行为。显示 store 保持版本 8缺失偏好在水合hydration时继承默认值而显式保存的选择在重载后存活。数据同步全局会话缓存与目录引导需求useSessionListSync唯一的引导需求持有者MainLayout与VSCodeLayout无条件调用 list/useSessionListSync.ts 中的useSessionListSync({ isVSCode })。该 hook 是唯一的 bootstrap demand 持有者只发布当前目录与所选会话的目录同时负责刷新新增拓扑topology合并coalesce控制事件执行权威清理authoritative cleanup。// packages/ui/src/components/session/sidebar/list/useSessionListSync.ts#L41-L47 React.useEffect(() { childStores.setBootstrapDemand(bootstrapDemandOwner, buildSessionBootstrapDemands({ currentDirectory, currentSessionDirectory, })); return () childStores.clearBootstrapDemand(bootstrapDemandOwner); }, [bootstrapDemandOwner, childStores, currentDirectory, currentSessionDirectory]);关键约束已知项目根目录与 worktree 是拓扑topology不是引导需求。行与会话来自全局会话列表活动来自全局状态索引与宿主状态种子。这是因为每次目录级读取都会让 OpenCode 创建并初始化一个 location若启动时把整个拓扑都设为需求每个目录就会各产生一个 OpenCode 实例源码注释原话initializing them only created one OpenCode instance per directory at startup。根级useGlobalSessionsPolling仍是唯一初始 45 秒间隔的全局轮询器带受限启动恢复。useSessionListSync不得创建第二个全局轮询生命周期——它订阅 OpenChamber 控制事件流scheduled-task-ran触发全量刷新、session-created按目录刷新500ms 去抖合并见 useSessionListSync.ts。全局缓存是权威源全局会话缓存global sessions cache是激活与归档覆盖的完整来源。已初始化的目录 store 只补充缓存中缺失的会话。实时 busy 与 retry 状态来自global-session-status从不来自全局缓存或持久化历史。全局或目录拉取失败时保留既有数据绝不视为权威空列表it is never treated as an authoritative empty list。useSidebarGroupStatus分组状态的订阅规则list/useSidebarGroupStatus.ts 同时订阅项目目录与独立 Chats 目录Chats 使用同一activity:chats身份做状态与行投影包括纯 Chats 侧边栏未解析的全局列表让未打开的分组保持 loading全局失败暴露 Retry直接作用于全局加载器不会引导bootstrap任何目录完整全局快照或完整激活目录覆盖会独立于初始化停止 loading归档分组要求全局覆盖目录失败保留各自的 retry/access 操作。其状态机由 types.ts 定义ready | loading | load-failed | initialization-failed | permission-denied并携带directory与canGrantAccess两个字段。会话位置解析与 worktree 索引recent/sessionLocation.ts是“会话的项目、目录、worktree 与分支标签”的唯一所有者供 Recent 与 Timeline 读取行元数据先通过会话所有权索引解析项目受管 worktree 位于项目路径之外路径前缀匹配永远找不到它们的项目回退到路径前缀匹配用最长前缀查找包含的 worktree使worktree/sub中的会话仍保留该 worktree 的分支与 PR key见 resolveSidebarSessionLocations。分支标签是live-first实时 git 状态优先于已发现的 worktree 元数据resolveBranchLiveFirst见 worktreeIndex.ts。Recent 隐藏与项目标签相同的分支Timeline 每行都显示分支。worktreeIndex.ts 是共享的精确 worktree 索引供 Recent/Timeline、项目分组与会话切换器复用其不变式归一化 key、排除项目根、first-wins 去重重复路径只保留第一条避免拓扑重复发布翻转所有权。findPrefixWorktreeEntry用最长前缀匹配worktree/sub场景。Timeline 视图扁平的活动时间线sidebarViewModeprofile 作用域、按 surface 区分在桌面与 Web 的projects与timeline之间切换VS Code 无开关、始终渲染projects。Timeline 的关键规则对应文档 “Timeline view” 一节保留受管 Chats 分区初始 reveal 为 3而非通常的 Chats 上限置顶pinnedchats 总是显示且不占用该额度Show more/Show fewer 只统计未置顶行。Chats 行以renderContext: timeline-chat渲染单行、无左侧 gutter、置顶与状态点在右侧时间旁。折叠分区头会重置其 Show more 状态。分区头在 projects 视图粘性、在 timeline 永不粘性且无用户开关Timeline 分区头去掉前导图标、使用更高的色带。Chats 之下渲染一个timeline活动头粘性分区头同chats、active-now随后是所有项目与 worktree 的全部非归档根会话的单一扁平列表按共享生命周期顺序排列、置顶会话在前。没有 reveal 上限列表完全虚拟化。Timeline 行携带renderContext: timeline、depth 0、children 为空永不展开、无 chevron、无文件夹、无项目头、无 worktree 分组、无 Recent 投影。由于文件夹不投影行菜单隐藏 “Move to folder”但归档/删除操作仍覆盖完整子树collectSessionSubtreeIds在动作时从全局缓存解析后代。搜索用与 Recent 相同的规则过滤 Timeline精确ses_id 否则标题包含每个列出的行计一次匹配。常量佐证sessionSidebarRowModel.tsSESSION_ESTIMATE 32、TIMELINE_SESSION_ESTIMATE 64、TIMELINE_CHATS_INITIAL_LIMIT 3、TIMELINE_CHATS_INCREMENT 7——行高估算直接参与虚拟化测量。搜索只在 Enter 提交精确 ID 优先侧边栏、移动会话列表与归档列表中的专用搜索框只在 Enter 提交。SessionSearchInput本地持有草稿文本列表所有者只接收已提交查询因此输入不会使会话树失效清空字段立即重置已应用查询。IME 确认与长按 Enter 不提交Escape 先清文本再在为空时关闭侧边栏搜索。会话行只收到一个稳定的 reset 动作而非瞬态的 search-open 或草稿状态关闭保留中的移动端搜索会丢弃未提交文本。ID 匹配规则以ses_开头的查询只匹配完整会话 ID大小写不敏感、忽略周边空白部分 ID 与拼写错误返回无匹配不回退到标题、目录、分组标签或文件夹名。匹配节点保留其子树用于渲染与子树操作祖先仍作为树上下文保留。只有精确 ID 匹配计入结果总数。ID 搜索不包含归档会话ArchiveView对其归档列表应用同一精确 ID 规则。搜索改变的是模型输入而非渲染器所有权它强制项目/分组/文件夹/活动行打开却不改变普通模式的折叠或 show-more 状态因此关闭搜索精确恢复之前的 Chats、Recent、项目、分组、文件夹投影。搜索不获取会话、不扩宽列表成员Search does not fetch sessions or broaden list membership。加载规则引导、失败与可视性的约束清单文档 “Loading rules” 一节总结了决定侧边栏稳定性的关键规则以下摘录最核心的几条并标注源码依据引导需求只发布当前目录与所选会话目录。目录需求与刷新请求在分隔符与盘符归一化后保留路径大小写大小写不敏感的侧边栏成员 key 留在集合投影内把 key 当路径发送会创建重复目录 store在大小写敏感文件系统上可能寻址到不同目录。未引导的目录只有完整全局快照后才 ready。此前分组显示全局 loading/failureRetry 重载全局列表已加载分组在后台轮询期间保持 ready。目录失败与拒绝的文件夹访问仍对受影响目录使用强制引导或原生访问恢复。同步调度器负责去重、提升、重试与限流侧边栏组件不得用 mount effects 复刻该生命周期。隐藏投机工作侧边栏/聊天 surface 隐藏时消息预取、Git/PR 富化与订阅、搜索监听、粘性头观察、归档文件夹派生全部停止会话行树卸载使行拥有的状态/权限/unseen/viewport 订阅不做后台工作。外层侧边栏保持挂载以保留 UI 状态与权威目录刷新可见性恢复时从当前状态重跑延迟的派生工作见 useSessionPrefetch.ts 与 useAuthoritativeSessionCleanup.ts。父展开完全手动选择或导航到子会话从不自动展开父级。project/worktree 与recent树使用独立持久化 context key 与独立稳定投影持久化存储 key 保持v3旧状态混用 context 且不迁移进此契约见 useExpandedParents.ts。空列表、未解析加载与失败是三种独立 UI 状态失败分组暴露 Retry 并保留旧数据。列表加载与工作区初始化分离spinner 只跟随列表队列config/MCP/LSP/实时状态恢复不能让已成功的空列表继续转圈。目录权限失败在保留陈旧会话时仍可见扁平分组检查每个代表的根/worktree 目录桌面本地可为精确失败目录打开原生选择器其他运行时保留普通 Retry。Shift 选择与 Ctrl/CmdA 消费模型的逻辑行序API 会话 ID 只在动作边界去重隐藏后代已纳入后。运行时切换与确认的会话删除会清除选择。批量破坏性动作在动作时从模型当前会话记录分类归档状态绝不使用挂载 DOM 或选择时元数据。Move to worktree整棵子树的迁移语义根会话右键与溢出菜单暴露Move to worktree子菜单列出规范主 worktree 与关联 worktree 目标当前目标禁用另有独立New worktree...动作。打开子菜单会刷新 worktree 拓扑见 sessionWorktreeMenu.ts。移动语义的关键保证移动转移完整空闲子树。干净与非 Git 源仅移动会话脏 Git 源提示三选一仅移动会话 / 移动全部源改动 / 取消。后代先移动且不带改动若之后某后代失败则仅回滚会话部分根最后移动且只携带一次源改动防止回滚把已转移的补丁重放进源。失败清理为移动创建的 worktree 只在明确失败后移除携带改动的请求失败且未确认结果时该 worktree保留可能持有用户改动的唯一副本两个目录都做权威刷新会话可能已在服务端移动toast 指向目标位置。既有目标从不移除同样给出指引。打开子菜单强制刷新所属项目的 worktree 拓扑外部创建的 worktree 无需完整重载即出现刷新期间菜单保留最后已知的主/关联拓扑刷新失败则陈旧拓扑保留且加载失败状态保持显式。worktree 拓扑发现是事件驱动的包括session-created与服务端worktree-changed控制事件无空闲轮询。服务端在自身 worktree 创建/移除、或在状态/列表请求注意到某仓库 worktree 集合变化后发送worktree-changed见 packages/web/server/lib/git/DOCUMENTATION.md事件点名该仓库服务端见过的每个目录侧边栏对其中每个已注册项目各刷新一次绕过 30 秒列表缓存。宿主移动端与桌面 mini chat 通过 lib/worktrees/worktreeTopologyRefresh.ts 处理同一控制事件VS Code 有意排除 worktree 拓扑。虚拟化与粘性头单一 scroller、交叉淡化分区头侧边栏虚拟化sessionSidebarVirtualization.ts的关键常数与函数const INITIAL_ROW_LIMIT 24; export const getInitialSessionSidebarRowIndexes (rowCount: number): number[] Array.from({ length: Math.min(rowCount, INITIAL_ROW_LIMIT) }, (_, index) index);既有ScrollableOverlay是唯一滚动所有者。共享行渲染器测量变高行、使用稳定 occurrence key、保持受限的预初始化窗口并在 range extractor 中钉住编辑/聚焦/打开菜单的 occurrence。scroller 通过 callback-backed state 发布其 DOM 元素使虚拟化在每次挂载后激活无需等待无关渲染。归档分组不得添加嵌套 virtualizer。SessionTreeItemsessions/SessionTreeItem.tsx用比较器做 memoization比较它实际读取的 props行列表在滚动每一帧与每次模型重建时重渲染向每行散布共享 props bag 与全新renderExtras对象身份比较永远无法命中。会话与次级元数据按值比较因此滚动只渲染进入视口的行模型重建只渲染会话发生变化的行。模型把父子会话展平为 occurrence-keyed 行SessionTreeItem每行渲染renderChildren{false}绝不在共享 scroller 中递归挂载后代。单个 preorder ID 池加索引区间为子树选择提供隐藏后代而不为每个祖先复制后代数组见 sessionSidebarRowModel.ts 的indexNodes。粘性项目/活动头来自模型头描述符与首个可见虚拟索引resolveSessionSidebarStickyHeader从描述符列表中取“rowIndex 不超过首个可见索引”的最后一个sessionSidebarRowModel.ts使当前与相邻头行保持挂载。CrossfadeZoneHeadersprojects/CrossfadeZoneHeaders.tsx使用其缓存的虚拟布局偏移做视觉交接活跃头在虚拟行占位符与原生 scroller 内静止层之间移动时保持一个 portal host保留其控件与菜单状态行/头尺寸或虚拟起点变化刷新缓存边界滚动只比较偏移并在分区交接时改 DOM离开的头做 inert、accessibility-hidden 快照在进入的头之上 150ms 淡出reduced motion 跳过淡出项目拖拽临时把头送回各自分区而不重挂载控件重排用包含虚拟定位但排除 sortable transforms 的布局偏移刷新边界使归位动画不会留下陈旧头位置。会话树操作、所有权与预取子树归档/删除归档或删除会话在每个 surface都带走其整个激活子树因为服务端不级联time.archived。Recent 与受管 Chats 用 list/sessionCollection.ts 的buildActiveSessionNode构建行移动会话 sheet 用getDescendantIds在完整激活列表上解析同一谱系。sessions/sessionSubtreeActions.ts 拥有单发/批发 store 调用与全部结果 toastcollectSessionSubtreeIds在动作时用全局激活 归档缓存的遍历扩展 surface 自身的后代列表归档中间节点之下的激活子代理仍被归档归档跳过已归档中间节点删除包含它。把树展平为一层的投影会静默遗留孙代激活。所有权解析与回退完整应用的激活记录在其目录不再属于已知拓扑时仍保留在集合中如已删除的 worktree。分组先使用精确配置的项目/worktree 所有权仅当某项目的规范 worktree 映射到已配置项目根时才回退使用权威 OpenCode 项目元数据。该回退只改显示所有权不改路由用的会话真实目录。无解析所有者的记录没有保证的项目分组。VS Code 保持精确工作区目录作用域、不使用回退移动 sheet、Recent 与 Timeline 使用同一所有权解析器行保留会话自身目录同时从索引取显示所有权project id、labels见 sessions/sessionOwnership.ts。邻近会话预取list/useSessionPrefetch.ts 实现相邻会话预取服务“选中会话旁的会话更快可读”的目标与文档“目录需求只覆盖正在工作的目录”呼应显示、展开或恢复项目从不引导它行挂载不得启动引导工作。选择与活动订阅保持会话作用域结构性列表更新不会让每行都观察无关的流式更新。跨 surface 差异Web/桌面、移动端与 VS CodeWeb 与桌面显示受管 Chats位于可选 Recent 活动之前Recent 默认关闭——timeline 视图存在后由显示菜单切换。Chats 使用共享受管根建文件夹、永不暴露 worktree 动作。项目显示可为全部项目或单选一个项目。移动端宿主移动端与 Capacitor 使用独立MobileSessionsSheet渲染器apps/MobileSessionsSheet.tsx通过partitionSidebarSessions以同样方式分区Chats 作为项目树之上的可折叠区段列出无 Recent 投影。共享目录缓存规则适用但此侧边栏虚拟化器不适用。VS Code排除 worktree 与受管 Chats保留工作区作用域的归组列表与行内归档桶。无 timeline 开关。VS Code 在alwaysShowActions下也在行右缘 hover-reveal 操作因此其徽章保持淡出selectRowBadgeVisibilityClass见 sessions/sessionNodeItemUtils.ts。项目动作指示器目录级终端活动SidebarTerminalActivitylist/SidebarTerminalActivity.tsx与动作头、终端面板共享终端发现一次服务端列表覆盖所有目录含折叠项目。侧边栏只在已知有项目动作运行期间保持该循环无运行中时挂载列表一次拾取另一客户端启动的运行后保持安静——空闲侧边栏零轮询。它保留比列表更新的本地变更失败时保留已知状态。终端发现与 OpenCode 会话引导分离。DirectoryActionIndicatorsessions/DirectoryActionIndicator.tsx只读自己目录的终端元数据输出块与无关目录不会重渲染它。活动项目动作含自动发现命令显示status.info中的静态pulse图标持久化的空闲标签页与普通交互终端不指示活动——这是进程活动而非服务端就绪状态。分组视图在项目根与 worktree 头上显示图标扁平项目视图在项目根头与关联 worktree 会话上显示Recent 在自身目录有活动动作的每个会话上显示归档桶不显示。指示器留在既有行/头操作 padding 边界内hover、键盘焦点与 always-visible 操作按钮把它们左移而不隐藏。命名与交互细节速查AI 重命名会话菜单与头部标签、单会话头共享SessionAiRenameMenuItemAI 重命名使用与 worktree 移动相同的前导 spinner待定操作在关闭菜单或切换会话后仍存活资格仅在菜单打开时加载。上下文选择与变更守卫见 packages/ui/src/sync/DOCUMENTATION.md 的 AI session titles 一节。手动重命名输入框与头、移动列表共享 components/session/sessionRenameKeyboard.tsEnter 在 keydown 时显式提交所属表单Escape 取消IME 组合键保持文本输入行为按住 Enter 不重复提交。Run fusion 资格来自 lib/multirun/identity.ts仅未标记的遗留会话解析标题行 memoization 比较同一语义元数据级成员变化即更新菜单。源选择与 fork 规则见 lib/multirun/DOCUMENTATION.md。Recent 成员规则激活根会话立即进入 Recent 成员即使其最后提交的time.updated超出 48 小时窗口子与归档会话仍排除非激活根仍基于时间戳。激活 ID 订阅在侧边栏隐藏时禁用并忽略 retry/status 细节变化避免流式频率重渲染。置顶与文件夹赋值不从不含置顶/文件夹的首启快照或乐观变更中修剪确认的本地删除与路由的外部删除立即清理基线确立后的权威遗漏覆盖错过的外部删除事件。结语读源码的地图回到起点——DOCUMENTATION.md 的目录划分就是这份地图的图例行为归属决定代码位置共享契约沉淀在根级types.ts/utils.tsx。调试侧边栏问题时先问“这是谁的业务对象”会话行、徽章、动作 →sessions/列表来源、引导、清理、预取 →list/分区、粘性头、排序 →projects/Recent/Timeline 行投影与位置 →recent/sessionLocation.ts文件夹拖拽/批量 →folders/行模型与虚拟化契约 →sessionSidebarRowModel.tsSessionSidebarRows.tsx。行为测试散落在各目录如 SessionSidebarRows.test.ts、SessionTreeItem.behavior.test.tsx、sessionFolderDnd.behavior.test.tsx与本文描述的规则一一对应是验证语义最直接的依据。赞分享AI Agent人工智能代码智能体交互助手【免费下载链接】openchamberAgentic Development Environment based on OpenCode AI agent项目地址https://gitcode.com/gh_mirrors/op/openchamber点击查看免费下载相关推荐Apache Maka Session Navigation桌面端会话侧栏的功能边界设计与实现解析Apache Maka Session Navigation桌面端会话侧栏的功能边界设计与实现解析 Session Navigation 是 Apache M人工智能AI Agent自主智能体工具调用交互助手AI 评测AO Session Rename Surfaces 设计解析侧边栏与终端会话标签的内联重命名实现指南AO Session Rename Surfaces 设计解析侧边栏与终端会话标签的内联重命名实现指南 导读 本文围绕 AOagent orchestrat如何在PC上完美运行Switch游戏yuzu模拟器终极性能优化指南如何在PC上完美运行Switch游戏yuzu模拟器终极性能优化指南 想要在电脑上畅玩Switch游戏却遇到卡顿、闪退、兼容性等问题yuzu模拟器作为目前最强虚拟化桌面应用图形学上一篇给 xiaozhi-esp32 配上 ES8389 音频编解码器接线、驱动到排障一次讲清下一篇10分钟精通暗黑破坏神2存档修改器Diablo Edit2终极实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考