dsh-composer-plus 0.8.0 → 1.0.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 (3) hide show
  1. package/README.md +8 -2
  2. package/client.js +172 -26
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -21,7 +21,7 @@ dsh plugin --profile web add dsh-composer-plus
21
21
 
22
22
  Then restart dsh. (The CLI can't install into the desktop profile; the app manages it.)
23
23
 
24
- > dsh's pnpm skips versions younger than 24 hours. Right after a release, add the version: `dsh-composer-plus@0.8.0`.
24
+ > dsh's pnpm skips versions younger than 24 hours. Right after a release, add the version: `dsh-composer-plus@1.0.0`.
25
25
 
26
26
  ## Features
27
27
 
@@ -35,10 +35,15 @@ Then restart dsh. (The CLI can't install into the desktop profile; the app manag
35
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
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
37
  | **Project status** | A project row in the sidebar shows the same spinner / waiting / done marker as its chats, so a collapsed project still tells you a chat inside is working or needs you. Hover it for details. |
38
+ | **Active projects first** | When a chat starts running or needs you, its project moves to the top of the sidebar with dsh's own row animation (the same move as dragging it there). Finished projects stay where they are; drag them anywhere as usual. |
38
39
  | **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. |
39
40
 
40
41
  All text — buttons, tooltips, notices, notifications and the Continue message — follows the language selected in dsh (English or Chinese, the two languages dsh ships). Switching the language in dsh settings updates it live.
41
42
 
43
+ ## Settings
44
+
45
+ Every feature can be switched off on its own: dsh **Settings → Composer Plus**. Each row has a switch and a short description; changes apply immediately, without a restart, and are remembered on that device. Everything is on by default.
46
+
42
47
  ## Keyboard shortcuts
43
48
 
44
49
  | Key | Where | Action |
@@ -64,13 +69,14 @@ Tested on dsh `0.2.0-rc.2` (desktop and web) and `0.2.1-alpha.2` (web). CI runs
64
69
 
65
70
  - **No notifications on macOS:** System Settings → Notifications → DeepSeek Harness → allow. Choose **Alerts** to keep them on screen.
66
71
  - **▶, queue grips, project status, 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).
67
- - **Install fails right after a release:** add the version (`dsh-composer-plus@0.8.0`), see the install note above.
72
+ - **Install fails right after a release:** add the version (`dsh-composer-plus@1.0.0`), see the install note above.
68
73
 
69
74
  ## Limits
70
75
 
71
76
  - 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.
72
77
  - A deeplink works inside chats; clicking it does not open the chat.
73
78
  - A notification sent before a restart only brings dsh to the front when clicked.
79
+ - Active projects are moved with dsh's saved project order, so the new order is kept after a restart. Chats outside any project ("Ungrouped") can't be moved; dsh always lists them last.
74
80
  - 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).
75
81
 
76
82
  ## Development
