dsh-composer-plus 0.6.0 → 0.6.1

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 +5 -9
  2. package/client.js +29 -7
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
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.
4
4
 
5
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.
6
+ - **Plan mode toggle** – while the chat is in plan mode, `Shift+Tab` leaves it (same as clicking the plan chip). Otherwise it 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
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
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
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.
@@ -15,22 +15,18 @@ Tested on dsh `0.2.0-rc.2` (desktop and web) and `0.2.1-alpha.2` (web). CI runs
15
15
 
16
16
  ## Install
17
17
 
18
- **Desktop app:** Settings → Plugins → **Add plugin**, paste
19
-
20
- ```
21
- https://github.com/furkancak1r/dsh-composer-plus
22
- ```
23
-
24
- then **Enable now** (or restart dsh). (The desktop profile is managed by the app, so the CLI cannot install into it.)
18
+ **Desktop app:** Settings → Plugins → **Add plugin**, enter `dsh-composer-plus` (npm) or paste `https://github.com/furkancak1r/dsh-composer-plus`, then **Enable now** (or restart dsh). The desktop profile is managed by the app, so the CLI cannot install into it.
25
19
 
26
20
  **Web / CLI profiles:**
27
21
 
28
22
  ```sh
29
- dsh plugin --profile web add https://github.com/furkancak1r/dsh-composer-plus
23
+ dsh plugin --profile web add dsh-composer-plus
30
24
  ```
31
25
 
32
26
  This adds the dependency and the profile bundle. Restart dsh.
33
27
 
28
+ pnpm in dsh skips versions younger than 24 hours by default (`minimum-release-age`). Right after a release, name the version explicitly, e.g. `dsh-composer-plus@0.6.1`.
29
+
34
30
  ## How it works / limits
35
31
 
36
32
  - 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.
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.1",
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
  },