@achasoft/dsh-advanced-sidebar 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +279 -128
  2. package/cordis.patch.yml +31 -3
  3. package/lib/client.js +2803 -466
  4. package/lib/client.js.map +1 -1
  5. package/lib/host.js +2071 -418
  6. package/lib/index.js +6 -2
  7. package/lib/preview-content-BVUQ5oOR.js +465 -0
  8. package/lib/remote.js +330 -25
  9. package/lib/typert.host.js +330 -25
  10. package/lib/ui-preview.js +352 -0
  11. package/package.json +8 -2
  12. package/types/client/ActionMenu.d.ts +16 -1
  13. package/types/client/LogDownloadDialog.d.ts +24 -0
  14. package/types/client/contract.d.ts +57 -1
  15. package/types/client/index.d.ts +4 -2
  16. package/types/client/locales.d.ts +100 -0
  17. package/types/client/log-download.d.ts +179 -0
  18. package/types/client/panels/PreviewPanel.d.ts +20 -15
  19. package/types/client/panels/preview-file.d.ts +61 -0
  20. package/types/client/panels/preview-mode.d.ts +67 -0
  21. package/types/client/panels/preview-scratchpad.d.ts +53 -0
  22. package/types/client/panels/preview-url.d.ts +17 -0
  23. package/types/client/panels/shared.d.ts +15 -2
  24. package/types/client/preview-driver.d.ts +121 -0
  25. package/types/client/preview-storage.d.ts +43 -0
  26. package/types/client/preview-types.d.ts +21 -0
  27. package/types/client/preview-values.d.ts +43 -0
  28. package/types/host/deletion.d.ts +32 -23
  29. package/types/host/git.d.ts +94 -8
  30. package/types/host/index.d.ts +97 -5
  31. package/types/host/preview-content.d.ts +179 -0
  32. package/types/host/preview-serve.d.ts +242 -0
  33. package/types/host/settings-section.d.ts +49 -0
  34. package/types/host/types.d.ts +341 -0
  35. package/types/host/ui-bridge.d.ts +197 -0
  36. package/types/host/ui-preview-tool.d.ts +60 -0
  37. package/types/index.d.ts +6 -2
  38. package/types/ui-preview.d.ts +11 -0
package/README.md CHANGED
@@ -1,204 +1,355 @@
1
1
  # @achasoft/dsh-advanced-sidebar
2
2
 
3
- Advanced sidebar operations for the DeepSeek Harness Web Client: **Changes**, **Terminal**, **Files**, **Preview**, **Background tasks**, **Open in**, **Archive**, and **Delete**, reachable from one menu in the open session's header and shown in a **resizable dock** beside the conversation.
3
+ A session menu and a resizable side dock for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) Web Client. The **⋯** menu in the session header opens these panels in a column to the right of the conversation:
4
4
 
5
- Everything the browser can already do goes through the Web Client's own capabilities. Everything it structurally cannot — running `git`, allocating a pseudo-terminal, launching an editor, stopping a background task, removing a session log goes through this plugin's own Typert Remote namespace, `ctx.remote.advancedSidebar`.
5
+ - **Changes**: git status, diffs, staging, commit, a model-written commit message, and push;
6
+ - **Terminal**: your own shells, in tabs;
7
+ - **Files**: a file browser with text preview;
8
+ - **Preview**: dev servers, workspace files, URLs, or a scratchpad;
9
+ - **Background tasks**: the session's jobs.
6
10
 
7
- Nothing here is model-facing: no tool, no prompt section, no session event. The one model call is the Changes panel's **Generate**, which a person presses and which reads only the staged patch.
11
+ The same menu has **Open in**, **Download session log**, **Archive**, and **Delete**. Work the browser cannot do itself (running git, opening a pseudo-terminal, launching an editor) runs on the host through this plugin's own RPC namespace. One optional model tool, `ui_preview`, lets the agent inspect and drive the Preview panel.
8
12
 
9
- ## Install
10
-
11
- ```sh
12
- dsh plugin --profile web add /abs/path/to/dsh-advanced-sidebar
13
- dsh web
14
- ```
15
-
16
- From a harness source checkout instead:
17
-
18
- ```sh
19
- pnpm dsh plugin --profile web add ../dsh-plugins/dsh-advanced-sidebar
20
- pnpm dsh web
21
- ```
22
-
23
- Build the plugin first (`pnpm install && pnpm run build`); the Web Client refuses to start when a composed plugin has no built `lib/client.js`.
24
-
25
- ## The menu
13
+ ![The dock open beside the conversation, showing the Changes panel with staged and unstaged files, a diff, and the commit box](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/dock-changes.png)
26
14
 
27
- The menu has one seat: the session header's utilities row. It acts on the session it sits in.
15
+ ## Features
28
16
 
29
- | Entry | What it opens | What it needs on the Host |
30
- |---|---|---|
31
- | Changes | Uncommitted changes in the session's working directory, each file's patch, staging, commit, a model-written commit message, and push | `git` on PATH, `ctx.subprocess`, `ctx.fs`, and `ctx.llm` + `ctx.agentDefaultModel` for the message |
32
- | Terminal | Interactive shells of your own, on a tab strip, in that directory | `ctx.subprocess`, `ctx.fs` |
33
- | Files | That directory, one level at a time, with a text preview | `ctx.fs` |
34
- | Preview | The workspace's dev server, started and shown in a frame, with its logs | `ctx.subprocess`, `ctx.fs` |
35
- | Background tasks | This session's `ctx.jobs` records, with Stop and the output of a settled one | `ctx.jobs` |
36
- | Open in ▸ | A second browser window, a configured editor, or the operating system's file manager | `ctx.subprocess` for the editors |
37
- | Archive | Hides the session; its log and its accounting slot remain | `ctx.workspaceRegistry` |
38
- | Delete | Archives, and — in `purge` mode — removes the durable session log | `ctx.workspaceRegistry`, `ctx.sessionPersistence` |
17
+ ### Session menu
39
18
 
40
- An entry whose capability is missing is **disabled with the reason beside it** rather than hidden, so a mistyped `command` or an uninstalled `git` is visible instead of silent. An entry switched off in settings is absent entirely. The entry of the panel currently in the dock carries a check, so choosing it again reads as the toggle it is.
19
+ A **⋯** button in the open session's header. It acts on that session, using the session's own working directory, or its workspace path when the session has none.
41
20
 