package/client.js CHANGED
@@ -605,6 +605,46 @@ window.__ModuleLoader__.load({
605
605
  if (done > 0) return { state: 'done' };
606
606
  return null;
607
607
  }
608
+ /** Moves that bring active projects (running or waiting for the user) to the top, keeping their relative order:
609
+ * each active project that sits below an idle one goes before the first idle project. */
610
+ function activeFirstMoves(items, active) {
611
+ const firstIdle = items.findIndex((item) => !active.has(item.workspaceId));
612
+ if (firstIdle === -1) return [];
613
+ const anchor = items[firstIdle].workspaceId;
614
+ return items.slice(firstIdle + 1).filter((item) => active.has(item.workspaceId)).map((item) => [item.workspaceId, anchor]);
615
+ }
616
+ /** Keeps active projects at the top of the sidebar with dsh's own reorder (the same move as dragging a project,
617
+ * so dsh animates the rows). Projects are only ever moved up; finished ones stay where they are. */
618
+ function watchActiveProjects(workspaces, sessionsList, status) {
619
+ let moving = false; let again = false;
620
+ const run = async () => {
621
+ if (moving) { again = true; return; }
622
+ moving = true;
623
+ try {
624
+ do {
625
+ again = false;
626
+ const snapshot = workspaces.list.getSnapshot();
627
+ const list = sessionsList.getSnapshot();
628
+ const statuses = status.getSnapshot();
629
+ if (snapshot?.items === undefined || list === undefined || statuses === undefined) break;
630
+ const archived = new Set(snapshot.archivedSessionIds ?? []);
631
+ const active = new Set(snapshot.items.filter((item) => {
632
+ const state = projectStatus(item.sessionIds, list, statuses, archived);
633
+ return state !== null && state.state !== 'done';
634
+ }).map((item) => item.workspaceId));
635
+ for (const [id, before] of activeFirstMoves(snapshot.items, active)) await workspaces.insertBefore(id, before);
636
+ } while (again);
637
+ } catch (error) {
638
+ console.warn('dsh-composer-plus: project reorder rejected', error);
639
+ } finally {
640
+ moving = false;
641
+ }
642
+ };
643
+ const schedule = () => { queueMicrotask(run); };
644
+ const stops = [workspaces.list.subscribe(schedule), sessionsList.subscribe(schedule), status.subscribe(schedule)];
645
+ schedule();
646
+ return () => { for (const stop of stops) stop(); };
647
+ }
608
648
  const PROJECT_STATUS = 'data-composer-plus-project-status';
609
649
  /** Shows the session status (spinner / waiting / done dot) on each project row, so a collapsed project still says
610
650
  * that one of its chats is working. dsh has no project-row slot.
@@ -759,10 +799,49 @@ window.__ModuleLoader__.load({
759
799
  return React.createElement('section', { 'data-composer-plus-changes': '', style: { height: '100%', overflowY: 'auto', fontSize: 13 } }, header, body);
760
800
  }
761
801
 
802
+ /** Feature switches, in Settings order. Every feature is on until switched off. */
803
+ const FEATURES = ['changesPanel', 'projectStatus', 'activeProjectsFirst', 'notifications', 'allowForChat', 'continueButton',
804
+ 'inputHistory', 'planToggle', 'queueReorder', 'copyDeeplink'];
805
+ const TOGGLES_KEY = 'dsh-composer-plus.features';
806
+ /** `{ [feature]: false }` for switched-off features, kept in the app's own browser storage (per device). */
807
+ function createToggles() {
808
+ let state = {};
809
+ try { const raw = localStorage.getItem(TOGGLES_KEY); if (raw !== null) state = JSON.parse(raw) ?? {}; } catch { state = {}; }
810
+ const listeners = new Set();
811
+ return {
812
+ getSnapshot: () => state,
813
+ subscribe: (fn) => { listeners.add(fn); return () => { listeners.delete(fn); }; },
814
+ update: (mutate) => {
815
+ const next = { ...state }; mutate(next);
816
+ for (const key of Object.keys(next)) if (next[key] !== false) delete next[key];
817
+ state = next;
818
+ try { localStorage.setItem(TOGGLES_KEY, JSON.stringify(state)); } catch { /* storage full or blocked: keep in memory */ }
819
+ for (const fn of [...listeners]) fn();
820
+ },
821
+ };
822
+ }
823
+ /** Settings → Composer Plus: one switch per feature, styled like dsh's own preference rows. */
824
+ function FeatureSettings({ useComposerPlusToggles, setFeature, t }) {
825
+ const enabled = useComposerPlusToggles((state) => state);
826
+ const row = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 24, padding: '16px 0',
827
+ borderBottom: '.5px solid var(--dsw-alias-border-l2)' };
828
+ return React.createElement('div', { 'data-composer-plus-settings': '' },
829
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 12, lineHeight: '18px', paddingBottom: 4 } }, t('settings.intro')),
830
+ ...FEATURES.map((id) => React.createElement('div', { key: id, style: row },
831
+ React.createElement('div', null,
832
+ React.createElement('div', { style: { fontSize: 14, lineHeight: '20px' } }, t(`feature.${id}`)),
833
+ React.createElement('div', { style: { color: 'var(--dsw-alias-label-secondary)', marginTop: 4, fontSize: 12, lineHeight: '18px' } }, t(`feature.${id}.description`))),
834
+ React.createElement(primitives.Switch, { checked: enabled[id] !== false, label: t(`feature.${id}`), onChange: (on) => setFeature(id, on) }))));
835
+ }
836
+
762
837
  return {
763
838
  inject: ['slots', 'sessions', 'locale', 'remote', 'remote.commands'],
764
839
  apply(ctx) {
765
- ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
840
+ // Each feature installs through `feature(id, setup)`: setup returns its disposers, and a switch in
841
+ // Settings → Composer Plus removes or reinstalls just that feature, live.
842
+ const features = [];
843
+ const feature = (id, setup) => { features.push({ id, setup, off: null }); };
844
+ feature('queueReorder', () => [ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
766
845
  name: 'conversation.input.dock',
767
846
  id: 'queue-reorder',
768
847
  order: 21,
@@ -775,8 +854,8 @@ window.__ModuleLoader__.load({
775
854
  notify: (level, text) => { try { conversation.input.for(actx).notify(level, text); } catch { /* notice is best-effort */ } },
776
855
  };
777
856
  },
778
- }, QueueReorder));
779
- ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
857
+ }, QueueReorder))]);
858
+ feature('planToggle', () => [ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
780
859
  name: 'conversation.input.right',
781
860
  id: 'composer-plan-toggle',
782
861
  order: 999,
@@ -792,15 +871,37 @@ window.__ModuleLoader__.load({
792
871
  notify: (level, text) => { try { actx.get('conversation').input.for(actx).notify(level, text); } catch { /* notice is best-effort */ } },
793
872
  };
794
873
  },
795
- }, PlanToggle));
796
- ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
874
+ }, PlanToggle))]);
875
+ feature('continueButton', () => [ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
797
876
  name: 'conversation.input.right',
798
877
  id: 'composer-continue',
799
878
  order: 1000,
800
879
  locale: LOCALE_NS,
801
- }, ContinueButton));
880
+ }, ContinueButton))]);
802
881
  if (ctx.locale !== undefined) ctx.effect(() => ctx.locale.register(LOCALE_NS, {
803
882
  en: {
883
+ 'settings.nav': 'Composer Plus',
884
+ 'settings.intro': 'Turn individual dsh-composer-plus features on or off. Changes apply immediately on this device.',
885
+ 'feature.changesPanel': 'Changes panel',
886
+ 'feature.changesPanel.description': 'Right sidebar page with uncommitted, staged and branch changes and their diffs.',
887
+ 'feature.projectStatus': 'Project status',
888
+ 'feature.projectStatus.description': 'Show the running / waiting / done marker of a project\'s chats on the project row.',
889
+ 'feature.activeProjectsFirst': 'Active projects first',
890
+ 'feature.activeProjectsFirst.description': 'Move a project to the top of the sidebar when one of its chats runs or needs you.',
891
+ 'feature.notifications': 'Background notifications',
892
+ 'feature.notifications.description': 'Notify when a chat finishes or needs you while dsh is in the background, with a Dock badge.',
893
+ 'feature.allowForChat': 'Allow for this chat',
894
+ 'feature.allowForChat.description': 'Add “Allow for this chat” to Auto Review approvals and auto-allow granted tools.',
895
+ 'feature.continueButton': 'One-click Continue',
896
+ 'feature.continueButton.description': 'Turn Send into ▶ after a stopped turn; one click sends “continue”.',
897
+ 'feature.inputHistory': 'Input history',
898
+ 'feature.inputHistory.description': 'Use ↑ / ↓ in an empty composer to reuse messages you sent.',
899
+ 'feature.planToggle': 'Shift+Tab plan mode',
900
+ 'feature.planToggle.description': 'Shift+Tab adds or removes /plan, or leaves plan mode.',
901
+ 'feature.queueReorder': 'Queue drag-and-drop',
902
+ 'feature.queueReorder.description': 'Reorder queued messages by dragging their grip.',
903
+ 'feature.copyDeeplink': 'Copy deeplink',
904
+ 'feature.copyDeeplink.description': 'Right-click a chat in the sidebar to copy a link to it.',
804
905
  'project.running': '{count} chat(s) running',
805
906
  'project.approval': 'A chat is waiting for approval',
806
907
  'project.plan-review': 'A plan is waiting for review',
@@ -846,6 +947,28 @@ window.__ModuleLoader__.load({
846
947
  'changes.refresh': 'Refresh',
847
948
  },
848
949
  zh: {
950
+ 'settings.nav': 'Composer Plus',
951
+ 'settings.intro': '单独开启或关闭 dsh-composer-plus 的各项功能。更改会立即在本设备生效。',
952
+ 'feature.changesPanel': '改动面板',
953
+ 'feature.changesPanel.description': '右侧边栏页面,显示未提交、已暂存和分支改动及其差异。',
954
+ 'feature.projectStatus': '项目状态',
955
+ 'feature.projectStatus.description': '在项目行上显示其对话的进行中 / 等待 / 完成标记。',
956
+ 'feature.activeProjectsFirst': '活跃项目置顶',
957
+ 'feature.activeProjectsFirst.description': '当项目中的对话正在运行或需要你处理时,将该项目移到侧边栏顶部。',
958
+ 'feature.notifications': '后台通知',
959
+ 'feature.notifications.description': 'dsh 在后台时,对话完成或需要你处理时发出通知,并显示程序坞角标。',
960
+ 'feature.allowForChat': '本对话内允许',
961
+ 'feature.allowForChat.description': '在 Auto Review 审批中添加“本对话内允许”,并自动允许已授权的工具。',
962
+ 'feature.continueButton': '一键继续',
963
+ 'feature.continueButton.description': '回合停止后,发送按钮变为 ▶,点击即发送“继续”。',
964
+ 'feature.inputHistory': '输入历史',
965
+ 'feature.inputHistory.description': '在空输入框中按 ↑ / ↓ 复用已发送的消息。',
966
+ 'feature.planToggle': 'Shift+Tab 计划模式',
967
+ 'feature.planToggle.description': 'Shift+Tab 添加或移除 /plan,或退出计划模式。',
968
+ 'feature.queueReorder': '队列拖拽排序',
969
+ 'feature.queueReorder.description': '拖动排队消息的把手以调整顺序。',
970
+ 'feature.copyDeeplink': '复制深层链接',
971
+ 'feature.copyDeeplink.description': '在侧边栏右键点击对话以复制其链接。',
849
972
  'project.running': '{count} 个对话进行中',
850
973
  'project.approval': '有对话等待审批',
851
974
  'project.plan-review': '有计划等待审阅',
@@ -891,25 +1014,24 @@ window.__ModuleLoader__.load({
891
1014
  'changes.refresh': '刷新',
892
1015
  },
893
1016
  }), 'dsh-composer-plus: dictionaries');
894
- ctx.slots.inject('shell.overlay', () => ctx.slots.register({
1017
+ feature('projectStatus', () => [ctx.slots.inject('shell.overlay', () => ctx.slots.register({
895
1018
  name: 'shell.overlay',
896
1019
  id: 'project-status',
897
1020
  locale: LOCALE_NS,
898
- }, ProjectStatusMarkers));
899
- ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
900
- name: 'conversation.session.header.utilities',
901
- id: 'chat-tool-grants',
902
- order: 0,
903
- locale: LOCALE_NS,
904
- }, ChatToolGrants));
905
- if (ctx.inject !== undefined) ctx.inject(['uiSession', 'uiWorkspace', 'sessions'], (scope) => {
1021
+ }, ProjectStatusMarkers))]);
1022
+ const injectScope = (deps, setup) => (ctx.inject === undefined ? [] : [ctx.inject(deps, setup)]);
1023
+ feature('notifications', () => injectScope(['uiSession', 'uiWorkspace', 'sessions'], (scope) => {
906
1024
  const t = ctx.locale.bind(LOCALE_NS);
907
1025
  scope.effect(() => watchAttention(scope.uiSession.sessionStatus, scope.sessions.list, t,
908
1026
  (sessionId) => scope.uiWorkspace.openSession(sessionId)), 'dsh-composer-plus: attention notifications');
909
1027
  // First use asks the OS once; a denied permission stays denied (re-enable it in System Settings).
910
1028
  if (typeof Notification !== 'undefined' && Notification.permission === 'default') Notification.requestPermission().catch(() => {});
911
- });
912
- if (ctx.inject !== undefined) ctx.inject(['sidebarRight', 'sidebarRightTabs'], (scope) => {
1029
+ }));
1030
+ feature('activeProjectsFirst', () => injectScope(['workspaces', 'uiSession', 'sessions'], (scope) => {
1031
+ scope.effect(() => watchActiveProjects(scope.workspaces, scope.sessions.list, scope.uiSession.sessionStatus),
1032
+ 'dsh-composer-plus: active projects first');
1033
+ }));
1034
+ feature('changesPanel', () => injectScope(['sidebarRight', 'sidebarRightTabs'], (scope) => {
913
1035
  const t = ctx.locale.bind(LOCALE_NS);
914
1036
  scope.effect(() => scope.sidebarRightTabs.register({
915
1037
  id: CHANGES_ID,
@@ -928,8 +1050,13 @@ window.__ModuleLoader__.load({
928
1050
  openLastTurn: (sessionId, { seq, turn }) => scope.sidebarRight.openResource(`dsh-resource://changes-review/session/${encodeURIComponent(sessionId)}/${seq}/${turn}`),
929
1051
  }),
930
1052
  }, ChangesTab)), 'dsh-composer-plus: changes tab body');
931
- });
932
- if (ctx.inject !== undefined) ctx.inject(['uiSession'], (scope) => {
1053
+ }));
1054
+ feature('allowForChat', () => [ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
1055
+ name: 'conversation.session.header.utilities',
1056
+ id: 'chat-tool-grants',
1057
+ order: 0,
1058
+ locale: LOCALE_NS,
1059
+ }, ChatToolGrants)), ...injectScope(['uiSession'], (scope) => {
933
1060
  const status = scope.uiSession.sessionStatus;
934
1061
  // Deferred so answering never re-enters the status notification that triggered it.
935
1062
  const scan = () => { queueMicrotask(() => answerGranted(status.getSnapshot())); };
@@ -939,25 +1066,44 @@ window.__ModuleLoader__.load({
939
1066
  scan();
940
1067
  return () => { offStatus(); offGrants(); };
941
1068
  }, 'dsh-composer-plus: chat tool grants');
942
- });
1069
+ })]);
943
1070
  if (ctx.effect !== undefined) ctx.effect(() => () => { grants.clear(); grantsChanged(); }, 'dsh-composer-plus: forget grants on unload');
