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:
tao.chen
2026-07-22 19:07:58 +08:00
commit c919c95842
61 changed files with 26642 additions and 0 deletions
+105
View File
@@ -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 的 testmock 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 reapernotebook 关闭后 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 testsconfig + client + routes + session_manager
- **前端**23 jest teststypes + cell_context + opencode_client + opencode_cell_footer
- **Schema 字段**63 auth + 1 timeout + 2 provider/model
- **设计文档**`design.md` v0.2.26 节
- **CLAUDE.md** 已建
总进度:Slices 1–3.5 完成,47 待开发。