Skip to content

vscode: reopen sessions, attach pictures, send a selection at once (refs #20) - #54

Merged
blockmandev merged 2 commits into
mainfrom
feat/vscode-sessions-images
Oct 22, 2026
Merged

blockmandev merged 2 commits into
mainfrom
feat/vscode-sessions-images

Conversation

@blockmandev

Copy link
Copy Markdown
Member

Three code items of #20 for the VS Code extension (editors/vscode).

New:

  • Open Session: incode: Open Session…, plus a title-bar button shown only when the agent offers session/list and session/load. It lists the workspace's sessions, reopens the one you pick, and shows its replayed history.
  • Pictures: paste, drop or pick PNG/JPEG pictures. They show as thumbnails you can remove, and are sent as ACP image blocks, only to an agent that advertises images.
    • Bounds are incode's own: 3,750,000 bytes per picture, 4 per prompt, 5 MiB together, so the prompt's single JSON-RPC line stays under incode's 8 MiB cap.
    • The type is recognised from the file's bytes, and checked again in the extension whatever the page did.
  • incode.sendSelectionImmediately (default off): Send Selection sends at once.

Fixed after an independent review (second commit):

  • A refused send keeps your draft.
  • New Session during a load really starts a new session.
  • A restart can't revive the dead process's session id.
  • A failed load says so and clears its partial history.
  • Reopening is refused while a turn runs.
  • Two quick sends at startup can't both go through.

Security:

  • The CSP only adds data: to img-src, for the thumbnails. Scripts stay nonce-only, and connect-src stays 'none'.
  • The page is still built with createElement/textContent only.
  • The page can't name a file for the extension to read: Image opens the editor's file dialog.

Tests: 91/91 against the fake agent, and 93/93 with the live tests against a real incode (lists and loads a real transcript, and has it decode an image block). Each fix has a control: the code broken, the test failing on its intended assertion. The controller tests now also run on Windows instead of being skipped.

Not verified: nothing has run in a real VS Code window yet. Drag-drop, paste and the file dialog were exercised only in a fake DOM.

…ce (refs #20)

incode advertises loadSession, sessionCapabilities.list and
promptCapabilities.image, but the extension used none of them: a session
could not be found or reopened once the window moved on, a screenshot could
not go with a question, and Send Selection could only fill the input.

Open Session. `incode: Open Session…` (and a title-bar button, shown only
when the agent offers both session/list and session/load) lists the
workspace's sessions, following nextCursor, in a QuickPick with title and
last update, and loads the pick. The current session is closed first and
waited for, because incode refuses to load a session still open on the
connection. The loaded id is routed while the request is in flight, since
incode replays the history BEFORE it answers; without that the replay was
buffered and shown after the answer, or dropped past the early-update cap.

Pictures. Paste, drop or the Image button (the editor's file dialog) attach
PNG/JPEG pictures, shown as thumbnails with a remove control, sent as ACP
image blocks beside the text, only to an agent advertising image. PNG and
JPEG only, recognised from their bytes, because those are what incode
accepts. Bounds are incode's: 3 750 000 bytes per picture (its
MAX_IMAGE_BYTES), 4 per prompt, and 5 MiB together so the prompt's one
JSON-RPC line stays under incode's 8 MiB cap. The page refuses a file before
reading it; src/images.ts checks everything again in the extension. The
bytes never enter the view's replay. The CSP gains `data:` in img-src only,
for the thumbnails.

incode.sendSelectionImmediately (default false) makes Send Selection send at
once; while a turn runs the selection still goes to the input.

Tests: the fake agent gains session/list (two pages), session/load with a
replay before the answer and the already-open refusal, image prompts, and
--no-reopen / --no-image. New tests cover the controller paths and their
negatives, extension activation against package.json, chat.js in a fake DOM,
and the image rules at each bound; the live test lists and loads a
transcript with the real binary and has it decode an image block. The
controller tests now start the fixture with node, so they run on Windows
instead of being skipped there.
… races (refs #20)

Review of f62dbcc found six defects in src/controller.ts and media/chat.js:

- The page cleared the input and pictures on send, so every refusal in the
  controller (a turn running, a picture failing the byte check, no session,
  an agent without images) lost the draft; a session that could not be
  opened refused silently. A send now carries a draft id and is answered
  `sent` or `notSent` (with the reason) on every path; the page keeps its
  own copy and restores it on notSent unless something new was typed. No
  image bytes travel back. Send Selection with sendSelectionImmediately
  falls back to the input on any refusal.
- New Session during a load returned the in-flight load as the "new"
  session. It now waits for the load to settle, then closes it like any
  other session.
- A restart during a load let the dead process's late answer set the new
  process's session id. Each openSession call has a token; a restart or exit
  abandons it, and every continuation checks it and the agent before
  writing the session, caps or replay. Updates from a replaced process are
  ignored.
- A failed load left part of a history and no session, silently. It now
  clears the view and says the next message starts a new session (the
  previous session is not reopened: it was closed, and a second load could
  fail the same way).
- Reopening during a turn put the old turn's "cancelled" in the reopened
  session. Reopening is refused while a turn runs; a turn cancelled by New
  Session ends the spinner without its stop reason.
- Two sends during startup both got through, because the turn was claimed
  after awaiting the session. It is claimed before the first await and
  released on every refusal, with a claim number so a stale release after a
  restart frees nothing.

The fake agent gains a slow load (answered before exit when told to stop),
a load that fails mid-replay, unique session ids per session/new, and
`whoami`.
@blockmandev
blockmandev merged commit 524013b into main Oct 22, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant