Commit Graph
9 Commits
Author SHA1 Message Date
b7089519fd feat: async + SSE message flow with interactive permission/question UI
Replace the synchronous /edit (wait for full markdown response) with an
async + SSE flow per demo.html so the user sees text stream in real-time
and can interact with permission/question events the agent raises.

Backend
-------
- EditHandler now calls OpenCode /session/:id/prompt_async and returns
  immediately with {ok, sessionId, notebookPath}; the LLM reply is no
  longer embedded in this response.
- New GlobalEventHandler proxies OpenCode /global/event as
  text/event-stream. Server forwards ALL events; the client filters.
  A too-eager server-side ?session= filter was silently dropping events
  the client would have accepted, so it was removed.
- New PermissionReplyHandler + QuestionReplyHandler forward user
  replies (once/always/reject and freeform answer) back to OpenCode
  Serve at /session/:sid/permissions/:permId and
  /session/:sid/question/:qId/reply.
- OpenCodeClient gains send_message_async(), stream_global_events()
  (async generator over the SSE feed via tornado streaming_callback),
  reply_permission() and reply_question(). Legacy send_message_sync
  removed.

Frontend
--------
- subscribeOpenCodeEvents() opens a fetch+reader SSE client (XSRF
  token injected from serverSettings); AbortController-backed close()
  is idempotent.
- OpenCodeInlinePrompt.applyEvent() routes events into the streaming
  UI: text delta -> assistant message (re-rendered as markdown on
  every delta so the user sees formatted <pre><code> blocks in
  real-time, not raw fence source); reasoning/tool/permission/question
  get collapsible details blocks. session.idle resets stream pointers
  and fires onStreamEnd.
- Permission/question blocks render real interactive UI: three
  buttons (once/always/reject) for permission, a text input + submit
  for question. Click handlers post through the new API routes and
  show success/failure status in place.
- Centralized idle detection (4 shapes: top-level + payload-nested
  x {session.idle, session.status/idle}) inside the prompt; cell
  action reacts via onStreamEnd callback rather than re-parsing
  event types.
- System / workspace / pty / lsp / mcp / installation events AND
  session-level control events (agent.switched, model.switched,
  file.edited) are not rendered in the frontend.

Tests
-----
- 52 backend pytest (was 43)
- 58 frontend jest (was 46)

design.md section 3 updated for the new async /edit response shape
and the new GET /events SSE endpoint.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 17:38:53 +08:00
tao.chenandtao.chen a43174e5bc update: optimize config.py 2026-07-27 17:38:53 +08:00
tao.chenandClaude Fable 5 ce59502e97 feat: inline prompt history area — scrollable session messages
The inline prompt now shows the current session's full message
history (user + assistant), scrollable, replacing the single-output
display from the previous commit. The user can scroll back through
prior exchanges in the same notebook's OpenCode session.

Server (opencode_bridge/):
  - New route: GET /opencode-bridge/session-messages?notebook=<path>
    - Resolves notebook to session via SessionManager.peek (no create).
    - If no session: {"messages": []} (no OpenCode call).
    - Else: GET /session/{id}/message on OpenCode Serve, project the
      raw {info, parts}[] into a frontend-friendly {role, content}[].
  - OpenCodeClient.list_session_messages(sid).
  - SessionManager.peek(notebook_path) -> Optional[str] (read without
    creating, to avoid spawning a session just to report emptiness).
  - Wired the new route in setup_route_handlers.