944
1071
  if (typeof fetch === 'function') loadGrants();
945
- ctx.slots.inject('sidebar.workspaces.session.menu.item', () => ctx.slots.register({
1072
+ feature('copyDeeplink', () => [ctx.slots.inject('sidebar.workspaces.session.menu.item', () => ctx.slots.register({
946
1073
  name: 'sidebar.workspaces.session.menu.item',
947
1074
  id: 'copy-deeplink',
948
1075
  order: 350,
949
1076
  locale: LOCALE_NS,
950
- }, CopyDeeplinkItem));
951
- ctx.slots.inject('sidebar.workspaces.session.row.action', () => ctx.slots.register({
1077
+ }, CopyDeeplinkItem)), ctx.slots.inject('sidebar.workspaces.session.row.action', () => ctx.slots.register({
952
1078
  name: 'sidebar.workspaces.session.row.action',
953
1079
  id: 'session-context-menu',
954
1080
  order: 0,
955
- }, RowContextMenu));
956
- ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
1081
+ }, RowContextMenu))]);
1082
+ feature('inputHistory', () => [ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
957
1083
  name: 'conversation.composer.dock',
958
1084
  id: 'composer-history',
959
1085
  order: 20,
960
- }, ComposerHistory));
1086
+ }, ComposerHistory))]);
1087
+ const toggles = createToggles();
1088
+ const apply = () => {
1089
+ const enabled = toggles.getSnapshot();
1090
+ for (const f of features) {
1091
+ const on = enabled[f.id] !== false;
1092
+ if (on && f.off === null) { const offs = f.setup(); f.off = () => { for (const off of offs.reverse()) off?.(); }; }
1093
+ else if (!on && f.off !== null) { f.off(); f.off = null; }
1094
+ }
1095
+ };
1096
+ apply();
1097
+ const offToggles = toggles.subscribe(apply);
1098
+ if (ctx.effect !== undefined) ctx.effect(() => () => { offToggles(); for (const f of features) if (f.off !== null) { f.off(); f.off = null; } }, 'dsh-composer-plus: feature toggles');
1099
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
1100
+ name: 'settings.section',
1101
+ id: 'composer-plus',
1102
+ order: 90,
1103
+ label: () => (ctx.locale === undefined ? 'Composer Plus' : ctx.locale.bind(LOCALE_NS)('settings.nav')),
1104
+ locale: LOCALE_NS,
1105
+ inject: () => ({ hooks: { composerPlusToggles: toggles }, setFeature: (id, on) => toggles.update((draft) => { draft[id] = on; }) }),
1106
+ }, FeatureSettings));
961
1107
  },
