Skip to content

Latest commit

 

History

History
186 lines (159 loc) · 14 KB

File metadata and controls

186 lines (159 loc) · 14 KB

交接文档:工作空间文件树功能(feat/workspace-file-tree)

本文档供后续 agent 接棒使用。功能已实现并提交本地,未推送、未合并,等待人工验证。

  • 日期:2026-08-28
  • 仓库:opensquilla(dark605/opensquilla)
  • worktree:D:\WorkTree\workspace-file-tree
  • 分支:feat/workspace-file-tree(基于 main @ 4de93713
  • 状态:✅ 已合并并推送(2026-08-29):mainfeat/workspace-file-tree 均在远程 33546342(直连推送成功,无需镜像);合并方式为快进,main 无冲突
  • 清理待办:git worktree remove D:\WorkTree\workspace-file-tree + git branch -d feat/workspace-file-tree(注意先停掉从本目录启动的网关服务)
  • 提交:d6e4b01d feat: 新增工作空间文件树(侧边栏浏览 + workbench 预览 + 附加到对话) 7fe8147d docs: 修正交接文档统计 · 7c4675e6 fix: 修复文件预览请求风暴与内容不显示 9f818980 feat: 预览面板选中文本右键菜单(复制/添加到对话) 46386aee fix: 根除预览右键原生菜单遮挡与重载后编辑器空白 e5ba2ffb fix: 修复任务执行期间预览面板闪烁 4d0c40f7 fix: 文件页签纳入工作台 8 页签淘汰上限 a42b40f3 feat: 页签栏右端新增倒三角下拉,可切换被压缩遮挡的页签 0a0ce31d fix: 页签下拉按钮贴右缘,消除页签旁泄漏的 identity 块 e566fb5d feat: 移除页签栏右侧的收起工作区按钮,关闭入口收进下拉与单页签行
  • 状态:✅ 本地提交完成,❌ 未推送(等用户验证后手动推送)

2026-08-29 首轮走查发现并已修复两个 bug7c4675e6): ① 预览面板 watch 依赖含 state 且回调内自调 load() → 请求指数风暴 → 网关 OOM 崩溃(MemoryError 冒顶未捕获)→ WS 断开 → 主区被重置到新任务界面; ② Monaco 在 onMounted 创建时容器 ref 为 null 被 early-return,编辑器从未创建、 内容不显示。修复:watch 只跟踪 path/workspace、幂等 ensureEditor() 在容器 渲染后创建、网关两个 handler 捕 MemoryError 返回 500。前后端各补 2 项回归测试。

2026-08-29 新增右键菜单9f818980):预览面板选中文本后右键/Shift+F10 弹出"复制 / 添加到对话"菜单;选区经 attach 事件新增的 content 字段直达 ChatView 管线,以 <文件名>.selection.txt 附件进 composer(保留 1MB 上限)。

2026-08-29 右键菜单两缺陷修复46386aee):① 原生菜单遮挡 —— Monaco ContextMenuController 先注册先执行,onContextMenu 里 preventDefault 迟于原生 菜单弹出;改为 contextmenu:false 根除原生菜单 + 容器 DOM contextmenu 监听 成为唯一菜单来源。② 重载后白屏不可恢复 —— load() 回 loading 时模板条件分支 销毁编辑器容器、ensureEditor 幂等守卫拒绝重建;编辑器容器改 v-show 恒在。 另加 openNonce 贯穿(重点击同文件 = 强制重载而非静默 no-op)。测试改走 synthetic contextmenu 真实 DOM 路径。

2026-08-29 任务执行期间面板闪烁修复e5ba2ffb):reload watch 的 单-getter-返回-数组写法被 Vue 以引用比较(恒新),流式任务执行期间 AppWorkbench 高频重渲染 + getProps 每次重建 workspace prop 对象 → load() 被误触发风暴(15s 内 32 次闪烁 + 33 次重复拉取);改数组源写法 (逐值比较)后归零。

2026-08-29 内存边界验证与修复4d0c40f7):实测重点击 30 次零泄漏 (nonce 重载复用实例)、关闭页签正确释放 Monaco;发现 file 面板无上限且 永不淘汰(每个 keep-alive 页签 = 常驻 Monaco 实例),已纳入与 artifact 预览相同的 8 页签激活顺序淘汰(openItem 保护当前项)。

2026-08-29 页签溢出下拉a42b40f3):tab 栏为隐藏滚动条的横向滚动区, 页签多时被压缩截断难辨难点。栏末新增倒三角按钮(sticky 右缘),点击弹出 全部页签下拉(激活项高亮),选择即切换;Escape/外点关闭。列表渲染在 滚动区外防裁剪;i18n 六语言 workbench.tabOverflow。

2026-08-29 下拉两处 UI 修复0a0ce31d):菜单 div 原插在 tabs 的 v-if 与 single-title 的 v-else 之间,v-else 错配到菜单自身 v-if (!tabMenuOpen),菜单关闭时 single-title identity 无条件渲染成"幽灵 页签";菜单移到 header 末尾(v-if/v-else 链外)+ ▽ 加 margin-left:auto 贴右缘。注意:往 v-if/v-else 结构化对之间插元素会破坏配对,编译器 不报错,改 WorkbenchHost header 模板时留意。

