description, kind
| description | kind |
|---|---|
| GUI panel for the change requests a Session creates, and a worktree option for new Sessions, with the Host Remote that serves them. | package-reference |
@deepseek-ai/dsh-experimental-client-ui-forge
English | 中文
Summary
This optional package shows a Session's created or pushed change requests and their changes, and starts new Sessions in their own Git worktrees. The Host forgePanel Remote inspects the checkout using the Session's permissions, checks code host sign-in, streams bound change requests with their polled status for one Session or for the Session list, reads diffs, and creates a requested worktree while the first message waits in the Session's inbox. The Client adds a sidebar tab, a changes view, header entries, a New Session option, a creation card, Session list icons, and toasts.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
Enable the forge bundle on the Plugins page; it composes this package with the forge registry, the GitHub provider, the Session bindings, and its own worktree service.
Panel text follows the application language: Chinese, English, Japanese, Spanish, French, German, Russian, or Portuguese.
- Worktree option — on the New Session screen, New worktree sits beside the current branch at the right end of the row with the workspace and mode pickers. Both stay hidden outside a Git checkout. The branch is read-only until New worktree is checked, when it becomes the base branch selector; the menu lists recent local branches. If permissions block an existing request, its selected base stays visible without a selector and the request can still be withdrawn. Hovering, focusing, or tapping the read-only branch or base shows its full description. The option is disabled, with the reason in its tooltip, in a read-only Session and when the checkout or its Git directory lies outside the Session's writable root, as for a project that is itself a linked worktree. Sending the first message hands it to the Session at once, and it waits there while the branch
dsh-<first 8 hexadecimal digits of the Session id>-<4 random hexadecimal digits>is created from the base under the repository's.agents/worktrees/and its checkout becomes the Session's working directory; the message's turn starts once the worktree exists. - Creation card — while the worktree is created, a card above the composer shows the new branch and its two steps, creating the branch and checking out files; More details adds the branch and base. Cancel stops the attempt. After a stopped or failed attempt the message keeps waiting, and the card shows the reason of a failure: Retry tries again with a new branch name, and Continue without a worktree withdraws the request so the message goes on in the current checkout.
- Removed worktree — when the worktree the Session works in has been removed outside the Session, a card above the composer says so, and a message sent meanwhile waits without starting its turn until the worktree is restored or left; the card then says that the message goes on afterwards. Restore worktree, also offered as Restore in the tab, checks the branch out again at the same directory, or creates the branch again at the commit it started from when the branch is gone too; while it runs, the card follows its two steps, preparing the worktree and checking out files, and the tab's Restore turns into a spinner. Continue in the project moves the Session to the source checkout. Meanwhile the tab's first row shows a warning glyph.
- Code review and changes — on the right-sidebar start page, opens the same change request tab as the Session header button.
- Header button — appears when the Session has bound change requests or its directory is inside a Git checkout, and shows or hides the tab. A red dot marks an open change request with failed checks, requested changes, or conflicts.
- Session list — after a Session row's time, a branch glyph shows while the Session works in the worktree the panel created for it, and a glyph shows the state of its most urgent change request: one needing attention first, then open, draft, not yet polled, merged, and closed. The glyph of an open change request keeps the row's subdued color with a dot for running, passed, or failed checks; merged and closed ones keep their state colors. The row's hover card names the worktree branch and what it started from, then up to three change requests with their number, title, state, and checks, and how many more there are. A restart shows the states known before it until the next poll.
- Tab — the first row names the checked-out branch, or the abbreviated commit on a detached HEAD, after a folder glyph for the project directory or a branch glyph for a worktree, and the kind of checkout, which is the worktree the Session created with its base, another worktree, or the project directory; its tooltip adds the path with the Host home shortened to
~, its button copies the full path, and for a removed worktree it offers Restore. Below it, each change request card shows a glyph for its state, its title, which opens it in the browser, and its number; the glyph tells open, draft, merged, and closed apart, and marks an open one that is not a draft with failed checks, requested changes, or conflicts as needing attention, as the header button's red dot does. The card's header also offers Open in browser, Copy change request link, and Remove from this Session, whose toast offers Undo. Cancelled checks have a separate count; a collapsed card names cancellations when no listed check failed. A collapsed card adds the checks of an open change request and the most urgent of requested changes, conflicts, or draft, or when a merged or closed one ended. An expanded card shows the head and base branches, then one row per fact: for an open change request the checks, whose row opens the list of checks with failed ones first and their durations and links, the review decision with unresolved threads, and the merge state, or when a merged or closed one ended; and the changed lines, formatted in the selected UI language, whose row opens the changes view. An open draft also offers Ready for review; a change request the host would merge now offers Merge, whose menu lists the methods the repository allows with the user's usual one chosen and pins the displayed head when opened. Confirm merge uses that head; a polled head change disables confirmation until the menu is reopened after review. A toast reports each outcome, and new commits since that head stop the merge. When exactly one bound change request is open, its card starts expanded. Refresh inspects the checkout, checks sign-in, and polls every change request again; its icon turns until the inspection and the sign-in check end. A banner shows thegh auth logincommand when sign-in is missing. - Local changes card — while the
branchscope changes files and no open change request has the checked-out branch as its head, a card above the change requests names the ref it compares with, such asHEAD → origin/main, and shows the added and deleted lines and the number of files, which open the changes view; its file list, which names files as the changes view does, opens the view at one file. Create change request, offered once the code host repository is recognized, sends the Session a user message in the UI language that asks the Agent to create a change request for these changes, and a toast reports whether it was sent. - Changes view — The Changes row of an expanded change request card and the local changes card open a right-sidebar tab that lists the changed files. The local tab is named Local changes and shows Branch by default: the working tree and untracked files against the fork point, which is the base commit of the worktree the Session created, else the merge base of HEAD with the default branch of the code host remote. Uncommitted compares them with HEAD, Unstaged with the index, and Staged compares the index with HEAD. A change request view shows the host's diff at its head and says how many commits the checked-out branch has beyond that head when it is the change request's head branch. The toolbar jumps to a file, totals the changed lines, refreshes, and switches between two columns and one and between wrapped and cut long lines. Visited changes tabs stay mounted across tab and Session switches, hiding the sidebar, and docking changes, preserving file folds, revealed large files, expanded context, layout, and scroll position while the diff is unchanged. Hidden changes views release their panel subscription and defer diff reads; returning reads again only when the latest head or local summary differs. A refresh that replaces the diff discards expanded context, and changed content may move the reading position. Temporary menus close when the view is hidden. Closing the tab or reloading the application discards this progress. Files are named before their directories, which use smaller subdued type and shorten first; hovering one shows its complete path, and both paths for a renamed file. Each file shows badges for a new, deleted, or renamed file, the last naming the old file name, or the old path when only the directory changed, and, in the Branch scope, an untracked one, and a menu that copies its path and opens the whole file in the sidebar for local changes or its page at the head in the browser for a change request. Unchanged lines between hunks collapse into rows that expand from the file's new side: the working tree, the index for Staged, or the head commit through the code host; a file that changed after the diff was read stays collapsed until the view reads again. A binary file and an untracked file over
maxDiffBytesshow a note instead of their lines, a file whose hunks draw more than 1,000 lines starts as one collapsed strip that names its changed lines and draws them when opened, and a file below files that change more than 5,000 lines starts collapsed. The view reads again on Refresh and the page refresh shortcut, on each navigation, when the change request's head moves, and when the Branch scope's summary changes; a change request's view first waits for the status that names its head, unless no provider serves it or that poll failed. While a read replaces changes already shown, the Refresh icon turns. A change request whose diff the code host or its provider refuses over a size limit, such as a GitHub diff over 300 files, says its changes are too large to show here and offers View in browser; another failed read offers Retry and, for a change request, View in browser. A change request's diff is downloaded once per head commit and base branch while it stays cached, so reopening, refreshing, and navigating at the same head read nothing from the host.
The checks row initially shows the polling preview. While it is incomplete, the card uses the host’s aggregate outcome without a failure count and labels the loaded check count as partial. Opening the list loads the remaining pages for that PR through changeRequestChecks; its deadline is independent of polling. A failed read keeps the preview and offers Retry. Closing the list, changing the head, or unmounting cancels the read; late results are ignored. Reopening reads current details. The Remote requires a visible Session binding and rejects a response for a different head commit.
Click the diff content to focus it. Left and Right move to the previous and next file in diff order and expand it; navigation stops at the first and last file. Up and Down keep their normal scrolling behavior. After manual scrolling, file navigation starts from the file at the top of the viewport. Text editing, non-collapsed text selections, modified keys, menus, and file-tree controls keep their own keyboard behavior.
gitCommand, gitTimeoutMs, graceMs, maxOutputBytes, readinessTimeoutMs, maxBranches, worktreePrefix, maxDiffBytes, maxUntrackedFiles, and maxCachedDiffBytes are listed in the configuration catalog.
All files opens a searchable directory tree. Single-child directories share a row, and renamed files also match their old path. A wide changes pane shows the same tree beside the diff; its File tree toolbar button cycles through left, right, and hidden, starting on the left. Its accessible name describes the next action, and focus stays on the button when it moves or hides the tree. The search, folds, selection, and width survive moving or hiding the tree and switching tabs or Sessions. Drag the divider or use its arrow keys to resize the tree. Files and folders reserve the same disclosure column so each child is indented from its parent. Local changes and each PR retain separate tabs. Select code text or use line numbers to select a range within one displayed hunk and side; text selection and copying stay on the starting side. The plus button opens the plain-text comment editor. It accepts newlines and IME composition; only its action buttons submit the comment. Drag its lower edge to adjust the height. Add to input inserts a removable quote into the main composer; multiple quotes are sent together only when the user sends that composer. The GitHub split button starts or adds to a pending review; its menu publishes a single comment immediately. Comments remain beside their code after quoting, queueing, or publishing. Pending comments are edited or removed there. Request changes requires a review summary; the dialog explains this beside the summary field and announces the requirement when the decision changes. Submit review in the PR title bar opens a centered dialog showing only the pending count, summary, and Comment, Approve, or Request changes. Added and deleted files show only their populated line-number column. Pending reviews and comment records are stored in this browser per Session and tab, including the original PR head commit. Switching Sessions or hiding the pane retains them; closing the tab clears its comments, summary, and decision, and reopening starts an empty review. Published GitHub comments and references already added to the main composer are unaffected. Records whose code changed remain visible below their file, or after the diff when the file is absent. A changed head requires fresh line selections. While the tab remains open, an uncertain write stays blocked across reloads until the user checks GitHub and explicitly permits another submission.
Understand the implementation
Implementation internals — click to expand
ForgePanelController keeps one SessionPanel per followed Session and disposes it when the last follower leaves. A panel starts with the Session's working directory and bindings, re-inspects after each working-directory/change, tool/result, and sandbox/mode event, and publishes a new ForgePanelState for every change. Each change request keeps the latest snapshot a poll returned while later polls fail. A failed parallel Git read cancels and joins its siblings. Inspections run one at a time with one trailing rerun, and a result for a directory the Session has since left is dropped. The code host comes from the remotes in order: the upstream's remote, then origin, then the rest. When an inspection finds an upstream branch on a remote a provider recognizes, the panel asks ctx.forgeSession.discoverBranch to bind that branch's open change requests, once per remote and branch until a refresh. A sign-in probe runs when the recognized host changes and on refresh, within readinessTimeoutMs; a refresh settles once the inspection and the probe it started have ended, and poll results arrive later.
Inspection runs rev-parse --show-toplevel, rev-parse --path-format=absolute --git-common-dir, rev-parse --path-format=absolute --git-dir, symbolic-ref --short HEAD, rev-parse --short HEAD, rev-parse --abbrev-ref @{upstream}, and remote -v; branches runs symbolic-ref and for-each-ref --count=<maxBranches>. Inspection then sizes the branch scope for the local changes card: it finds the fork point, runs diff --numstat -z -M against it with the same helper and submodule restrictions as localDiff below, and lists untracked files. Every command uses the Session's resolved sandbox mode: read-only and workspace-write require confinement, while danger-full-access runs without a sandbox backend. Commands disable optional index writes, hooks, the filesystem monitor, prompts, and lazy object fetches, remove inherited Git selectors and secret-bearing variables, and use their own gitTimeoutMs deadline and maxOutputBytes limit per stream. The top-level, Git, and common Git directories Git prints are resolved through ctx.fs into the canonical process paths that the working-directory service records; a checkout whose Git directory differs from the common one is a linked worktree. The worktree option is usable under the danger-full-access sandbox mode, never under read-only, and under workspace-write only when both directories lie inside the canonical workspace root of the Session's sandbox policy.
A worktree request is a plugin:client-ui-forge/worktree record, folded by the forgeWorktree projection: requests and withdrawals apply until the first turn/start or creation, and the first created record of an open request applies until the first step/start. The projection also records the Session's directory from its header and each working-directory/change; its client view, which the Session summary carries, is the created worktree while that directory is the worktree or lies below it, else null. requestWorktree refuses with forgePanel/session-started after a turn started or the worktree was created, with forgePanel/worktrees-unavailable without a worktrees service, and with forgePanel/invalid-branch for an empty branch; cancelWorktree refuses only with the first and needs no worktrees service; both forget a failed attempt. When a message enters the inbox of a Session whose request is open, an agent/inbox/inserted listener starts maintenance of the Session's Agent before the delivery wakes it, so the Agent latches the wake and starts the message's turn once that hold ends. The hold makes one attempt with a new branch name, publishing the running stage in the panel state with held set and waiting as creation, and records created with the source repository root. After a failed attempt, whose branch, base, failed step, and reason stay in the panel state as failure, or after stopCreation, the hold waits: retryWorktree makes another attempt, refused with forgePanel/not-waiting while none waits or one runs, and withdrawing the request lets the message go on in the current checkout. Each message that enters while the Agent is idle and the Session works in its created worktree starts a hold that checks whether the worktree is missing; while it is, the hold waits with waiting as restoration, and restoreWorktree or continueInProject runs inside it and then lets the message go on; a check that cannot answer lets the message go at once. The Agent's cancellation and the Remote's stop end a hold, and a message that enters while the Agent runs a turn or other maintenance gets none. The worktree service queues the working-directory notice, which the first step claims. An agent/pre-step listener creates the worktree at step 1 of a Session whose request is still open, which covers a message no hold took; it moves the notice into the proposed messages, and a failure ends the turn with the request kept. restoreWorktree and continueInProject run as maintenance of their own while no message waits; restoreWorktree publishes its running step as restoration, and clears missing in the same state that ends it. The bundle isolates its worktrees service, so enabling Git Worktrees as well leaves two independent services. track rejects a URL no provider recognizes with forgePanel/unrecognized-url. markReady and merge act only on a visible binding of the Session that a provider serves, else they refuse with forgePanel/not-bound; merge passes the head commit the card showed, and a moved head refuses with forgePanel/stale-head, while any other refusal is forgePanel/action-failed with the host's reason.
Restoration records restoring before mutation and restored only after success. Projection version 3 retains incomplete restoration across reloads. Directory existence alone cannot release a waiting message: the checkout root and shared Git directory must match the recorded worktree. Plugin unload cancels and joins all creation, restoration, and waiting tasks before releasing panel readers and the projection.
Each inspection also decides whether the worktree is missing: the Session's directory lies inside the created worktree, ctx.fs.lstat finds no directory there, and show-ref in the source repository tells whether the branch still exists.
localDiff takes the fork point from the created worktree's baseCommit when the Session works in it, else, for each remote in the code host order, from symbolic-ref --quiet --short refs/remotes/<remote>/HEAD and merge-base HEAD; without one the branch scope refuses with forgePanel/no-fork-point. It runs diff --no-ext-diff --no-textconv --submodule=short --ignore-submodules=dirty --no-color -M with fixed a/ and b/ prefixes and core.quotePath=false against the fork point, against HEAD (the empty tree before the first commit), against the index, or with --cached against HEAD. Before patches and statistics, Git configuration is read to apply command-local clean/process filter overrides. A failed or truncated configuration read stops the comparison: localDiff reports forgePanel/filter-config-failed, with a retry after correcting Git configuration or the output limit; failed statistics leave the local changes card hidden. Filter enumeration uses maxDiffBytes for patches and maxOutputBytes for statistics. Untracked files, at most maxUntrackedFiles with the rest counted as omitted, come from ls-files --others --exclude-standard -z and are read through ctx.fs as new files, binary when their first 8,000 bytes hold a NUL byte or they are not UTF-8. A patch, file list, or file over maxDiffBytes refuses with forgePanel/diff-too-large, and a directory outside a checkout with forgePanel/not-a-checkout. The Host parses patches with jsdiff into files and hunks, so the browser receives no raw patch. localText reads the new side of one file, from the index through show :<path> for Staged and through ctx.fs otherwise, and refuses a path that leaves the checkout. changeRequestDiff and changeRequestText act only on a visible binding a provider serves, else they refuse with forgePanel/not-bound; they read the head commit of the binding's latest status through the provider's diff and fileText, refuse a too-large failure with forgePanel/diff-too-large, and refuse with forgePanel/diff-failed before a status was read or when the host refuses otherwise. The Host keeps the patches changeRequestDiff read, one per change request at its head commit and base branch, and drops the least recently read once they exceed maxCachedDiffBytes; a later read at the same head and base answers from memory for any Session, and a failed read is not kept. The commits the checkout has beyond the head come from symbolic-ref, cat-file -e, and rev-list --count, and are null when the checked-out branch is not the head branch, the head commit is absent, or Git fails.
changeRequestStates streams, for the Session list, the last known number, title, state, draft flag, checks rollup, review decision, and mergeability of every change request that a listed Session the caller may read binds: a baseline frame, then changed frames. One run serves every stream. Cancellation is observed before initialization and between frames; the last stream and plugin disposal await initialization and accepted writes, then close the run’s storage domain. A later run waits for that closure before reopening the domain and reading its stored states. Binding updates received during listing override its initial snapshot. It lists Sessions through sessionQuery without loading them, takes the visible bindings of a loaded Session from ctx.forgeSession and of another from its cached forgeChangeRequests summary, follows binding changes of loaded Sessions, and subscribes to each bound change request through ctx.forge.watch; closed requests use the idle polling cadence, and merged requests remain subscribed for explicit refresh. It stores every changed state in the forge_panel_change_requests storage domain under the SHA-256 digest of its URL and drops unbound stored states only when every listed Session has known bindings; a failed write is logged and the state stays in memory. The stream fails when the Host lacks the Session query, projection cache, storage, or Session store service.
The browser entry mounts the Remote, then the UI. One stream per Session feeds the tab, the header button, the New Session option, and the creation card; it opens with the first subscriber, closes with the last, reopens after a transport reset, and keeps the last state while the Session scope is retained. Releasing that scope closes the stream and removes its cached state. The New Session option fills conversation.hero.options and the worktree card conversation.input.dock. The worktree card shows a running creation, with Cancel calling stopCreation while a message waits for it, or the failed or stopped attempt a message waits after, with Retry calling retryWorktree and Continue without a worktree calling cancelWorktree. The tab shortens paths with the Host home from the Remote's Host facts, read again after each transport reset. The tab reports its visibility to the header button's pressed state. The header button collapses a docked tab or closes its floating tab. The toast lives in a shell.overlay entry so it outlives the tab. One stream of change request states feeds the Session row icons in sidebar.session.row.trailing and the hover card section in sidebar.session.row.hover while any of them is mounted; both take a Session's worktree and bindings from its Session list summary values. The change request card takes a change request, its snapshot, and optional actions, so another surface can reuse it. The changes view is the forge-changes resource tab type: its addresses, dsh-resource://forge-changes/<session>/local and dsh-resource://forge-changes/<session>/change/<url>, name the source, and a navigation may name a file to scroll to. The tab type enables keepMounted, retaining visited bodies and their owning Session references until their tab records end. Its keyed panel hook subscribes only for the visible body; other panel surfaces may keep the shared Session stream open. Its body keeps the scope and layout choices of each tab in a slot store, reads through the Remote, and highlights lines with the shared code highlighter; a chip title shows its name with the changes icon.
| Source | Responsibility |
|---|---|
src/index.ts |
forgePanel Remote and per-Session panels |
src/panel.ts |
Panel state, inspection scheduling, and sign-in probes |
src/worktree.ts |
Worktree record, projection, waiting messages, and creation at the first step |
src/states.ts |
Change request states for the Session list |
src/git.ts |
Git queries under Session permissions |
src/parse.ts |
Git output parsing and remote order |
src/diff.ts |
Patch parsing, untracked files as additions, and numstat records |
src/local-diff.ts |
Fork point, local scopes, new-side text, and the branch scope summary |
src/client/mount.ts |
Remote mount, slot entries, and action outcome mapping |
src/client/ChangeRequestCard.tsx |
Reusable change request card |
src/client/LocalChangesCard.tsx |
Local changes card |
src/client/ChangesTab.tsx |
Changes view |
src/client/DiffFile.tsx |
One file of the changes view |
src/client/FileLabel.tsx |
Changed file label: name, then directory |
src/client/WorktreeToggle.tsx |
New Session worktree option |
src/client/WorktreeProgress.tsx |
Worktree creation and removed-worktree card |
src/client/SessionRowForge.tsx |
Session row icons |
src/client/SessionHoverForge.tsx |
Session row hover card section |
Further Exploration
- Forge subsystem — change request types and service reference.
- Session bindings — how change requests bind to a Session.
- Worktree service — how a worktree is created and entered.
Model Experience
Indirectly, through the working-directory service and the Session's conversation: the notice about a Session's new worktree joins the first request's messages, Continue in the project queues the same kind of notice for the next request, and Create change request sends a user message that asks the Agent for a change request. Add to input contributes a frozen ## User Comment N quote containing File, Side, Lines, Source, the diff hunk, and Comment; PR quotes also name the reviewed commit. These quotes enter the normal logged user message only when the composer is sent. Inspection, status, restoring, the Session list, and GitHub review submission add no model context.
KV Cache effect
None for a new worktree, as its notice belongs to the first request of the Session, before any cached prefix exists. A notice after Continue in the project and the message Create change request sends, and submitted review quotes are appended after the cached prefix.
Known Limitations and Deferred Work
- Filtered files and submodules — local diffs bypass clean/process filters; Git's built-in newline, ident, and encoding conversions still apply. Materialized LFS files and other filtered files can appear changed even when ordinary Git considers them clean; pointer-only checkouts compare normally. The panel does not request LFS downloads. Filter overrides rely on Git 2.31 or newer; older Git can ignore them and run helpers under Session permissions. Drivers added between enumeration and diff are not covered by the overrides. Submodule commit changes remain visible; internal uncommitted changes are omitted.
- Polling while shown — status is polled only while a Session list row or an active panel surface for the Session subscribes; retained hidden changes bodies do not subscribe; there are no notifications for checks that finish meanwhile.
- Unopened Sessions in the list — the Session list reads the worktree and bindings of a Session that is not loaded from its cached summary: a Session without one shows neither, and one last used before this version shows its worktree only after it is opened.
- Inspection triggers — the checkout is inspected after Session tool results and directory changes; a change made outside the Session appears after Refresh or the next tool result.
- Retained checkouts — the panel creates a worktree only before a Session's first step and does not remove existing checkout directories or branches; a failed or cancelled attempt can leave its branch or checkout behind. Branch names come from the Session id and random digits, not from the Session title, which does not exist yet.
- Messages during a turn — a message that enters while the Agent runs a turn or other maintenance gets no hold: a requested worktree is then created at the first step, where a failure ends the turn without the message, and a removed worktree is not checked, so the working-directory service moves the Session back to the project directory.
- Waiting messages — while a message waits, the Agent stays idle in maintenance and the composer offers no stop; the card's actions, the Agent's cancellation, or the Remote's stop release it, and callers that wait for the Agent to become idle wait as well.
- Restoring — restoring recreates the checkout from the branch, or from the commit the branch started from when the branch is gone; changes that were never committed are lost with the removed directory. The worktree service validates the recorded target and removes only its missing, unlocked registration. An existing or incomplete directory stays blocked; preserve its files and remove the directory before retrying, or continue in the source project.
- Linked-worktree projects — the option is unavailable when the project is itself a linked worktree: under workspace-write its Git directory lies outside the writable root, so neither the new branch nor later commits could be written.
- Changes scopes — deferred: scopes for one commit and for the last turn. The Branch scope needs a remote whose default branch HEAD shares history with, or a worktree the Session created.
- Large changes — a local patch over
maxDiffBytesshows no files, and a change request diff over the size limit of the host or its provider shows no files and offers only View in browser. Without wrapping, the side-by-side view cuts long lines at the column edge. - Review page — deferred: a left-navigation page that lists the user's change requests from the selected provider; the decision note records where it attaches.
Dev Note
Working context for maintainers — click to expand
None.