Initial commit: opencode_bridge JupyterLab extension (Slices 1-3.5)
A JupyterLab extension that bridges the cell UI to a local OpenCode Serve process. The extension is a dual package: a Python server extension exposed under /opencode-bridge/*, plus a TypeScript frontend that registers per-cell toolbars. Backend (Python, tornado) - Slice 1: config + auth + OpenCode HTTP client (tornado.httpclient, no aiohttp). 4 settings in schema/plugin.json (url, user, password, request timeout). - Slice 2: handlers for /hello, /health, /providers, /edit. - Slice 2.1 (correction): SessionManager with 1 notebook = 1 session mapping, async-safe via per-path locks, 404 recovery via invalidate(). Two new endpoints: GET /sessions, DELETE /session?notebook=<path>. - 32 pytest tests pass. Frontend (TypeScript, JupyterLab 4.6) - src/types.ts: CellContext, OpenCodeRequest/Response, OpenCodeSettings. - src/context/cell_context.ts: extract CellContext from a CodeCell + its parent NotebookPanel, structured error collection. - src/api/opencode_client.ts: callOpenCodeEdit, callOpenCodeProviders. - src/components/opencode_cell_footer.ts: OpenCodeCellFooter Widget implementing ICellFooter with 3 buttons (optimize / fix / edit), resolved via this.parent instanceof CodeCell. NOT cellToolbar (does not exist in JL 4.6) and NOT Widget.findParent (removed in @lumino/widgets 2.x). - src/components/opencode_cell_factory.ts: Cell.ContentFactory subclass returning the OpenCodeCellFooter. - src/components/opencode_installer.ts: installOpenCodeEverywhere patches every notebook (existing + new) to use the custom factory. - src/index.ts: registers the factory, loads settings, fetches /providers on activation and logs the list to the console. - 23 jest tests pass (mocked JupyterLab boundary, pnpm path safe). Settings - 6 fields: 3 auth (url/user/password) + 1 timeout + 2 model selection (provider/model). Provider list is fetched at startup from /opencode-bridge/providers and printed to the browser console so users can copy values into Settings Editor. Docs - design.md: 6 sections covering architecture, UI flow, API contract, TS skeletons, session management (v0.2.1 correction), and provider/model selection (v0.2.2 addition). - CLAUDE.md: agent guidance for working in this repo. - TODO.md: remaining work for Slices 4-7 + v0.4+ backlog. CI - Gitea release workflow at .github/workflows/build.yml. - Bark notification helper (non-fatal on failure). Generated artefacts ignored: opencode_bridge/labextension/, _version.py, *.tsbuildinfo, junit.xml, test.ipynb scratch notebook.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# TODO
|
||||
|
||||
剩余开发任务。按依赖顺序切片,每步可独立验证。
|
||||
|
||||
---
|
||||
|
||||
## Slice 4 — Diff 面板 + Yjs 写入
|
||||
|
||||
**目标**:把 `OpenCodeCellFooter._handleResponse` 里的 `Notification.info(...)` 替换成真正的 Diff 面板,支持 Accept / Reject / 微调,写入走 Yjs 事务保护。
|
||||
|
||||
**未决问题先回答再写代码**:
|
||||
1. Accept 后是否自动执行 cell?(建议:否,让用户自己决定;slice 5 再说)
|
||||
2. 多个 cell 同时打开 Diff 面板时如何管理?(每个 footer 实例一个面板 vs 全局单例——建议每 cell 一个)
|
||||
3. Diff 视图用什么库?JL 自带 `IDiffModel`?还是简单 `diff-match-patch`?**零新依赖**优先
|
||||
|
||||
**任务**:
|
||||
- [ ] 在 `src/components/diff_panel.ts` 新建 `DiffPanel` widget(继承 `ReactWidget` 或纯 Lumino `Widget`)
|
||||
- [ ] 接受 → `cell.model.sharedModel.transact(() => cell.model.sharedModel.setSource(finalSource), 'opencode-bridge-accept')`
|
||||
- [ ] 拒绝 → 关闭面板
|
||||
- [ ] 微调 → 进入 textarea 编辑模式
|
||||
- [ ] 在 `_handleResponse` 里替换 `Notification.info` 调用,改为打开 `DiffPanel`
|
||||
- [ ] 新增 `src/__tests__/diff_panel.spec.ts` 测试 accept/reject 路径
|
||||
|
||||
**验收**:
|
||||
- `pytest opencode_bridge/tests/` 仍 32/32 pass
|
||||
- `jest src/__tests__/` 仍 23/23+ pass(+ diff_panel 新测试)
|
||||
- `tsc --noEmit` 干净
|
||||
|
||||
---
|
||||
|
||||
## Slice 5 — Inline prompt + Edit flow
|
||||
|
||||
**目标**:替换 `opencode_cell_footer.ts:127` 里的 `window.prompt(...)`,做内嵌 prompt 框,跟 design.md §2 一致。
|
||||
|
||||
**任务**:
|
||||
- [ ] 在 `opencode_cell_footer.ts` 加内嵌 prompt 状态机:`closed → open → streaming → done / error`
|
||||
- [ ] `🪄 编辑` 按钮点击 → 状态变 `open` → footer 下方滑出 textarea + 发送/取消按钮
|
||||
- [ ] 提交 → 状态变 `streaming` → 显示进度(用现有 loading 状态) → 完成后切到 `done` → Diff 面板出现
|
||||
- [ ] 取消 → 状态回到 `closed`
|
||||
- [ ] 多个 cell 同时 open 的 prompt 互相独立(每个 footer 自己的状态)
|
||||
|
||||
**验收**:
|
||||
- 所有现有测试 pass
|
||||
- 新增 1 个状态机集成测试
|
||||
|
||||
---
|
||||
|
||||
## Slice 6 — Fix flow 完整闭环
|
||||
|
||||
**目标**:🐛 排错按钮从"灰着"变成真能用——点击后调用 `/edit` 带 `mode: 'fix'`,自动注入 traceback 到 context(后端 `_build_request_body` 已支持,前端只发就行)。
|
||||
|
||||
**任务**:
|
||||
- [ ] 验证 cell 报错后 → fix 按钮可点(已实现)
|
||||
- [ ] 点击 → 后端在 `_build_request_body` 里用 `error` 字段拼 traceback(已实现)
|
||||
- [ ] 流式回包后 → Diff 面板显示修复版代码(Slice 4 完成后顺带支持)
|
||||
- [ ] 验证 acceptance 后 `cell.model.sharedModel.setSource(...)` 后用户能直接重跑
|
||||
|
||||
**验收**:
|
||||
- `pytest opencode_bridge/tests/` 加一个 fix-mode 的 test(mock OpenCode client 验证 traceback 被拼进 parts)
|
||||
|
||||
---
|
||||
|
||||
## Slice 7 — 边界场景
|
||||
|
||||
**目标**:把所有 v0.2/v0.2.1/v0.2.2 设计里"未解决"和"边界"集中处理。
|
||||
|
||||
**任务**:
|
||||
- [ ] **OpenCode 不可达**:前端 `HealthHandler` 返回 503 → 3 个按钮全 disabled + Notification 显示
|
||||
- [ ] **401/403**:OpenCode 拒绝 Basic Auth → Notification 提示检查 `opencodeServerPassword` setting
|
||||
- [ ] **session 失效**:OpenCode 返回 404 → 后端 `EditHandler` 调用 `sm.invalidate(notebook_path)` → 下次 `get_or_create` 重建(**后端逻辑已在 v0.2.1 实现**,补前端 Notification 提示)
|
||||
- [ ] **空 notebook path**:`EditHandler` 400 + `{error: "missing context.notebookPath"}`(已实现,前端检查)
|
||||
- [ ] **长 prompt 截断**:超 4000 chars 的 prompt 提示用户
|
||||
- [ ] **provider/model 拼错**:`/config/providers` 返回 200 但 provider 不存在 → OpenCode 端报错 → 后端 `EditHandler` 502 + 错误信息透传
|
||||
- [ ] **取消进行中请求**:用户点 ✨ 之后能取消 → AbortController + `client.abort(session_id)` 调 OpenCode `/session/:id/abort`
|
||||
|
||||
**验收**:
|
||||
- 每个边界场景有一个对应的 test(pytest 或 jest,看发生在哪层)
|
||||
- Notification 文案一致
|
||||
|
||||
---
|
||||
|
||||
## v0.4+(不在本轮迭代范围内)
|
||||
|
||||
来自 design.md §5.6:
|
||||
- TTL reaper:notebook 关闭后 30 分钟 idle 自动 release session
|
||||
- 多 server 横向扩展:in-memory state → Redis / sticky session
|
||||
- 启动清理:Jupyter 重启后调 `GET /session` 列出 OpenCode 端孤儿删除
|
||||
- 并发 LLM 调用:`asyncio.Semaphore(per_session=1)` 显式控制
|
||||
- 流式 UX:v0.3 砍掉流式(同步版),v0.4 加 `prompt_async` + `/event` SSE 多路复用
|
||||
- Provider/model 选值 UX:升级为 JupyterLab command + QuickPick,替代 console.log
|
||||
|
||||
来自 design.md §6.2:
|
||||
- Settings UI dynamic enum:等 JupyterLab 支持后把 console.log 替换成真正的 settings 渲染器
|
||||
|
||||
---
|
||||
|
||||
## 当前统计
|
||||
|
||||
- **后端**:32 pytest tests(config + client + routes + session_manager)
|
||||
- **前端**:23 jest tests(types + cell_context + opencode_client + opencode_cell_footer)
|
||||
- **Schema 字段**:6(3 auth + 1 timeout + 2 provider/model)
|
||||
- **设计文档**:`design.md` v0.2.2,6 节
|
||||
- **CLAUDE.md** 已建
|
||||
|
||||
总进度:Slices 1–3.5 完成,4–7 待开发。
|
||||
Reference in New Issue
Block a user