From f9d52f7b8e43282f13d279ac9fec2df502ad8687 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:10:20 +0800 Subject: [PATCH] docs: add HANDOVER.md for next session context The current session's context window is nearly full. This document captures the project state, the key design decisions that future maintainers should NOT re-derive, and the known minor issues that were intentionally left for a follow-up session. Contents: - Project overview + current state (tag v0.1.1, branch main @ 310d507) - Architecture diagram - Critical code locations - All 11 route endpoints in one table - 5 key design decisions (SSE filter, no cell auto-inject, multi-session model, no partial-render markdown, real interactive permission/question UI) - written so the next session doesn't accidentally undo them - How to run the tests - Known minor issues / TODO list Co-Authored-By: Claude --- HANDOVER.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 HANDOVER.md diff --git a/HANDOVER.md b/HANDOVER.md new file mode 100644 index 0000000..3a10443 --- /dev/null +++ b/HANDOVER.md @@ -0,0 +1,110 @@ +# opencode_bridge 交接文档 + +## 项目概览 +JupyterLab extension,把本地 OpenCode Serve 进程的能力桥接到 notebook 单元。 +**v0.1.1 (tag 在远端,当前 main 已经是 310d507)**。 + +## 仓库 +`/Users/taochen/llm/notebook-extension/opencode-bridge` +- 主分支:`main`(已切回) +- 远端:`http://101.43.40.124:3000/tao.chen/notebook-ai-extension.git` +- Tag:`v0.1.1` (310d507) + +## 架构 +``` +Frontend (JupyterLab TS) Server ext (Python tornado) OpenCode Serve +───────────────────────────────────── ───────────────────────────────── ───────────── +opencode_cell_actions.ts ─POST /opencode-bridge/edit──> EditHandler + per-cell AI 按钮 + inline panel _build_request_body(prompt) ──> /session/:id/prompt_async + (只发 prompt,不再自动注入 cell) ──> /session/:id/abort +opencode_inline_prompt.ts /session (list all) + 1. SessionSelector 下拉(全 OpenCode sessions) + 2. Provider + Model 选择(本地存储) + 3. History 区域(流式 markdown 渲染) + 4. "📋 插入单元格内容" 按钮(显式 opt-in) + 5. 输入框 + send/cancel + + SSE 订阅 GET /events?session= GlobalEventHandler ──proxy──> /global/event + → 5 类事件路由:text/reasoning/tool/permission.asked/question.asked + session.idle +``` + +## 关键代码位置 +| 文件 | 作用 | +|------|------| +| `src/components/opencode_cell_actions.ts` | cell 工具栏按钮 + 流编排(open/edit/SSE/permission/question/session 管理) | +| `src/components/opencode_inline_prompt.ts` | 内嵌 prompt 面板(最复杂,~1300 行) | +| `src/api/opencode_client.ts` | 8 个 session 管理 API + 4 个原有 API(edit/messages/permissions/questions/events) | +| `src/components/model_selection.ts` | (provider, model) localStorage 持久化 | +| `opencode_bridge/opencode_client.py` | HTTP 客户端,带 `send_message_async` + `stream_global_events`(SSE) + `reply_permission/question` + `list_all_sessions` | +| `opencode_bridge/routes.py` | 11 个路由 | +| `opencode_bridge/session_manager.py` | **1 notebook N sessions** 模型(`_active` + `_bound`) | +| `opencode_bridge/config.py` | OpencodeConfig Configurable(env var → `OpencodeConfig` Traitlets) | + +## 主要路由 +| 端点 | 方法 | 用途 | +|------|------|------| +| `/opencode-bridge/edit` | POST | 异步发 prompt_async(只发 prompt + context.notebookPath) | +| `/opencode-bridge/events?session=X` | GET | SSE 代理 OpenCode `/global/event` | +| `/opencode-bridge/session-messages?notebook=X` | GET | 当前 session 的消息历史 | +| `/opencode-bridge/sessions/all` | GET | OpenCode 上**所有** session(全局) | +| `/opencode-bridge/sessions/notebook?notebook=X` | GET/POST/PUT/DELETE | 绑定 + 列出 + 创建 + 删除 | +| `/opencode-bridge/sessions/active?notebook=X` | GET/PUT/DELETE | active session 操作 | +| `/opencode-bridge/permissions/:permId?session=X` | POST | 权限响应(once/always/reject) | +| `/opencode-bridge/questions/:qId/reply?session=X` | POST | 提问响应 | + +## 关键设计决策(避免来回改) + +### 1. SSE 事件路由(`applyEvent`) +- 客户端**不**对内容事件(text/reasoning/tool/idle)做 sessionID 过滤 — 服务端 `?session=` 参数是唯一权威 +- 只对 `permission.asked` / `question.asked` 严格过滤,防止用户误答别 session 的提示 +- 这条规则是 **bug fix 310d507 的核心**,改之前会"切 session 后发消息无回复" + +### 2. Cell 上下文不再自动注入 +- `OpenCodeRequest.context` 收缩为 `{notebookPath}` — 只告诉服务端哪个 notebook +- `_build_request_body` 只发 `parts=[{text: prompt}]` +- 用户通过"📋 插入单元格内容"按钮**显式 opt-in** 把 cell 源码作为 markdown code block 插入 +- 理由:LLM context 严格匹配用户意图,避免被前一个 cell 的 traceback 污染 +- 这是 310d507 的第二部分 + +### 3. 1 notebook N sessions +- `SessionManager._active` (dict[notebook] → active sessionId) + `_bound` (dict[notebook] → [sessionId, ...]) +- 新方法:`create_new / bind_existing / set_active / unbind / unbind_active / delete_session / list_sessions_for_notebook / get_active` +- `release(notebook)` **删所有** bound sessions(不只是 active) +- `delete_session(notebook, sid)`:先调 OpenCode delete,成功后再 unbind(失败时 retry 仍可能) + +### 4. 流式 markdown 渲染(避免 partial-render flicker) +- 流式期间只 `textContent += delta`,**不**调 `marked.parse`(否则 fence 没闭合时的部分渲染会闪 toolbar) +- `session.idle` 时 `_resetStreamPointers` 触发**一次**完整 `marked.parse` 渲染 +- 终态与 `setMessages` 路径(setMessages 重新打开时拉的)完全一致 + +### 5. 交互 UI 权限 +- `permission.asked` / `question.asked` 事件渲染**真实交互组件**(按钮 / 输入框) +- 点击通过后端路由代理回 OpenCode Serve +- 后端路由:`PUT /session/:sid/permissions/:permId` + `PUT /session/:sid/question/:qId/reply` + +## 测试 +- 后端:73 pytest(主要看 `test_routes.py` + `test_session_manager.py`) +- 前端:72 jest(`opencode_cell_actions.spec.ts` 是核心,900+ 行覆盖了 SessionSelector / 流式渲染 / permission/question 交互) +- 命令: `source .venv/bin/activate && jlpm test && python -m pytest opencode_bridge/tests/` + +## 已知小问题 / 待优化 +1. **多 session 切换**时,前端用 PUT `/sessions/notebook` 一个调用搞定(bind + set active 都做),不再用 `/sessions/active` — 后者保留供"已 bound sessions 之间切换"用 +2. `cell_context.ts` 已删,如果新增"只想要 cellId"的功能,需要重建 +3. `package.json` version 是 `0.1.1`,跟远端 tag 一致(之前是用户手工 bump 上去的) +4. `UNIFIED_SYSTEM_PROMPT` 还在后端 routes.py 里,描述 LLM 的角色("你是一个代码助手...")。如果改 prompt,会同时影响所有 prompt + +## 修改后的交互模式 +1. 用户在 cell 工具栏点 🪄 按钮 +2. 内嵌 prompt 出现:SessionSelector(列出 OpenCode 所有 session) + Provider/Model + history + "📋 插入单元格内容" + 输入框 + send/cancel +3. 用户输入 prompt,**可选**按"📋 插入单元格内容"把 cell 源码作为 code block 追加 +4. send → 后端 `prompt_async` 立即返回 `{ok, sessionId}` + 订阅 SSE +5. 流式事件实时渲染,`session.idle` 触发 markdown 渲染(终态正确) +6. permission/question 事件渲染交互 UI,点击代理回 OpenCode +7. 切换 session → 关闭旧 SSE + bind 新 session(后端 PUT `/sessions/notebook`)+ reload history + +## 还没做过的事(如果新会话要继续) +- UI tests (Playwright) — 写了 spec 但没真跑 +- 持久化 session 选择(目前只持久化 provider/model) +- cell 类型推断优化(目前用 `model.type`,真实 cell 应该有 metadata.language) +- 后端 session TTL / cleanup(目前不删,除非用户显式 release) +- 真实 OpenCode Serve 接入测试(目前用 mock)