2026-08-29 移除页签栏收起按钮e566fb5d):用户反馈 ▽ 右侧的 ×(收起工作区)易误解为页签关闭。关闭/折叠入口重排:▽ 菜单末尾新增 分隔线 +「收起工作区」项;单页签模式 identity 旁新增 × 关闭当前文件。 焦点管理同步调整(aside tabindex=-1、关闭页签后聚焦剩余 tab 或宿主根)。 推送状态:未推送(ghfast.top 镜像与直连当时均连不上,用户令先不提交)。


1. 任务来源

需求:参考 anomalyco/opencodehttps://github.com/anomalyco/opencode)工作空间文件树的实现, 为 OpenSquilla Web UI 增加文件树功能。用户拍板的决策(勿改动):

决策项 结论
入口布局 侧边栏项目行操作区("新建任务"按钮与"更多"菜单之间)加"查看文件"按钮;点击后左侧边栏整体替换为该项目 workspace 的文件树;顶部"< 返回任务"切回会话列表
根目录范围 按钮所属项目行的 workspace(不跟随当前会话)
文件动作 预览(workbench 面板)+ 附加到对话 + 复制路径
实时性 手动刷新按钮(v1 不做轮询/watcher)

2. 改动清单(25 文件,+4419/−531 行)

行数大头来自 6 个 locale 文件的重新格式化(单行 JSON 展开为多行,文案内容未变); 扣除后实际净增约 2400 行。

后端(Python / gateway)

文件 说明
src/opensquilla/gateway/files_api.py(新增,424 行) 两个端点:GET /api/v1/files?workspace=<id>&path=<rel>(单目录列取,非递归)与 GET /api/v1/files/content?workspace=<id>&path=<rel>&max_bytes=<n>(文本预览,默认/上限 1MB 截断)
src/opensquilla/gateway/app.py(+8 行) create_gateway_app 中注册 register_workspace_files_routes(在 uploads 注册之后)
tests/test_gateway/test_workspace_files_api.py(新增,~330 行) 19 项 pytest:路径归一化/穿越拒绝、workspace 404/409、点文件与 .gitignore 过滤(含 !negation)、symlink 逃逸、二进制、截断、token auth

后端关键设计(接棒者须知):

  • workspace 解析走 resolve_validated_project_workspace(storage, id)project_workspaces.py), 只服务 trusted 的已注册 workspace;ProjectWorkspaceStateError 映射 404/409
  • 路径安全双保险:normalize_workspace_rel_path(内部 .. 折叠、越界/绝对路径拒绝)+ _resolve_inside_workspaceresolve() 后 containment 检查,挡 symlink 逃逸)
  • 点文件(.env/.git 等)一律不出现在列表和预览中;根 .gitignore 规则生效 (last-match-wins 语义,与 OpenTUI completion walker 一致,实现自包含在 files_api.py 内, 未复用 tui 模块以避免 gateway→tui 依赖);node_modules/.venv/__pycache__ 无条件跳过
  • 鉴权照抄 uploads.py 模式:request_origin_allowed + Bearer token(token mode 下)
  • 阻塞 I/O 全部 asyncio.to_thread

前端(opensquilla-webui / Vue 3 + Pinia + TS)