42
- The target of every entry is the session's own `cwd`, falling back to its Workspace path. A session started in a subdirectory therefore works there, rather than at a repository root the model is not using.
21
+ | Entry | What it does |
22
+ | --- | --- |
23
+ | Changes, Terminal, Files, Preview, Background tasks | Opens that panel in the dock. The open panel's entry has a check mark, and choosing it again closes the dock. |
24
+ | Open in ▸ | **New window** (a second tab of the Web Client), each configured editor, and the OS file manager: Finder, File Explorer, or `xdg-open` on Linux. |
25
+ | Download session log | Exports the session as a ZIP through the harness's own exporter. See the note below. |
26
+ | Archive | Hides the session. Its log stays on disk. |
27
+ | Delete | Asks for confirmation, then archives. See [Known limitations](#known-limitations). |
43
28
 
44
- ## The dock
29
+ When the host cannot serve an entry (git not installed, an editor command not found, no subprocess capability), the entry stays in the menu, disabled, with the reason next to it. An entry switched off in settings is not shown at all. The menu asks the host again each time it opens, so installing git or an editor shows up without a restart.
45
30
 
46
- Panels open in a **resizable column on the right of the app frame**, not in a layer over it. The frame's own two columns — the sidebar and the details panel are grid tracks a plugin cannot add to, so the dock takes the one additive frame-wide seat there is (`shell.overlay`) and reserves its width on the frame itself: `PanelHost` sets `--dsh-advanced-dock-reserved` and a marker attribute on the frame element, and two attribute-selector rules in [`PanelHost.module.css`](src/client/PanelHost.module.css) turn that into a padding on the frame plus a matching shift of the frame's own details handle, so that handle stays on the column border it drags.
31
+ **Download session log.** The harness package `@deepseek-ai/dsh-session-log-export` adds its own **⋯** button, with that single entry, to the same header row. This plugin hides that button: it registers an entry with the same id (`session-log-download`) at priority `-1`, and moves the entry into its own menu. Exporting still uses the harness's controller and dialog, so `/export` keeps working. If the harness package is absent or its controller has a different shape, nothing is hidden and the menu has no Download entry.
47
32
 
48
- Dragging the dock's left edge resizes it; so do Left and Right on the focused handle, and a double-click restores the configured width. The width is written to the controller immediately and to `panelWidth` in the settings section when the scope is writable — a remote Web Client has no settings document, and a drag there still has to resize the dock. During a drag the width is written straight to the DOM rather than through React, so a diff list or a terminal emulator is not re-rendered at pointer cadence.
33
+ ![Session header menu open: the panel entries, the Open in submenu, Download session log, Archive, and Delete](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/session-menu.png)
49
34
 
50
- How wide the dock may get is what the frame can spare: its own width **less the sidebar's**, less a 400px floor for the conversation. The sidebar is subtracted because the frame's solver never makes it concede — it holds the sidebar at its preference and lets the centre absorb every squeeze, so a ceiling measured from the frame alone spends that width twice and leaves the conversation a sliver. Below the width at which even the smallest dock would breach that floor, the dock stops reserving and floats over the conversation with a margin and an elevation. Both are measured from the frame's own box, not from a media query: the Web Client can be embedded, and the window is not the frame.
35
+ ### The dock
51
36
 
52
- ## The component kit
37
+ Panels open in a column on the right of the app frame. The conversation narrows to make room, so the dock does not cover it.
53
38
 
54
- Every control this plugin draws comes from [`src/client/ui/`](src/client/ui), a small kit in **shadcn/ui's vocabulary**: the variant and size axes, the geometry scale, the flat bordered surfaces, and the focus ring — over the harness's own design tokens, so the components sit in the app's light and dark themes unmodified. shadcn itself cannot be installed here: it is Tailwind utilities over Radix, and this browser half ships as one bundled CSS-Modules file with no Tailwind pipeline and no second React runtime to give Radix.
39
+ - **Resizing.** Drag the left edge, use Left and Right on the focused handle, or double-click the handle to return to the configured width.
40
+ - **Width limits.** The width is kept between 280 px and 960 px, and never leaves the conversation less than 400 px.
41
+ - **Narrow windows.** When even the minimum width would squeeze the conversation below 400 px, the dock floats over the conversation instead.
55
42
 
56
- `Button`, `Badge`, `Input`, `Textarea`, `Switch`, `Checkbox`, `Select`, `Tabs`, `Alert`, `Dialog`, `AlertDialog`, `DropdownMenu`, `Tooltip`, `Separator`, `Calendar`, and `DatePicker` are the pieces; [`Layer`](src/client/ui/Layer.tsx) is the portal they all float in.
43
+ ### Changes
57
44
 
58
- The kit is also what fixes the menu. The harness's `Menu` primitive clamps a root list into the viewport but pins a submenu at `left: calc(100% + 10px)` with no collision handling — so the header's menu, which is always anchored near the right edge, pushed its `Open in` submenu off the window entirely. Every surface here is placed through [`placeLayer`](src/client/ui/anchor.ts), a pure function of four rectangles that flips a submenu to the left of its row when the right cannot hold it, shifts it to stay inside the window, and reports the height it may occupy. `tests/anchor.spec.ts` covers the flip, the shift, and the case where neither side fits.
45
+ - **Status.** The working directory's status, grouped as Staged, Not staged, Untracked, and Conflicted, with branch, ahead, and behind counts. Click a file to see its patch, with **Copy patch**.
46
+ - **Staging.** **Stage** / **Unstage** a file, or a whole group with **Stage all** / **Unstage all**.
47
+ - **Commit.** **Commit** records what is staged, with an optional **Amend the previous commit**. The author git would record (`git var GIT_AUTHOR_IDENT`) is shown under the message box, so a missing `user.email` is visible before you commit.
48
+ - **Generate.** Writes a commit message with the model the composer is currently set to. The model sees only the staged patch, up to `commitMessageMaxBytes`. The message goes into the box for you to edit, and nothing is committed automatically.
49
+ - **Push.** Pushes the current branch to its upstream (`git push` with no arguments). A branch with no upstream shows **Publish** instead, which pushes to `origin`, or to the first remote if there is no `origin`, and sets the upstream. Force push, a remote picker, and custom refspecs are not offered.
50
+ - **No discard.** There is no way to discard changes from this panel. Use the Terminal panel.
59
51
 
60
- ## What each panel does, and what it deliberately does not
52
+ ### Terminal
61
53
 
62
- **Changes** reads the repository and writes to its index. The reading is `git status --porcelain=v2 --branch -z --untracked-files=all`, parsed in [`src/host/porcelain.ts`](src/host/porcelain.ts); `-z` is what keeps a path containing a space, a quote, or a newline identical between the status reading and the diff request that follows it.
54
+ - **Your own shells.** Interactive shells in the session's working directory, in tabs, up to `maxTerminals`. They are separate from the model's terminals.
55
+ - **Controls.** **Interrupt** and Ctrl+C send SIGINT to the foreground process group. **Clear** clears the screen, and **Restart** starts a new shell.
56
+ - **Shells keep running.** Closing the dock or switching panels leaves them running. Reopening a tab replays the output the host kept (`terminalScrollback`). Closing a tab ends its shell, and so does archiving or deleting the session.
57
+ - **Emulator.** The screen is `@xterm/xterm`, so colors, line editing, and full-screen programs work.
63
58
 
64
- Stage and unstage act on one file or a whole group, and **Commit** records what is staged, with an optional amend. Each write returns the reading that follows it, so the lists never lag a round trip behind the index they describe, and open patches are dropped with the index they described.
59
+ ![Terminal panel with two shell tabs and git command output](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/terminal-panel.png)
65
60
 
66
- **Generate**, beside Commit, has the deployment's own model write the message. It is the model the composer is already set to (`ctx.agentDefaultModel.currentSelection()`), so there is no second credential and no second provider to configure; a Host with no `ctx.llm` or no selection reports the button unavailable rather than failing when it is pressed. The model is shown **the staged patch and nothing else** — not the working tree, not the history, not the session — bounded by `commitMessageMaxBytes` and told when it was cut. The answer lands in the message box for a person to edit; nothing commits on its own. `commitMessagePrompt` replaces the built-in instruction for a repository with its own convention.
61
+ ### Files
67
62
 
68
- **Push** sends the current branch to its own upstream, and nothing else. There is no refspec assembled from browser text: `git push` with no arguments already means exactly what the button offers, and one built from a text field would let a single control push anything anywhere. A branch with no upstream is not pushed but **published** — the button says so, and records the remote it went to (the branch's own remote where it has one, then `origin`, then the first remote there is). Credential prompts are refused rather than waited on (`GIT_TERMINAL_PROMPT=0`), so a repository needing one fails fast instead of hanging until the timeout; that timeout is `gitPushTimeoutMs`, separate from every reading because a push waits on a network and on the remote's own processing.
63
+ - **Browsing.** The working directory, one level at a time. Hidden entries are excluded unless `filesShowHidden` is on.
64
+ - **Preview.** A text preview up to `filesMaxPreviewBytes`. Binary files show their size.
65
+ - **Actions.** **Open with the default application** and **Show in file manager**.
69
66
 
70
- **Discarding is deliberately absent.** Stage, unstage, and commit are all recoverable — the working tree is untouched by the first two, and a commit stays in the reflog — while `git restore` destroys uncommitted work with nothing left to recover it from. A sidebar is the wrong place for the one irreversible verb in the set, and it is a keystroke away in the Terminal panel beside it.
67
+ ![Files panel listing a directory, with a text file previewed](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/files-panel.png)
71
68
 
72
- Four things the panel does rather than leaving to git's own error text:
69
+ ### Preview
73
70
 
74
- - Every path is proved to sit inside the repository before git sees it, and passed after `--` as a literal path `git add` takes *pathspecs*, so an unchecked `:(exclude)` would stage something nobody picked.
75
- - The message crosses as one argument to `-m`, so no shell sees it and nothing in it can become an option. A message of `--amend --author=someone` commits that text.
76
- - The author is read with `git var GIT_AUTHOR_IDENT` — git's own answer to "who would this commit be by" — and shown under the box, so a missing `user.email` is visible *before* the button is pressed.
77
- - Committing has its own timeout (`gitCommitTimeoutMs`, default 2 minutes) because it runs the repository's `pre-commit` hook, which can far outlast any reading; killing one mid-run leaves a stale `index.lock`. Hooks run, and a hook's stderr comes back verbatim rather than summarized.
71
+ Four modes, with a viewport picker (Desktop, Tablet, Mobile, Custom) in every mode.
78
72
 
79
- Every write is gated by a setting the **Host** enforces, not just the panel — `allowGitStaging`, `allowGitCommit`, `allowGitPush`, `allowCommitMessageDraft`: switching one off takes the verb away rather than hiding it.
73
+ | Mode | What it shows |
74
+ | --- | --- |
75
+ | **Server** | Launch configurations from the workspace's `.claude/launch.json` (Claude Code's format) and the `previews` setting. The file wins when both define the same name. **Start** and **Stop** a server, view **Logs** (these open automatically when a start fails), **Open in a new window**, or **Open inspectable**, which hands the URL to URL mode. |
76
+ | **File** | A workspace file, rendered by type: HTML and SVG in a frame, Markdown rendered, images, audio, video, PDF, and plain text. The panel reloads when the file changes. Files over `previewMaxFileBytes`, or of unknown types, offer **Open with the default application**. |
77
+ | **URL** | Any `http(s)` address. A `localhost`, `127.x.x.x`, or `[::1]` address is loaded through the host's proxy, so the frame is same-origin and its DOM and console can be read. Other addresses are framed directly and labeled cross-origin. |
78
+ | **Scratchpad** | HTML you type, rendered from a host route. The text is saved per workspace in this browser's `localStorage`. |
80
79
 
81
- **Terminal** allocates its own shells through `ctx.subprocess.spawnTerminal` deliberately **not** `ctx.terminals`. That registry's sessions are owner-fenced to an `Agent` and are the model's working terminals; joining them would let a human's keystrokes land in a session the model believes it controls.
80
+ For a launch configuration, readiness means the configured `port` accepts a TCP connection, checked until `previewReadyTimeoutMs`. The child process gets `PORT`, `NO_COLOR=1`, and `FORCE_COLOR=0`. Stopping sends SIGTERM to the process tree, then SIGKILL after `previewGraceMs`.
82
81
 
83
- There are **as many shells as `maxTerminals` allows**, on a tab strip, each with its own emulator, its own poll chain, and its own Restart. The handles live in the plugin's shared controller rather than in the panel, so switching to another panel or closing the dock leaves the shells running exactly as hiding a terminal pane in an editor does; a reopened tab replays from the Host's retained scrollback rather than restarting. Closing a tab closes its shell, and so does the session going away. Every open tab stays mounted and laid out — hidden with `visibility`, never `display: none`, because a box with no layout makes the emulator's fit throw and the screen would have to be rebuilt on every tab switch.
84
-
85
- The screen is a real terminal emulator (`@xterm/xterm`), and that is why the browser bundle is large. It is not a preference: an interactive shell redraws its prompt with cursor addressing on every keystroke, and a hand-rolled screen model renders a login shell's prompt as overwritten fragments — verified, then replaced. Colour, line editing, history recall, and full-screen programs come with the emulator. Ctrl+C is intercepted and delivered as a **signal to the foreground process group** rather than as a byte, which is the difference between interrupting a running command and doing nothing.
86
-
87
- Output is polled, not pushed: an out-of-tree plugin has no host-to-client push channel, so the panel holds the whole-stream offset it has already written into the emulator and asks for whatever came after it. That offset is also what makes a reopened panel replay the retained scrollback.
88
-
89
- There is no resize: the subprocess seam exposes none. The emulator follows the dock so rendered rows stay readable, but the *shell* keeps the size it was allocated at; **Restart** allocates one at the new size.
90
-
91
- [`src/client/terminal-screen.ts`](src/client/terminal-screen.ts) survives as the log renderer for the Preview and Background tasks panels, which show plain output rather than an interactive screen.
92
-
93
- **Files** lists through this plugin's own endpoint rather than the Web Client's `listDirectory`, because the Host's browse capability returns directories only — its one shipped caller is a workspace picker. Every path is resolved through `ctx.fs` and proved to sit inside the workspace before anything reads it.
94
-
95
- **Preview** runs what you are building and shows it beside the conversation.
96
-
97
- Launch configurations are read from the workspace's own **`.claude/launch.json`** — Claude Code's file, unchanged — and from the `previews` settings rows, in that order; a name declared in both is taken from the repository's file, because a repository is the authority on how to run itself. A row with a `runtimeExecutable` starts a process; a row with only a `url` is attach-only and simply points the frame at something already running.
82
+ `.claude/launch.json` example:
98
83
 
99
84
  ```json
100
85
  {
101
86
  "version": "0.0.1",
102
87
  "configurations": [
103
- { "name": "web", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], "port": 3000 },
104
- { "name": "docs", "runtimeExecutable": "pnpm", "runtimeArgs": ["docs:dev"], "port": 5173, "cwd": "website" }
88
+ { "name": "web", "runtimeExecutable": "npm", "runtimeArgs": ["run", "dev"], "port": 3000 }
105
89
  ]
106
90
  }
107
91
  ```
108
92
 
109
- Readiness is a **TCP connect to the configured port**, retried until it accepts or `previewReadyTimeoutMs` passes. An HTTP probe would need a path, a method, and an opinion about which status codes count; a listening socket is the one fact every dev server agrees on. `PORT` is exported to the child, and `NO_COLOR`/`FORCE_COLOR` are set so the log view shows text rather than escape sequences.
110
-
111
- The frame is a plain `<iframe>` with viewport presets (desktop, tablet, mobile) scaled to the dock, and an editable address bar so you can navigate into a route rather than only the root. Two controls are permanent rather than error states:
93
+ ![Preview panel in Server mode running a launch configuration, with the proxied page and the viewport picker](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/preview-panel.png)
112
94
 
113
- - **Logs** — stdout and stderr interleaved in arrival order, read by the same caller-owned offset the Terminal panel uses. A server that failed to start has nothing to put in the frame and its stderr is the only place the reason exists, so a failed start opens the log view itself.
114
- - **Open in a new window** — a page can refuse to be framed (`X-Frame-Options`, `frame-ancestors`), and cross-origin framing gives the panel no way to detect that: the load event fires either way. The escape hatch is therefore always present instead of appearing after a failure nothing can observe.
95
+ ### Background tasks
115
96
 
116
- Stopping is `SIGTERM` then `SIGKILL` on the whole process tree after `previewGraceMs`, so a dev server's own child processes go with it. Starting a configuration that is already running replaces it rather than racing it for the port, and every server is stopped when the plugin unloads.
97
+ - **List.** The session's background jobs, filtered by text, status, and start date, with duration and status.
98
+ - **Stop.** Requires `allowTaskKill`. Stopping a task also suppresses the completion notice the model would otherwise receive.
99
+ - **Output.** Requires `showTaskOutput`, and appears only after the task has finished and its completion has been reported. Reading output earlier would consume the output the model reads.
117
100
 
118
- **Background tasks** does not fetch its list. The Host already pushes `session/jobs` frames that the Web Client folds into `jobsBySession`, so the panel reads the same live data the session header's job chip reads. Only Stop and output cross this plugin's endpoint:
101
+ ### Settings card
119
102
 
120
- The list is filtered in the browser by a text filter, a status `Select`, and a `DatePicker` bounding how old a task may be. The date is compared against a **local midnight** rather than a formatted day, so a task started at 23:30 belongs to the day the operator saw on the clock.
103
+ **Settings Plugins Advanced sidebar** edits most settings and shows, for each Open in target and preview configuration, whether it is available on this host. See [Configuration](#configuration).
121
104
 
122
- - **Stop** marks the registry record *reported*, which suppresses the completion notice its producer would otherwise deliver to the model. That is the correct trade for a person pressing Stop — the work is cancelled on their authority — and it is why the verb is a setting (`allowTaskKill`) rather than always on.
123
- - **Output** is served only once a task has settled **and** its completion has been reported. `ctx.jobs.read()` consumes the same cursor the model reads from, so draining a live task's stream would silently delete output the model was about to receive. Text already drained is retained Host-side, so reopening the panel shows it again instead of an empty second read.
105
+ ![Advanced sidebar settings card, expanded, with menu entry toggles, limits, and the Open in target list](https://raw.githubusercontent.com/navid-kianfar/dsh-advanced-sidebar/main/docs/screenshots/settings.png)
124
106
 
125
- **Open in** distinguishes a directory from a file: a directory is opened, a file is *selected* in its folder (`open -R`, `explorer /select,`). The harness's own `host.openPath` hands a path to its default application, which is the right verb for the first and the wrong one for the second.
107
+ ## Requirements
126
108
 
127
- **Delete** is assembled, because no harness capability deletes a session — persistence is append-only and exposes no delete verb, and the workspace registry can only archive. So Delete is honest about which half it managed:
109
+ - **DeepSeek Harness 0.1.5-rc.2** with the `web` profile. This is the version the plugin is tested against. Node `^22.19 || >=24`.
110
+ - **pnpm** on `PATH`, because `dsh plugin` runs pnpm.
111
+ - **git 2.23 or newer** on the host `PATH` for Changes, because unstaging uses `git restore --staged`.
112
+ - Harness capabilities, each optional. A missing one disables only the entries that need it, with the reason shown:
128
113
 
129
- - `archive` hides the session and keeps its log.
130
- - `purge` also removes the persistence backend's per-session artifact. Nothing undoes that, it requires `confirmDelete`, and it **never touches a live session** — a running turn would keep appending to a file that no longer exists, so the archive commits and the reason comes back with it. A backend that keeps no per-session artifact (SQLite) reports `archive` from `describe()`, so the confirmation never promises a removal that will not happen.
114
+ | Capability | Needed by |
115
+ | --- | --- |
116
+ | `subprocess` | Changes, Terminal, Preview servers, Open in |
117
+ | `fs` | Changes, Terminal, Files, Preview |
118
+ | `jobs` | Background tasks (Stop and Output) |
119
+ | `workspaceRegistry` | Delete; Preview File mode (files are served only from registered workspaces) |
120
+ | `llm` + `agentDefaultModel` | **Generate** commit message |
121
+ | `connection` (with `requestRejection`) + `webServer` | Preview's same-origin routes: File mode, the loopback proxy, Scratchpad |
122
+ | `tools` | The `ui_preview` model tool |
123
+ | `sessionLogDownload` (from `@deepseek-ai/dsh-session-log-export`) | Download session log |
131
124
 
132
- A live session is detected through the **agent registry**, not the session store: an agent is what runs a turn, and a cold session the store merely retains is not being written to.
125
+ - **OS:** developed and tested on macOS. The code has Windows and Linux branches (shell fallback, file-manager command) that are not verified.
133
126
 
134
- ## Settings
127
+ ## Install
135
128
 
136
- The `advanced-sidebar` section is registered by the Host half and rendered as a card on the settings **Plugins** tab. Every control writes straight through the bound settings scope, which owns revision fencing; there is no save or discard.
129
+ ```bash
130
+ dsh plugin --profile web add @achasoft/dsh-advanced-sidebar
131
+ dsh web
132
+ ```
137
133
 
138
- `describe()` also carries the **resolved section**, and every surface reads `bound scope value ?? describe().settings`. That is not redundancy: `ctx.settingsScope` resolves to a real document only on a loopback connection and answers `unavailable` with no value on every remote Web Client. Without the Host's copy the whole surface would read "no settings" as "switched off" and disappear for remote access; with it, a remote client sees the deployment's real configuration, read-only.
134
+ `dsh plugin --profile <name> …` runs pnpm with the remaining arguments in `$DSH_HOME/profiles/<name>` (default `~/.dsh/profiles/web`). Afterwards, dsh adds every dependency whose `package.json` declares `dsh.bundle` to `dsh.profile.bundles`. This package declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`, so it is enabled with no manual edit. Restart `dsh web` after installing.
139
135
 
140
- Every deployment-varying choice is a `config` field on the `advanced-sidebar` row in [`cordis.patch.yml`](cordis.patch.yml) — placement, which entries exist, dock width, delete mode, task permissions, git bounds, the shell, the terminal count, the file-preview bounds, and the Open in targets. The card edits all of them except the target list, which stays in `cordis.yml` where a command and its arguments can be written properly; the card shows each target's **availability on this Host**, which the file cannot state.
136
+ To uninstall, remove the package. dsh also drops it from `dsh.profile.bundles`:
141
137
 
142
- ## Composition
138
+ ```bash
139
+ dsh plugin --profile web remove @achasoft/dsh-advanced-sidebar
140
+ ```
143
141
 
144
- Two rows, both always composed:
142
+ ### How `cordis.patch.yml` is applied
145
143
 
146
- ```yaml
147
- - insert:
148
- - id: advanced-sidebar
149
- name: '@achasoft/dsh-advanced-sidebar/host'
150
- config: { ... }
151
- - id: advanced-sidebar-ui
152
- name: '@achasoft/dsh-advanced-sidebar'
153
- ```
144
+ At boot, dsh builds the configuration from patch layers, in this order:
154
145
 
155
- The second row is the **bare package name** deliberately: the Web Client discovers a browser half by resolving `<row name>/package.json`, so a subpath row would leave every seat silently unserved.
146
+ 1. Each bundle's `cordis.patch.yml`, in `dsh.profile.bundles` order.
147
+ 2. `$DSH_HOME/profiles/<name>/cordis.patch.yml`.
148
+ 3. `$DSH_HOME/cordis.patch.yml`.
149
+ 4. Any `--patch <file>` overlays.
156
150
 
157
- Three slot registrations: `conversation.session.header.utilities`, `shell.overlay` (the dock and the Delete confirmation), and `settings.plugin.item`. They have no common React ancestor, so what a person opened — the panel, the dock's width, and the terminal tabs — lives in a controller this package owns and hands to each registration through its inject face.
151
+ Later layers override earlier ones by row `id`. This package inserts three rows:
158
152
 
159
- There was a fourth, at the sidebar foot. It was withdrawn: the same menu in two places gave the session column an action whose target was whichever session happened to be current, which is not what a column of sessions reads as.
153
+ | id | name | Role |
154
+ | --- | --- | --- |
155
+ | `advanced-sidebar` | `@achasoft/dsh-advanced-sidebar/host` | Host service, RPC namespace `advancedSidebar`, the `advanced-sidebar` settings section, and the preview routes. |
156
+ | `advanced-sidebar-ui` | `@achasoft/dsh-advanced-sidebar` | Browser half. It must be the bare package name, because the Web Client finds browser code by resolving `<row name>/package.json`. |
157
+ | `advanced-sidebar-ui-preview` | `@achasoft/dsh-advanced-sidebar/ui-preview` | The `ui_preview` model tool. |
160
158
 
161
- ## The generated Typert artifact
159
+ To keep the sidebar but not give the model a tool, disable the third row in your profile's `cordis.patch.yml`:
162
160
 
163
- `generated/` carries the RPC contract the browser half mounts. The harness's Typert generator reads a TypeScript program seeded from the harness's own `tsconfig.host.json`, so it cannot run against a package outside that checkout; the artifact is therefore **authored to that generator's format** by [`scripts/emit-typert.mjs`](scripts/emit-typert.mjs), which holds the endpoint list and every wire schema.
161
+ ```yaml
162
+ - id: advanced-sidebar-ui-preview
163
+ disabled: true
164
+ ```
164
165
 
165
- Editing `src/host/types.ts` or the `@Remote` surface means editing that spec too:
166
+ To see the composed result:
166
167
 
167
- ```sh
168
- pnpm run regen:typert # re-emits generated/ and records its fingerprint
168
+ ```bash
169
+ dsh --profile web --dump-config
169
170
  ```
170
171
 
171
- `pnpm test` refuses a mismatch: `scripts/check-typert.mjs` compares declared and generated endpoints as sets and re-hashes the inputs, and `tests/typert-contract.spec.ts` parses a representative value through every descriptor's schemas.
172
+ ## Configuration
173
+
174
+ All keys below are in the `config` of the `advanced-sidebar` row. A patch replaces a row's whole `config`, so an override must restate every key. Copy the row from this package's `cordis.patch.yml` and edit it.
175
+
176
+ The host schema declares no defaults, and every key except `commitMessagePrompt` and `terminalShell` is required. The defaults listed are the values `cordis.patch.yml` ships. Changes saved from the settings card are stored as a user layer over the patch value. **Card** marks keys the card can edit. On a Web Client that is not on loopback the harness settings scope is unavailable, so the card and menu show the host's values read-only.
177
+
178
+ **Menu and behavior**
179
+
180
+ | Key | Default | Card | What it does |
181
+ | --- | --- | --- | --- |
182
+ | `showInSessionHeader` | `true` | yes | Shows the ⋯ menu in the session header. When off, the button still appears if needed to offer Download session log, and nothing else. |
183
+ | `showChanges`, `showTerminal`, `showFiles`, `showTasks`, `showPreview`, `showOpenIn`, `showArchive` | `true` | yes | Shows each menu entry. |
184
+ | `showDelete` | `true` | yes | Shows Delete. When off, the host also refuses `deleteSession`. |
185
+ | `panelWidth` | `460` | yes | Dock width in px (schema allows 280–1400; the dock uses at most 960). Dragging the edge saves here when the scope is writable. |
186
+ | `deleteMode` | `archive` | yes | `archive` or `purge`. `purge` is unavailable on this harness; see limitations. |
187
+ | `confirmDelete` | `true` | yes | Asks before Delete. Must be `true` when `deleteMode` is `purge`. |
188
+ | `allowTaskKill` | `true` | yes | Offers Stop, enforced by the host. |
189
+ | `showTaskOutput` | `true` | yes | Offers Output for finished tasks, enforced by the host. |
190
+
191
+ **git (Changes panel)**
192
+
193
+ | Key | Default | Card | What it does |
194
+ | --- | --- | --- | --- |
195
+ | `gitMaxFiles` | `500` | yes | Most files in one status reading. |
196
+ | `gitDiffMaxBytes` | `262144` | no | Largest patch returned for one file. |
197
+ | `gitTimeoutMs` | `20000` | yes | Time limit for each read-only git command. |
198
+ | `allowGitStaging` | `true` | yes | Stage and unstage, enforced by the host. |
199
+ | `allowGitCommit` | `true` | yes | Commit, enforced by the host. Also requires staging. |
200
+ | `gitCommitTimeoutMs` | `120000` | yes | Time limit for `git commit`, which runs hooks. |
201
+ | `allowGitPush` | `true` | yes | Push and Publish, enforced by the host. |
202
+ | `gitPushTimeoutMs` | `180000` | yes | Time limit for a push. |
203
+ | `allowCommitMessageDraft` | `true` | yes | **Generate**, enforced by the host. Also requires commit. |
204
+ | `commitMessagePrompt` | `''` | no | Replaces the built-in commit-message instruction. Empty uses the built-in one. |
205
+ | `commitMessageMaxBytes` | `65536` | yes | Largest staged patch sent to the model. A longer patch is truncated, and the model is told it was cut. |
206
+
207
+ **Terminal and Files**
208
+
209
+ | Key | Default | Card | What it does |
210
+ | --- | --- | --- | --- |
211
+ | `terminalShell` | `''` | yes | Shell to run. Empty uses `$SHELL`, then `/bin/sh` (`%COMSPEC%` or `powershell.exe` on Windows). |
212
+ | `terminalScrollback` | `200000` | no | Characters of output kept per terminal for replay. |
213
+ | `maxTerminals` | `4` | yes | Most panel terminals open at once (1–32). |
214
+ | `terminalGraceMs` | `3000` | no | Delay between TERM and KILL when a terminal closes. |
215
+ | `filesMaxPreviewBytes` | `262144` | no | Largest file shown in the Files preview. |
216
+ | `filesMaxEntries` | `2000` | no | Most entries listed per directory. |
217
+ | `filesShowHidden` | `false` | no | Lists dot-files. |
218
+
219
+ **Preview**
220
+
221
+ | Key | Default | Card | What it does |
222
+ | --- | --- | --- | --- |
223
+ | `previewsFromLaunchFile` | `true` | yes | Reads `.claude/launch.json`. |
224
+ | `previews` | `[]` | no (listed) | Extra launch rows: `name`, `runtimeExecutable`, `runtimeArgs`, `port`, `url`, `cwd`. An empty `runtimeExecutable` makes the row attach-only: it points the frame at `url` and starts nothing. |
225
+ | `maxPreviews` | `3` | yes | Most dev servers running at once. |
226
+ | `previewReadyTimeoutMs` | `60000` | yes | How long to wait for the port to accept connections. |
227
+ | `previewScrollback` | `200000` | no | Log characters kept per server. |
228
+ | `previewGraceMs` | `3000` | no | Delay between TERM and KILL when a server stops. |
229
+ | `previewMaxFileBytes` | `33554432` | no | Largest workspace file served to the frame. |
230
+ | `previewProxyTimeoutMs` | `30000` | no | Time limit for one proxied request to a loopback server. |
231
+ | `previewCommandTimeoutMs` | `15000` | no | How long a `ui_preview` command other than `open` waits for the panel. |
232
+ | `previewBindTtlMs` | `6000` | no | How long a panel counts as open after its last poll. |
233
+
234
+ **Open in**
235
+
236
+ | Key | Default | Card | What it does |
237
+ | --- | --- | --- | --- |
238
+ | `editors` | VS Code (`code`), Cursor (`cursor`), Zed (`zed`) | no (availability listed) | Rows of `id`, `label`, `command`, `args`. `args` go before the path. `id` must match `^[a-z][a-z0-9-]*$`, be unique, and not be `reveal`. |
239
+
240
+ The host refuses a configuration at load when:
241
+
242
+ - an editor id is invalid or duplicated, or an editor command is empty;
243
+ - a preview name is empty or duplicated;
244
+ - a preview row has no command, no URL, and no port;
245
+ - `deleteMode` is `purge` while `confirmDelete` is `false`.
246
+
247
+ The `advanced-sidebar-ui-preview` row has one key, `commandTimeoutMs` (default `15000`, range 1000–600000). It is the default wait for `ui_preview open` when the call passes no `waitMs`.
248
+
249
+ ## Model tool and RPC
250
+
251
+ ### `ui_preview`
252
+
253
+ Registered only when the `advanced-sidebar-ui-preview` row is composed. It works only while the Preview panel is open in the same session. Otherwise it returns immediately with a message telling the model to open the panel.
254
+
255
+ | Action | Arguments | Result |
256
+ | --- | --- | --- |
257
+ | `open` | `url` or `path`; optional `workspace`, `waitMs` | Points the panel at an http(s) URL or a workspace file, and reports whether the page can be inspected. |
258
+ | `dom` | optional `selector` | The rendered DOM: tags, ids and classes, text, display, and box metrics, plus the page text, viewport, and URL. |
259
+ | `eval` | `expression` | Runs JavaScript in the frame and returns the value as JSON. |
260
+ | `console` | optional `cursor` | Console messages, uncaught errors, and unhandled rejections logged since `cursor`. |
261
+ | `click` | `selector` | Dispatches `click()` on the element. |
262
+ | `type` | `selector`, `text`, optional `key` | Sets the element's value, dispatches `input` and `change`, then optionally the key. |
263
+ | `reload` | none | Reloads the frame. |
264
+ | `resize` | `width`, `height` | Sets the frame viewport size. |
265
+ | `close` | none | Closes the preview. |
266
+
267
+ `dom`, `eval`, `click`, and `type` refuse a cross-origin frame by name instead of returning nothing.
268
+
269
+ ### RPC namespace `advancedSidebar`
270
+
271
+ The browser half calls these endpoints over the harness's client connection:
272
+
273
+ - `describe`
274
+ - `gitStatus`, `gitDiff`, `gitStage`, `gitUnstage`, `gitCommit`, `gitPush`, `gitCommitMessage`
275
+ - `terminalOpen`, `terminalRead`, `terminalWrite`, `terminalSignal`, `terminalClose`
276
+ - `listEntries`, `readFile`
277
+ - `previewList`, `previewStart`, `previewStop`, `previewLogs`, `previewFileInfo`, `previewPoll`, `previewResult`, `previewRelease`
278
+ - `openIn`
279
+ - `taskKill`, `taskOutput`
280
+ - `deleteSession`
281
+
282
+ Every endpoint returns a result value with a failure code instead of throwing. The root export re-exports the types.
283
+
284
+ ## Security notes
285
+
286
+ - **git does not run programs the repository configures.**
287
+ - Every git command passes `-c core.fsmonitor=false`.
288
+ - Every `git diff` passes `--no-ext-diff --no-textconv`.
289
+ - Read-only commands (status, diff, log, identity, remote list) switch off `filter.<driver>.clean`/`process` from the repository's local and per-worktree config. Global and system filters, such as git-lfs, still run. A driver name that cannot be disabled this way, because it contains `=`, makes the reading fail instead.
290
+ - Stage, commit, and push are explicit user actions and keep git's normal behavior, including hooks and filters.
291
+ - **No option injection.**
292
+ - Paths are checked to be inside the repository and passed after `--`.
293
+ - The commit message is a single `-m` argument.
294
+ - Publish checks the branch with `git check-ref-format --branch`, checks the remote as `refs/remotes/<remote>/HEAD`, and pushes `-- <remote> refs/heads/<b>:refs/heads/<b>`.
295
+ - Credential prompts are disabled (`GIT_TERMINAL_PROMPT=0`), so a push that needs credentials fails instead of hanging.
296
+ - **Host-enforced switches.** `allowGitStaging`, `allowGitCommit`, `allowGitPush`, `allowCommitMessageDraft`, `allowTaskKill`, `showTaskOutput`, and `showDelete` are checked by the host, not only hidden in the UI.
297
+ - **Paths stay inside the workspace.** Files, Preview File mode, and `ui_preview` file arguments are resolved through the harness filesystem's containment check, so symlinks cannot escape.
298
+ - **Panel terminals are separate from the model's terminals.** They are allocated with `ctx.subprocess.spawnTerminal`, not the agent's terminal registry, so your keystrokes never reach a terminal the model controls.
299
+ - **Preview routes are gated.** The routes `/advanced-sidebar/preview-file`, `/advanced-sidebar/preview-proxy` (plus its websocket upgrade), and `/advanced-sidebar/preview-scratchpad` each call the harness connection's `requestRejection` first. That is the same host/origin check and signed `dsh-auth-*` cookie check that guards `/api`, and it answers `401`/`403` otherwise. If the connection has no such gate, the routes are not registered at all.
300
+ - **Loopback only.** The proxy forwards only to literal loopback hosts (`localhost`, `127.0.0.0/8`, `[::1]`). A hostname that merely resolves to loopback is refused.
301
+ - **No credential forwarding.** The harness's `dsh-auth-*` cookie is stripped from forwarded requests and from upstream `Set-Cookie` headers.
302
+ - **Registered workspaces only.** The file route serves only files inside a workspace in `workspaceRegistry`.
303
+ - **Scratchpad size.** Scratchpad documents are limited to 1 MiB.
304
+ - **No caching.** Responses are sent with `no-store`.
305
+ - **A proxied page runs as the Web Client's origin.** Its scripts can call the harness API with your session. Only preview dev servers you trust as much as a browser tab signed in to the harness.
306
+ - **`ui_preview open` accepts any http(s) URL**, like the address bar. Only loopback URLs become same-origin and inspectable.
307
+
308
+ ## Known limitations
309
+
310
+ - **Delete only archives.** The harness's session persistence API (0.1.5-rc.2) has no way to remove a session. So `deleteMode: purge` is reported unavailable, the card will not select it, and a configured `purge` archives and returns the reason the log was kept.
311
+ - **No push channel.** An out-of-tree plugin cannot add wire frames, so terminal output, preview logs, and `ui_preview` commands are polled.
312
+ - The command poll runs every 600 ms while there is work and every 2 s when idle.
313
+ - A watched preview file is checked every 900 ms.
314
+ - Background tasks are the exception: they use the harness's existing `session/jobs` push.
315
+ - **No terminal resize.** The subprocess API has no resize call. The emulator follows the dock, but the shell keeps its starting size until you **Restart** it.
316
+ - **Server mode frames are cross-origin.** A started dev server's own URL is framed directly. Use **Open inspectable** to load it through the proxy.
317
+ - **Websocket proxying works only at the proxy root.** A dev server that opens its live-reload socket on a subpath is not tunneled. The page still renders, and HTTP streaming (including SSE) is proxied.
318
+ - **`ui_preview eval` runs as a function body.** Declarations do not persist between calls.
319
+ - **Console capture sees only the page's `console` and error events**, not network failures or workers.
320
+ - **Byte-range requests need `fs.readByteRange`.** Without it, the file route answers a full `200`, so media plays but cannot seek.
321
+ - **The dock depends on the frame's DOM.** It reserves width by setting a CSS property and a data attribute on the `shell.overlay` frame element. A harness layout change could require an update.
322
+ - **Large browser bundle.** The terminal emulator makes up most of it, and the harness serves one file per plugin, so it cannot be loaded lazily.
323
+ - **Generate costs model tokens.** Each press calls the deployment's provider. Set `allowCommitMessageDraft: false` to remove it.
324
+ - **Stopping a task hides its completion from the model.** Set `allowTaskKill: false` to remove Stop.
172
325
 
173
326
  ## Development
174
327
 
175
- ```sh
328
+ The dev dependencies are `link:` specifiers to a DeepSeek Harness source checkout at `../../deepseek-harness`, relative to this directory. Clone the harness there before installing.
329
+
330
+ ```bash
176
331
  pnpm install
177
- pnpm run build # tsc emit -> tsdown two-half bundle
178
332
  pnpm run typecheck
179
- pnpm test # typert drift check + vitest
333
+ pnpm test # checks generated/ against src/host, then runs vitest
334
+ pnpm run build # tsc -p tsconfig.build.json, then tsdown -> lib/
180
335
  ```
181
336
 
182
- For a live loop, run `npx tsdown --watch` in this package: the harness's HMR half stat-polls the served bundle, so any writer of `lib/client.js` triggers a reload.
183
-
184
- ## Known limitations
337
+ `generated/` is the committed Typert RPC contract, written by `scripts/emit-typert.mjs` in the harness generator's format. If you change `src/host/types.ts` or the `@Remote` methods, update that script's spec and regenerate:
185
338
 
186
- - **The preview frame cannot report its own console or network.** Those need same-origin access to the framed page, which a dev server on another port does not give. The server's own logs are what the panel shows; the browser's devtools are one "Open in a new window" away.
187
- - **No push channel.** An out-of-tree plugin cannot add a wire frame, so terminal output, preview logs, and the git reading are polled. Background tasks are the exception — they ride the Host's existing `session/jobs` push.
188
- - **No terminal resize.** The subprocess seam has no resize verb. Long lines wrap rather than scroll, and **Restart** re-measures the box.
189
- - **The dock reserves its width through the frame's DOM.** `shell.overlay` is the only additive frame-wide seat, and it draws above the columns rather than between them, so reserving space means setting a property and a marker on the frame element and letting two attribute-selector rules do the rest. Both are removed when the dock closes or the plugin unloads. A future frame that stops publishing `data-shell-overlay`, or that positions its details handle differently, would need this updated with it.
190
- - **The commit-message draft spends model tokens on a human's press.** It is not a session event and the model that writes it never sees the conversation, but it is a real call against the deployment's provider; `allowCommitMessageDraft: false` removes it.
191
- - **Push has no force, no remote picker, and no refspec.** Those are the verbs that lose work or push somewhere unintended, and the Terminal panel is one keystroke away for them.
192
- - **Stopping a task suppresses its model notice.** See Background tasks above; `allowTaskKill: false` removes the verb.
193
- - **Purge removes one artifact.** Exactly the path the persistence backend reported for that session — a sidecar the backend owns is the backend's to remove, and a recursive delete here could take a directory.
194
- - **The browser bundle is ~1 MB (205 KB gzipped).** The emulator is most of it. The client module loader serves one file per plugin with no code splitting, so it cannot be deferred until the Terminal panel opens.
195
- - **`--dsw-alias-label-error` is not used here.** ui-theme declares no such token, though three harness stylesheets reference it; this package uses `--dsw-alias-state-error-primary`, and `tests/styles.spec.ts` fails on any token ui-theme does not declare.
339
+ ```bash
340
+ pnpm run regen:typert # rewrites generated/ and its fingerprint
341
+ pnpm run check:typert # the same check pnpm test runs first
342
+ ```
196
343
 
197
- ## Verification
344
+ To load your checkout into a local profile, build it, then add it by path:
198
345
 
199
- Every panel was exercised against a running `dsh web` before release: the git reading, a per-file patch, staging one file, unstaging it, and a real commit against a real repository — verified with `git log`, then undone, a `/bin/zsh` shell running a command and rendering its output, the file listing and a text preview, a `.claude/launch.json` dev server started and shown in the frame plus a deliberately failing one whose stderr opened the log view, the empty task list, and the Delete confirmation. `tests/` covers the porcelain parser against real `git status -z` output, the launch-file parser and merge, the screen model's control vocabulary, the path-containment guard, the generated wire contract end to end, the layer placement that keeps a submenu on screen, the terminal group's tab bookkeeping, and every design token the stylesheets name.
346
+ ```bash
347
+ pnpm run build
348
+ dsh plugin --profile web add "$(pwd)"
349
+ dsh web
350
+ ```
200
351
 
201
- An adversarial audit of the finished package found 35 candidate defects; the confirmed ones are fixed here, including a path-traversal hole in the untracked-diff path (`git diff --no-index` applies no repository containment of its own), a duplicated task-output buffer, a preview poll loop that never stopped on a failed server, and the settings fallback described above.
352
+ The profile loads the built `lib/` output, so build first. After changing browser code, rebuild and reload the page. After changing anything under `src/host/`, or regenerating `generated/`, restart `dsh web`.
202
353
 
203
354
  ## License
204
355
 
package/cordis.patch.yml CHANGED
@@ -37,9 +37,9 @@
37
37
  panelWidth: 460
38
38
 
39
39
  # What Delete does. `archive` hides the session and keeps its log — reversible in principle,
40
- # since the durable slot is preserved. `purge` also removes the persistence backend's own
41
- # per-session artifact, which nothing undoes; it requires confirmDelete, refuses a live
42
- # session, and degrades to `archive` on a backend that keeps no per-session file.
40
+ # since the durable slot is preserved. `purge` asks for the session log to be removed too,
41
+ # but the harness (0.1.5-rc.2) has no supported removal verb, so it is unavailable: the
42
+ # settings card says so, and a Delete under `purge` archives and reports why the log was kept.
43
43
  deleteMode: archive
44
44
  confirmDelete: true
45
45
 
@@ -118,6 +118,18 @@
118
118
  previewReadyTimeoutMs: 60000
119
119
  previewScrollback: 200000
120
120
  previewGraceMs: 3000
121
+ # Same-origin preview serving. `previewMaxFileBytes` bounds one workspace file handed to the
122
+ # frame — a video legitimately dwarfs any text preview, which is why the cap is separate
123
+ # from filesMaxPreviewBytes — and `previewProxyTimeoutMs` bounds one proxied request to a
124
+ # loopback dev server.
125
+ previewMaxFileBytes: 33554432
126
+ previewProxyTimeoutMs: 30000
127
+ # The agent channel behind `ui_preview`. `previewCommandTimeoutMs` is how long one command
128
+ # waits for the panel to answer before the tool reports a timeout instead of holding the
129
+ # model's turn open; `previewBindTtlMs` is how long a panel is trusted after its last poll,
130
+ # which is what turns a closed tab into "no preview surface" rather than a silent wait.
131
+ previewCommandTimeoutMs: 15000
132
+ previewBindTtlMs: 6000
121
133
  previews: []
122
134
  # Example rows — uncomment and adapt, or put the same thing in .claude/launch.json:
123
135
  # previews:
@@ -156,3 +168,19 @@
156
168
  # `<row name>/package.json`, so a subpath row here would leave every seat silently unserved.
157
169
  - id: advanced-sidebar-ui
158
170
  name: '@achasoft/dsh-advanced-sidebar'
171
+
172
+ # The agent-facing `ui_preview` tool. Its own row, so a deployment can mount the sidebar without
173
+ # giving a model a verb on it — drop this row and the tool does not exist. The row declares
174
+ # `inject: ['tools', 'advancedSidebar']`, which is what makes it wait for the service it calls
175
+ # instead of racing the Host row that provides it.
176
+ #
177
+ # Trust boundary: `open` with a path and every file argument are contained to the session
178
+ # workspace, `open` with a URL accepts only http(s) (the same latitude the panel's address bar
179
+ # gives a person), and the Host's reverse proxy refuses every non-loopback host on its own — so
180
+ # this tool cannot turn the GUI into an open proxy. `commandTimeoutMs` here is how long `open`
181
+ # waits for the browser to load the page; every other action waits for the Host's own
182
+ # `previewCommandTimeoutMs` instead.
183
+ - id: advanced-sidebar-ui-preview
184
+ name: '@achasoft/dsh-advanced-sidebar/ui-preview'
185
+ config:
186
+ commandTimeoutMs: 15000