962
1108
  };
963
1109
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-composer-plus",
3
- "version": "0.8.0",
3
+ "version": "1.0.0",
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",
@@ -25,7 +25,7 @@
25
25
  "LICENSE"
26
26
  ],
27
27
  "scripts": {
28
- "test": "node test/continue.test.mjs && node test/queue-drag.test.mjs && node test/plan-toggle.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/notify.test.mjs && node test/changes.test.mjs && node test/project-status.test.mjs"
28
+ "test": "node test/continue.test.mjs && node test/queue-drag.test.mjs && node test/plan-toggle.test.mjs && node test/deeplink.test.mjs && node test/chat-grant.test.mjs && node test/notify.test.mjs && node test/changes.test.mjs && node test/project-status.test.mjs && node test/toggles.test.mjs"
29
29
  },
30
30
  "dsh": {
31
31
  "bundle": {
@@ -39,6 +39,7 @@
39
39
  "@deepseek-ai/dsh-client-ui-conversation",
40
40
  "@deepseek-ai/dsh-client-ui-workspace",
41
41
  "@deepseek-ai/dsh-client-ui-layout",
42
+ "@deepseek-ai/dsh-api-workspace-controller",
42
43
  "@deepseek-ai/dsh-client-ui-sidebar-right",
43
44
  "@deepseek-ai/dsh-client-ui-session",
44
45
  "@deepseek-ai/dsh-api-remotes",