# opencode_bridge 交接文档 ## 状态速览 **分支**:`main`(已切回,在 main 上操作) **远端**:`http://101.43.40.124:3000/tao.chen/notebook-ai-extension.git` **当前 HEAD (main)**:`d650736 chore: bump version to 0.1.2` **Tag**:`v0.1.2` → `d650736`(已推到远端,CI 会自动 build + 发 Gitea release + Bark 通知) ``` d650736 chore: bump version to 0.1.2 ← main HEAD, v0.1.2 f9d52f7 docs: add HANDOVER.md for next session context 310d507 fix: SSE filter no longer drops content events for a different session ``` ⚠️ **重要分歧**:`main` 这次的 v0.1.2 发布**只包含了 version bump**,没有功能改动。完整功能(per-cell → 全局 floating panel 重构)在分支 `feature/floating-ai-button` 上,**未合并**。git status 是干净的,如果你想继续 feature,先 `git checkout feature/floating-ai-button`。 ``` feature/floating-ai-button HEAD: 12ec84a docs: refresh HANDOVER for floating panel work (d412130) d412130 fix: hide+show cycles no longer accumulate prompt nodes in floating panel c0eba7e feat: replace per-cell AI button with global floating panel (已 push 到远端 + tracking 已设) ``` --- ## 本次会话做了什么(按时间序) 1. **HANDOVER.md 初版**(上一任留下的)— 已读 2. **创建 `feature/floating-ai-button` 分支** 3. **重构**:把 per-cell 工具栏 AI 按钮(`OpenCodeCellActions`)整个换成全局右下角悬浮按钮 + 弹出 panel - 新增 `src/components/opencode_floating_panel.ts`(Widget,挂 `document.body`) - 删除 `src/components/opencode_cell_actions.ts`(逻辑搬上去) - 改 `src/components/opencode_inline_prompt.ts` — 加 `getActiveCell?: () => CodeCell | null` 回调,3 处 cell 引用全用回调 - 改 `src/index.ts` — 删 `toolbarRegistry.addFactory`,接入 floating panel - 改 `style/base.css` — 删 `.opencode-cell-actions`,新增 `.opencode-floating-*`(fixed 定位、z-index 1000) - 重命名 + 重写 `src/__tests__/opencode_floating_panel.spec.ts` 4. **Bug fix (d412130)**:hide+show 循环累积 prompt 节点 — 用户手测报告,在 `_hidePrompt` 加 defensive DOM cleanup 5. **Commit + push** 到 `feature/floating-ai-button`(只到这,没合并) 6. **回 main**:**用户决定在 main 上单独发 v0.1.2**(只 bump 版本号,不含功能) 7. **v0.1.2 tag + push** --- ## 验证状态 | 检查 | 结果 | |------|------| | Jest | ✅ 68/68(3 suites)— **在 feature 分支验证过**,main 上 0.1.2 没跑过 | | pytest | ✅ 73/73(同上,feature 分支) | | `tsc --noEmit` | ✅ exit 0(feature 分支) | | ESLint | ⚠️ 2 pre-existing 命名错(`FlatProvider`/`BlockEntry` 接口名,不符合 `/^I[A-Z]/u`),我没引入 | | `jlpm build:prod` | ⚠️ 未跑(见下) | --- ## ⚠️ 下个 agent 必须知道的几件事 ### 1. Codex 在本会话里基本不可用 ⚠️⚠️⚠️ CLAUDE.md 强制 `model: kimi/kimi-k2.7-code`(或后来的 `kimi/kimi-k2.6`),但火山方舟 Coding Plan proxy 反复拒绝: - `kimi-k2.7-code` — HTTP 400 "model not valid"(模型名错) - `kimi/kimi-k2.7-code`(CLAUDE.md 当前版本) — 同样被拒 - `kimi-k2.6` — HTTP 400 "reasoning is not supported" - `kimi/kimi-k2.7-code` noop 偶尔通一次,然后实际任务又失败 **实际代码工作几乎全是 CC 手动做的**。Codex 临死前对 spec 文件做了一次有用的部分重写(改完了 65% 然后挂了)。 **建议下次**: - 先 noop 测一次 Codex,通了再用;不通直接走 CC fallback(CLAUDE.md 规则 `codex-fail-3x → switch-to-cc`) - 如果必须用 Codex,可能需要你这边再调一次 proxy 配置 ### 2. d412130 bug 的防御性修复 + 测试局限 **症状**:点悬浮按钮 → 显示 panel。再点 → 隐藏。再点 → **看到 2 个 prompt 同时显示**(节点累积)。 **根因**:`OpenCodeFloatingPanel` 用 `Widget.attach(prompt, this._panelEl)` 把 Lumino Widget 挂到裸 `HTMLDivElement` 上(host 不是 Widget),所以 prompt 的 Lumino `_parent` 是 null。真实 Lumino `Widget.dispose()` 在 parent=null 时走 `Widget.detach(this)` 分支按理能清 DOM,但实际有遗漏场景。 **修法**(`src/components/opencode_floating_panel.ts` 的 `_hidePrompt`): ```ts if (this._prompt) { const node = this._prompt.node; this._prompt.dispose(); if (node.parentNode === this._panelEl) { // defensive this._panelEl.removeChild(node); } this._prompt = null; } ``` **回归测试局限**:`src/__tests__/opencode_floating_panel.spec.ts` 的测试 mock `Widget.dispose()` 直接用 `node.parentNode.removeChild` 清 — **比真实 Lumino 更激进**,所以这个回归测试在 mock 下永远过。**真实 bug 只能在真实 JupyterLab 里复现和验证**。如果你看到类似累积 bug,**必须真跑 JupyterLab**,别只信 jest pass。 ### 3. 几个 `_onShellChanged` / `as any` 的取舍 - `_onShellChanged` 写成 **arrow function class field**(`= (): void => { ... }`)而不是 method,这样 `this` 锁定到实例上(Lumino signal 调用 slot 时 `this` 不可靠) - `this._app.shell as any` 出现过几次 — 因为 `JupyterFrontEnd.shell` 在不同 JupyterLab 版本里类型签名是 `JupyterShell | null`,严格模式要求 cast。可以后续更精确处理,但当前能用 ### 4. `jlpm build:prod` 没跑成功 `.venv/bin/jlpm` 是 Python 入口脚本,直接 `jlpm build:prod` 找不到。我试过 `python -m jupyter_builder.jlpm build:prod` 但也失败(参数解析对不上)。**完整 labextension 打包未验证** — 只跑了 `jest` + `tsc --noEmit` 间接确认编译能过。如果发版前要打包验证,你可能要在终端手动跑。 ### 5. `v0.1.2` 是个"假版本" 只 bump 了 `package.json` 的 version 字段(`pyproject.toml` 是 hatch dynamic,从 git tag 自动读),**没有功能变更**。发这个 tag 大概是为了占位 / 触发 CI。下次真发版时需要把 `feature/floating-ai-button` 合并到 main,然后重新打 tag。 --- ## 仍然没做的事 - [ ] 把 `feature/floating-ai-button` 合到 main(用户说"暂时不管",所以还没合) - [ ] **真实 JupyterLab + OpenCode Serve 端到端测试** — 没在活的 JupyterLab 里点过按钮。bug d412130 是用户手测报的 - [ ] **Playwright UI tests** — 项目交接文档里说写过 spec 但没跑,本次也没动 - [ ] **pre-existing lint 错清理**:`FlatProvider` → `IFlatProvider`,`BlockEntry` → `IBlockEntry`(2 处改名,小 PR) - [ ] **`jlpm build:prod`** — 没跑通(见上) - [ ] **CI 验证**:`v0.1.2` tag push 触发 CI,看一下 `.github/workflows/build.yml` 跑通没 --- ## 关键文件速查(给下任的 quick map) ``` src/components/opencode_floating_panel.ts ← 新文件,核心逻辑都在这(310+ 行) src/components/opencode_inline_prompt.ts ← 解耦 Cell,加了 getActiveCell 选项 src/__tests__/opencode_floating_panel.spec.ts ← 重写后的测试(1556 行,68 tests) src/index.ts ← 极简,只剩 activate() + 挂 floating panel style/base.css ← 找 .opencode-floating-* 块 ``` 参考架构/HTTP 细节(未变)参考本仓库 HANDOVER.md 之前章节的 [架构](#架构) / [主要路由](#主要路由) / [关键设计决策](#关键设计决策-避免来回改)。 --- ## 测试命令 ```bash # 后端 .venv/bin/pytest opencode_bridge/tests/ -q # 73 passed # 前端 ./node_modules/.bin/jest --no-coverage # 68 passed ./node_modules/.bin/tsc --noEmit # exit 0 ./node_modules/.bin/eslint --ext .ts,.tsx src/ # 仅 pre-existing 错 # 在哪个分支跑? feature/floating-ai-button 上跑过了,main 上 v0.1.2 没跑 ``` --- ## 上下文管理提示 上下文快满了。新 agent 第一件事: 1. **读这份 HANDOVER.md 全文**(包括本节之前的所有内容) 2. `git log --oneline -5` 确认 commit hash 没变 3. 跑上面 4 个验证命令 4. 如果要继续 feature 工作 → `git checkout feature/floating-ai-button` 5. 如果只是在 main 上发版 → 留在这里,准备好就 `git merge feature/floating-ai-button`(或 PR 流程) **不要**: - 不要从 main 重新开发 — 浪费时间 - 不要相信 jest 单测覆盖 d412130 那类 bug — 必须在真 JupyterLab 里点 - 不要相信 Codex 能用 — 先 noop 测一下