文件 说明
src/lib/fileTreeModel.ts(新增)+ .test.ts 自 opencode 移植的纯函数模型层(build/flatten/normalize),6 项 vitest
src/stores/fileTree.ts(新增)+ .test.ts 自 opencode 移植的 Pinia store:目录懒加载、inflight 去重、scope 序号使过期响应失效、删除文件时子树级联清理、refreshDir/refreshAll,8 项 vitest
src/components/SidebarFileTree.vue(新增,424 行) 虚拟化文件树视图(@tanstack/vue-virtual,行高 28px/overscan 10):目录展开/折叠、缩进导轨、hover 出"附加/复制"按钮、错误/空/加载态
src/components/workbench/WorkspaceFilePreviewPanel.vue(新增) workbench 预览面板:fetch content API → Monaco 只读编辑器(按扩展名切语言,面板复用时语言跟随);二进制/截断/错误降级提示;Monaco 加载失败退化为 <pre>
src/components/workbench/workspaceFileWorkbenchProvider.ts(新增) workbench kind:'file' 面板 provider(无 runtime,component + getProps 即可)
src/components/workbench/AppWorkbench.vue(+2 行) workbenchPanelRegistry.register(createWorkspaceFileWorkbenchDefinition()) —— 关键集成点,漏注册则预览面板不渲染
src/workbench/workspaceFileItems.ts(新增) createWorkspaceFileWorkbenchItem / workspaceFileFromWorkbenchItem(item id = ws-file:<workspaceId>:<path>,scope = {type:'workspace',id}
src/workbench/workspaceFileAttachEvent.ts(新增) 自定义事件 opensquilla:workspace-file-attach(App.vue 派发,ChatView 监听)
src/App.vue(+~70 行) 侧边栏状态机:fileTreeWorkspace ref + sidebarView computed;SidebarConversationsv-show 保留挂载;SidebarFileTree 条件渲染;viewWorkspaceFiles(经 useProjectWorkspaces().byId 取 name/path);preview → workbenchStore.openItem;attach → 派发自定义事件
src/components/SidebarConversations.vue(+~30 行) 项目行新按钮 data-testid="project-workspace-view-files"(可见性/禁用规则同 new-task:!selectionMode && canManageProjects && workspaceId && workspaceAvailable!==false)+ view-workspace-files emit
src/views/ChatView.vue(+~60 行) 监听附加事件 → fetch content → 构造 File 走既有 addAttachments 管线;拒绝二进制/截断/超 1MB(带 i18n toast 提示)
src/locales/*.json(6 语言 × +14 key) fileTree.* 命名空间(backToTasks/refresh/retry/empty/loading/viewFiles/attachToChat/copyPath/previewTruncated/binaryNotPreviewable/attachTooLarge/attachFailed/attachUnsupported/attached)
package.json + lock 新增依赖 @tanstack/vue-virtual
THIRD_PARTY_NOTICES.md(+~40 行) opencode MIT 许可登记 + 三个移植文件映射

3. 验证结果

提交时全部通过;2026-08-28 接棒复核重跑,结论一致

  • 后端:uv run pytest tests/test_gateway/test_workspace_files_api.py19 passed; 叠加 tests/test_gateway/test_uploads_endpoint.py(46 项)回归共 65 passed;ruff 干净 (提交信息中"27 项后端新测"为笔误,以后端新测试文件 19 项 为准)
  • 前端:npx vitest run4794 项中 4792 过,仅 ChannelsView.router.test.ts / ChannelsView.test.ts 各 1 项 5s 超时 —— 系全量并行负载抖动,两文件单独重跑 69/69 全过, 与本功能无关(文件树相关 14 项 fileTreeModel + fileTree 测试全绿); npx vue-tsc --noEmit 干净
  • npm run build 全量 gate 通过:architecture / chat-security / colors(9 主题对比度)/ motion / radius / theme-contract / theme-scope / cross-surface-tokens / i18n(6 语言 key 对齐)/ theme bundle guard / 404 产物写入 src/opensquilla/gateway/static/dist

4. 已知约束与遗留(接棒者注意)

  1. 未做:文件变更实时刷新(计划中 v1 为手动刷新);目录拖拽;git 变更角标(opencode 有,v1 砍掉)
  2. 附加到对话的边界:内容 >1MB 的文件拒绝附加(content API 上限),toast 提示; 二进制文件拒绝。若产品要求大文件附件,需后端新增"按 workspace 路径引用"的附件类型 (composer 的 Attachment 目前只接受上传的 File
  3. token 键名:HTTP 预览/列取用 sessionStorage.getItem('opensquilla.wsToken'), 与 src/stores/rpc.tsWS_TOKEN_KEY 约定一致(4 处新代码均遵循)
  4. App.sidebar-contract.test.ts 是文本扫描型契约测试,本次未断言项目行按钮构成, 无需改动;若后续在 App.vue 侧栏改结构,先看它
  5. Windows 行尾:新 Python 文件 LF,git autocrlf 会转 CRLF(警告无害)
  6. 环境特殊性(本机):直连 GitHub 443 被墙,git 操作走 ghfast.top 镜像 (git remote set-url origin https://ghfast.top/https://github.com/... 用后改回); uvC:\Users\Zc\.local\bin;worktree 的 .venvnode_modules 已就绪, 接棒无需重装

5. 接棒操作清单

cd D:\WorkTree\workspace-file-tree

# 本地运行验证(webui 已构建,可直接起网关)
uv run opensquilla gateway run        # → http://127.0.0.1:18791/control/

# 用户验证通过后推送(直连不通时临时切镜像)
git push -u origin feat/workspace-file-tree

# 按 worktree 规则合并(用户确认后在主目录执行)
git merge feat/workspace-file-tree && git push
git worktree remove D:\WorkTree\workspace-file-tree && git branch -d feat/workspace-file-tree

手工走查点

  1. 侧边栏项目行 hover → 文件夹图标按钮 → 侧栏切为文件树;"< 返回任务"切回(会话列表滚动位置不丢)
  2. 目录展开懒加载、刷新按钮、错误态重试
  3. 点文件 → 右侧 workbench Monaco 只读预览;切文件语言跟随;二进制/超 1MB 降级提示
  4. 文件行 hover → "+" 附加进 composer 输入框、复制按钮 → 剪贴板为绝对路径
  5. workspace 不可用(workspaceAvailable===false)时按钮禁用

6. 若需回滚

分支未推送,git worktree remove + git branch -D feat/workspace-file-tree 即可完全移除, main 无任何改动。