dsh-composer-plus 0.6.0 → 0.6.2

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 (3) hide show
  1. package/README.md +62 -34
  2. package/client.js +29 -7
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,57 +1,85 @@
1
1
  # dsh-composer-plus
2
2
 
3
- Small composer helpers for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (web / desktop UI). The client half adds the UI; a small Host half serves read-only git changes and saves Auto Review grants. No external network calls.
3
+ [![npm](https://img.shields.io/npm/v/dsh-composer-plus)](https://www.npmjs.com/package/dsh-composer-plus)
4
+ [![test](https://github.com/furkancak1r/dsh-composer-plus/actions/workflows/test.yml/badge.svg)](https://github.com/furkancak1r/dsh-composer-plus/actions/workflows/test.yml)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
4
6
 
5
- - **Input history** – in an empty composer, `↑` / `↓` walk your previously sent messages, like a terminal. An open `/` menu or an edited draft keeps the normal keys.
6
- - **`/plan` toggle** – `Shift+Tab` adds `/plan ` to the start of the draft, even when it already has text; press it again to remove that leading `/plan`. The rest of the draft, file references and attachments stay intact, and the caret ends up at the end. It never sends. An open `/` or `@` menu keeps `Shift+Tab` for closing itself.
7
- - **One-click Continue** – when a turn has stopped and the composer is empty, the Send button becomes **▶**; one click sends `continue`. Hidden on a blank chat, while running, in subagent views, or once you type or attach something.
8
- - **Copy deeplink** – right-click a session in the sidebar (or use its `…` menu) and pick **Copy deeplink** (label follows dsh's language: English or Chinese). You get a link like `dsh-session:InNlc3Npb24t…`; paste it into another chat and dsh attaches that session as read-only context.
9
- - **Allow for this chat (Auto Review)** – when Auto Review denies a call and asks you, the approval card gets an **Allow for this chat** button next to Reject / Allow once. It allows this call and every later Auto Review ask for the **same exact tool in the same chat**, with any arguments (e.g. any read-only SQL query on one database tool). Other tools, other chats and non-Auto-Review approvals still ask. A header chip (“N tool(s) allowed · Revoke”) lists the grants and revokes them in one click. Grants are saved by the plugin's Host half (`$DSH_HOME/storages/dsh-composer-plus/grants.json`, user-only permissions), so they survive a restart until you revoke them. If the tool can write, the grant covers any write too.
10
- - **Changes panel** – a Codex-style **Changes** page in the right sidebar (open it from the sidebar's `+` guide). Pick **Uncommitted**, **Unstaged**, **Staged** or **Branch** (your branch vs. its upstream, from their merge base) to list changed files with `+/-` counts and expand any file for its diff; **Last Turn** opens dsh's own review of the latest turn. It refreshes while visible. Git runs read-only on the Host in the chat's own folder.
11
- - **Background notifications** – while the dsh window is not focused, you get an OS notification titled with the chat name when a turn finishes (with the reply's first line), or when a chat waits for an approval (names the tool), a question (shows it) or a plan review. The Dock/taskbar badge counts chats waiting for you. Clicking it brings dsh to the front and opens that chat. Notifications stay in Notification Center until clicked; a newer one for the same chat replaces the older. Subagent chats don't notify on their own (the parent chat does). Text follows dsh's language (English or Chinese).
12
- - **Drag-and-drop queue reordering** – queued messages get a grip (⠿) at the left. Drag a row to a new position (drop line shown, `Esc` cancels), or focus the grip and press `↑` / `↓`.
7
+ Codex-style quality-of-life features for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) desktop and web UI: a Changes panel, input history, a plan-mode shortcut, one-click Continue, queue drag-and-drop, session deeplinks, per-chat Auto Review grants and background notifications.
13
8
 
14
- Tested on dsh `0.2.0-rc.2` (desktop and web) and `0.2.1-alpha.2` (web). CI runs the tests on every push.
9
+ Unofficial community plugin. No external network calls.
15
10
 
16
11
  ## Install
17
12
 
18
- **Desktop app:** Settings → Plugins → **Add plugin**, paste
13
+ **Desktop app:** Settings → Plugins → **Add plugin** → enter `dsh-composer-plus` → **Enable now**.
14
+ You can also paste `https://github.com/furkancak1r/dsh-composer-plus` instead of the npm name.
19
15
 
20
- ```
21
- https://github.com/furkancak1r/dsh-composer-plus
16
+ **Web / CLI profile:**
17
+
18
+ ```sh
19
+ dsh plugin --profile web add dsh-composer-plus
22
20
  ```
23
21
 
24
- then **Enable now** (or restart dsh). (The desktop profile is managed by the app, so the CLI cannot install into it.)
22
+ Then restart dsh. (The CLI can't install into the desktop profile; the app manages it.)
25
23
 
26
- **Web / CLI profiles:**
24
+ > dsh's pnpm skips versions younger than 24 hours. Right after a release, add the version: `dsh-composer-plus@0.6.2`.
27
25
 
28
- ```sh
29
- dsh plugin --profile web add https://github.com/furkancak1r/dsh-composer-plus
30
- ```
26
+ ## Features
27
+
28
+ | Feature | How to use it |
29
+ |---|---|
30
+ | **Changes panel** | Right sidebar → `+` → **Changes**. Pick **Uncommitted**, **Unstaged**, **Staged** or **Branch** to see changed files with `+/-` counts; click a file for its diff. **Last Turn** opens dsh's own review of the latest turn. |
31
+ | **Input history** | In an empty composer, press `↑` / `↓` to walk through messages you sent, like a terminal. |
32
+ | **Plan mode shortcut** | `Shift+Tab` adds `/plan ` to the start of the draft (press again to remove it). If the chat is already in plan mode, `Shift+Tab` leaves it. |
33
+ | **One-click Continue** | When a turn has stopped and the composer is empty, Send turns into **▶**. One click sends `continue`. |
34
+ | **Queue drag-and-drop** | Queued messages get a grip (⠿). Drag to reorder, or focus the grip and press `↑` / `↓`. `Esc` cancels. |
35
+ | **Copy deeplink** | Right-click a chat in the sidebar → **Copy deeplink**. Paste the `dsh-session:…` link into another chat to attach that chat as read-only context. |
36
+ | **Allow for this chat** | When Auto Review asks you, the approval card gets **Allow for this chat**: the same tool in the same chat is allowed from then on, with any arguments. A header chip lists the grants and revokes them. Grants survive a restart. |
37
+ | **Background notifications** | While dsh is in the background you get a notification when a turn finishes (first line of the reply), or when a chat needs an approval (tool name), an answer (the question) or a plan review. The Dock/taskbar badge counts waiting chats. Clicking opens that chat. |
38
+
39
+ Labels follow dsh's language (English or Chinese).
40
+
41
+ ## Keyboard shortcuts
31
42
 
32
- This adds the dependency and the profile bundle. Restart dsh.
43
+ | Key | Where | Action |
44
+ |---|---|---|
45
+ | `↑` / `↓` | empty composer | previous / next sent message |
46
+ | `Shift+Tab` | composer | add/remove `/plan `, or leave plan mode |
47
+ | `↑` / `↓` | focused queue grip | move the queued message |
48
+ | `Esc` | while dragging | cancel the move |
33
49
 
34
- ## How it works / limits
50
+ An open `/` or `@` menu keeps these keys for itself.
35
51
 
36
- - dsh's `session.updateQueue` has no `move` action, so a reorder rotates the affected rows' text with `edit` calls (tail → head). If the agent claims a row mid-move, the applied edits are reverted and a notice is shown; nothing is lost or duplicated.
37
- - Rows with images/files can't be moved or crossed (queue `edit` is text-only).
38
- - A deeplink uses dsh's `dsh-session:` reference format. It references a session inside chats; clicking it does not open the session (dsh's `dsh://` handler only focuses the app today).
39
- - Session rows have no context-menu slot, so right-click finds the row's own `…` button in the DOM. If dsh changes that markup, right-click shows the normal menu again; the `…` menu item keeps working.
40
- - The Send button and queue rows are not slots, so ▶ and the grips are portaled into the existing DOM. If a dsh update changes that markup, they simply stop appearing; nothing else breaks.
41
- - “Allow for this chat” answers the existing approval request with dsh's own `allowed-once` outcome; Auto Review still runs on every call, and review failures are never auto-allowed. The button is portaled into the approval card's button row (no slot exists); if dsh changes that markup, the button disappears and Allow once / Reject keep working.
42
- - Notifications use the web `Notification` API, which the desktop app allows; macOS asks for permission on the first one. Banners disappear after a few seconds by default; set the app's style to **Alerts** in System Settings → Notifications to keep them on screen. A notification sent before a restart only brings dsh to the front when clicked.
43
- - A native `move` action is requested in [#773](https://github.com/deepseek-ai/deepseek-harness/discussions/773); a built-in Continue state in [#9269](https://github.com/deepseek-ai/deepseek-harness/discussions/9269).
52
+ ## Compatibility
44
53
 
45
- ## Tests
54
+ Tested on dsh `0.2.0-rc.2` (desktop and web) and `0.2.1-alpha.2` (web). CI runs the tests on every push.
55
+
56
+ ## Privacy and security
57
+
58
+ - **Changes panel:** git runs read-only on your machine, in the chat's own folder, through dsh's authenticated `/api` channel. It never commits, stages or fetches, and only shows paths git itself listed.
59
+ - **Allow for this chat:** grants are stored in `$DSH_HOME/storages/dsh-composer-plus/grants.json` (owner-only permissions). A grant covers **one exact tool in one chat**. If that tool can write, the grant covers any write too. Revoke it from the header chip.
60
+ - Auto Review still runs on every call; review failures are never auto-allowed.
61
+
62
+ ## Troubleshooting
63
+
64
+ - **No notifications on macOS:** System Settings → Notifications → DeepSeek Harness → allow. Choose **Alerts** to keep them on screen.
65
+ - **▶, queue grips, Allow for this chat or right-click missing after a dsh update:** these attach to dsh's current HTML because dsh has no slot for them yet. If dsh changes that markup they hide themselves; nothing else breaks. Please open an [issue](https://github.com/furkancak1r/dsh-composer-plus/issues).
66
+ - **Install fails right after a release:** add the version (`dsh-composer-plus@0.6.2`), see the install note above.
67
+
68
+ ## Limits
69
+
70
+ - dsh's queue has no `move` action, so a reorder rewrites the affected rows' text. If the agent picks up a row mid-move, the move is undone and nothing is lost. Messages with images or files can't be moved.
71
+ - A deeplink works inside chats; clicking it does not open the chat.
72
+ - A notification sent before a restart only brings dsh to the front when clicked.
73
+ - Built-in support is requested upstream: queue `move` in [#773](https://github.com/deepseek-ai/deepseek-harness/discussions/773), a Continue state in [#9269](https://github.com/deepseek-ai/deepseek-harness/discussions/9269).
74
+
75
+ ## Development
46
76
 
47
77
  ```sh
48
78
  npm test
49
79
  ```
50
80
 
51
- CI runs the same tests on every push.
52
-
53
- Runs the real `client.js` against a fake React/DOM (/plan toggle, deeplink format and right-click, Continue, queue drag, Auto Review grants including persistence across a restart, notification bodies and Dock badge) and the real `index.js` against a temporary git repository (every Changes mode, rename parsing, untracked files, refusal of paths git did not list, grants file).
81
+ The tests run the real `client.js` against a fake React/DOM and the real `index.js` (Host half) against a temporary git repository. `node test/browser-history.mjs '<dsh web URL with ?token=…>'` checks input history in a real browser against a throwaway `dsh web` with its own `DSH_HOME`.
54
82
 
55
- `node test/browser-history.mjs '<dsh web URL with ?token=…>'` checks ↑/↓ history in a real browser against a throwaway `dsh web` (it sends test messages, so use a separate `DSH_HOME`). The `/plan` toggle (empty, multi-line, references, attachments, caret position), Continue and drag-and-drop were also checked by hand-scripted browser runs against a slow fake model.
83
+ ## License
56
84
 
57
- MIT License. Unofficial community plugin.
85
+ MIT
package/client.js CHANGED
@@ -96,12 +96,16 @@ window.__ModuleLoader__.load({
96
96
  /** Shift+Tab on every platform. */
97
97
  const isPlanKey = (event) => event.key === 'Tab' && event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey;
98
98
 
99
- /** The plan key toggles a leading `/plan` and leaves the caret at the end. Rides `conversation.input.right`
100
- * because, unlike the composer dock, it also renders on a blank chat. */
101
- function PlanToggle({ useInput, inputActions }) {
99
+ /** The plan key leaves plan mode when the chat is in it (as dsh's plan chip does: `/plan off`); otherwise it
100
+ * toggles a leading `/plan` and leaves the caret at the end. Rides `conversation.input.right` because, unlike
101
+ * the composer dock, it also renders on a blank chat. */
102
+ function PlanToggle({ useInput, inputActions, useProjection, exitPlanMode, notify }) {
102
103
  const draft = useInput((state) => (state === undefined ? '' : state.draft));
104
+ const plan = typeof useProjection === 'function' ? useProjection('plan') : undefined;
105
+ // Same effective target as dsh's PlanChip: a pending switch flips the folded host value.
106
+ const planOn = plan !== undefined && (plan.pending ? !plan.active : plan.active);
103
107
  const live = React.useRef(null);
104
- live.current = { draft, inputActions };
108
+ live.current = { draft, inputActions, planOn, exitPlanMode, notify };
105
109
  const anchor = React.useRef(null);
106
110
 
107
111
  React.useEffect(() => {
@@ -114,8 +118,14 @@ window.__ModuleLoader__.load({
114
118
  if (seat.querySelector('[data-trigger-menu]') !== null) return; // Shift+Tab closes an open slash/@ menu
115
119
  event.preventDefault();
116
120
  event.stopPropagation();
117
- const { draft, inputActions } = live.current;
118
- if (event.repeat || event.isComposing || event.keyCode === 229 || inputActions === undefined) return;
121
+ const { draft, inputActions, planOn, exitPlanMode, notify } = live.current;
122
+ if (event.repeat || event.isComposing || event.keyCode === 229) return;
123
+ if (planOn && exitPlanMode !== undefined) {
124
+ exitPlanMode().then((failure) => { if (failure !== null && notify !== undefined) notify('error', failure); },
125
+ (reason) => { if (notify !== undefined) notify('error', reason instanceof Error ? reason.message : String(reason)); });
126
+ return;
127
+ }
128
+ if (inputActions === undefined) return;
119
129
  const { start, end, text } = planToggle(draft);
120
130
  // Edit only the prefix so the rest of the draft, its file chips and attachments stay intact.
121
131
  if (!inputActions.insertText(text, { ...inputActions.captureInsertion(), start, end })) return;
@@ -667,7 +677,7 @@ window.__ModuleLoader__.load({
667
677
  }
668
678
 
669
679
  return {
670
- inject: ['slots', 'sessions', 'locale'],
680
+ inject: ['slots', 'sessions', 'locale', 'remote', 'remote.commands'],
671
681
  apply(ctx) {
672
682
  ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
673
683
  name: 'conversation.input.dock',
@@ -686,6 +696,18 @@ window.__ModuleLoader__.load({
686
696
  name: 'conversation.input.right',
687
697
  id: 'composer-plan-toggle',
688
698
  order: 999,
699
+ inject: (sessionId) => {
700
+ const commands = ctx.remote === undefined ? undefined : ctx.remote.commands;
701
+ const actx = ctx.sessions === undefined ? undefined : ctx.sessions.scope(sessionId);
702
+ return {
703
+ exitPlanMode: commands === undefined ? undefined : async () => {
704
+ const result = await commands.execute(sessionId, '/plan off', []);
705
+ if (!result.ok) return `${result.error.message} (${result.error.code})`;
706
+ return result.value === undefined ? 'unknown command: /plan off' : null;
707
+ },
708
+ notify: (level, text) => { try { actx.get('conversation').input.for(actx).notify(level, text); } catch { /* notice is best-effort */ } },
709
+ };
710
+ },
689
711
  }, PlanToggle));
690
712
  ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
691
713
  name: 'conversation.input.right',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-composer-plus",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "DeepSeek Harness helpers: Codex-style Changes panel, input history, Shift+Tab /plan, one-click Continue, queue drag-and-drop, Copy deeplink, Auto Review allow-for-chat, background notifications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,7 +40,8 @@
40
40
  "@deepseek-ai/dsh-client-ui-workspace",
41
41
  "@deepseek-ai/dsh-client-ui-sidebar-right",
42
42
  "@deepseek-ai/dsh-client-ui-session",
43
- "@deepseek-ai/dsh-api-remotes"
43
+ "@deepseek-ai/dsh-api-remotes",
44
+ "@deepseek-ai/dsh-api-session-controller"
44
45
  ]
45
46
  }
46
47
  },