Client (src/):
  - types.ts: OpenCodeMessage { role, content } + OpenCodeMessagesResponse.
  - api/opencode_client.ts: callOpenCodeSessionMessages(notebook, serverSettings).
  - components/opencode_inline_prompt.ts:
    - Replaces the single .opencode-inline-output area with a
      scrollable .opencode-inline-history (max-height 320px, overflow-y
      auto, auto-scrolls to bottom on update).
    - setMessages(messages): renders user messages as plain text,
      assistant messages via marked.parse. No more setOutput/hideOutput.
  - components/opencode_cell_actions.ts:
    - _showPrompt now fires a _refreshHistory(notebookPath) which
      fetches and calls prompt.setMessages.
    - _handleResponse on success also calls _refreshHistory (the new
      assistant message appears as the last item in the history).
    - Cell source is still NOT replaced.
  - api/opencode_client module is mocked in the cell_actions test to
    avoid jsdom network calls.

style/base.css:
  - .opencode-inline-output* rules replaced with .opencode-inline-history
    (max-height 320px, overflow-y auto, border, padding) and
    .opencode-msg / .opencode-msg-user / .opencode-msg-assistant.
  - pre/code/p/h1-3 content styling scoped under .opencode-inline-history.

Tests:
  - pytest 37/37: FakeOpenCodeClient.list_session_messages + 3 new
    session_messages route tests (no session, projects messages, 400).
  - jest 29/29: setMessages renders user/assistant + history area tests.
  - FakeSessionManager.peek (returns None when session_id is falsy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:45:03 +08:00
tao.chenandClaude Fable 5 1d7f5da9d4 feat: inline markdown output box — render AI reply with marked, do not replace cell
Flow (v3-final corrected):
  1. Model call succeeds.
  2. Server passes the AI reply through unchanged as 'markdown' (the
     system prompt allows ```language fences + a brief explanation, so
     the response is real markdown that marked can render into code
     blocks, headings, etc.).
  3. Client OpenCodeCellActions._handleResponse calls prompt.setOutput(
     resp.markdown); the inline prompt widget renders it with
     marked.parse and shows it in a new output area (hidden until a
     response arrives). The cell source is NOT replaced.
  4. User can close the output or cancel the whole prompt.

Server:
  - New unified system prompt: '你是代码助手 ... 按指令修改代码,可附简
    短说明' (allows ```fences``` + explanation; no more 'no markdown
    fences' restriction).
  - EditHandler returns {ok, markdown, sessionId, notebookPath} (raw
    text, fences intact). _strip_code_fence kept as a helper for any
    future apply-to-cell path; no longer called.
  - finalSource field dropped (the cell-apply path is gone).

Client:
  - OpenCodeSuccess: markdown: string (finalSource removed).
  - OpenCodeInlinePrompt: new output area, setOutput(md) renders via
    marked.parse, hideOutput() closes it.
  - OpenCodeCellActions._handleResponse: setOutput(markdown) instead of
    sharedModel.setSource + auto-hide. The prompt stays open so the
    user can read the output.
  - Uses marked@17 (already in node_modules via JupyterLab; no new dep).
  - CSS: output area styling (border, max-height 320px scroll, code/pre
    styling, close button).

Tests: pytest 34/34, jest 29/29. marked is mocked in jest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:29:58 +08:00
tao.chenandClaude Fable 5 b6c0273ee0 test: cover auth-on-request and close() in OpenCodeClient
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:40:58 +08:00
tao.chenandClaude Fable 5 c86de1d31b fix: set Basic Auth on HTTPRequest, not as fetch kwargs; close client cleanly
tornado rejects fetch(request, **kwargs) when the first arg is already an
HTTPRequest and the kwargs overlap request construction (auth_username /
auth_password). Move auth onto the HTTPRequest. Add OpenCodeClient.close()
to release its AsyncHTTPClient; module-level atexit closes the tornado
singleton so 'jupyter lab' can exit cleanly after SIGINT instead of
hanging on 'received signal 2, stopping'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:40:57 +08:00
tao.chen f9b93906c6 refactor: move all server config to startup env vars; drop plugin settings 2026-07-23 13:21:31 +08:00
tao.chen 42ea857313 refactor: drop mode from backend; unify system prompt 2026-07-23 12:31:28 +08:00
tao.chen c919c95842 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.
2026-07-22 19:07:58 +08:00