dsh-composer-plus 0.9.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.
- package/README.md +6 -2
- package/client.js +130 -28
- package/package.json +2 -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.
|
|
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
|
|
|
@@ -40,6 +40,10 @@ Then restart dsh. (The CLI can't install into the desktop profile; the app manag
|
|
|
40
40
|
|
|
41
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.
|
|
42
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
|
+
|
|
43
47
|
## Keyboard shortcuts
|
|
44
48
|
|
|
45
49
|
| Key | Where | Action |
|
|
@@ -65,7 +69,7 @@ Tested on dsh `0.2.0-rc.2` (desktop and web) and `0.2.1-alpha.2` (web). CI runs
|
|
|
65
69
|
|
|
66
70
|
- **No notifications on macOS:** System Settings → Notifications → DeepSeek Harness → allow. Choose **Alerts** to keep them on screen.
|
|
67
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).
|
|
68
|
-
- **Install fails right after a release:** add the version (`dsh-composer-plus@0.
|
|
72
|
+
- **Install fails right after a release:** add the version (`dsh-composer-plus@1.0.0`), see the install note above.
|
|
69
73
|
|
|
70
74
|
## Limits
|
|
71
75
|
|
package/client.js
CHANGED
|
@@ -799,10 +799,49 @@ window.__ModuleLoader__.load({
|
|
|
799
799
|
return React.createElement('section', { 'data-composer-plus-changes': '', style: { height: '100%', overflowY: 'auto', fontSize: 13 } }, header, body);
|
|
800
800
|
}
|
|
801
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
|
+
|
|
802
837
|
return {
|
|
803
838
|
inject: ['slots', 'sessions', 'locale', 'remote', 'remote.commands'],
|
|
804
839
|
apply(ctx) {
|
|
805
|
-
|
|
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({
|
|
806
845
|
name: 'conversation.input.dock',
|
|
807
846
|
id: 'queue-reorder',
|
|
808
847
|
order: 21,
|
|
@@ -815,8 +854,8 @@ window.__ModuleLoader__.load({
|
|
|
815
854
|
notify: (level, text) => { try { conversation.input.for(actx).notify(level, text); } catch { /* notice is best-effort */ } },
|
|
816
855
|
};
|
|
817
856
|
},
|
|
818
|
-
}, QueueReorder));
|
|
819
|
-
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
857
|
+
}, QueueReorder))]);
|
|
858
|
+
feature('planToggle', () => [ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
820
859
|
name: 'conversation.input.right',
|
|
821
860
|
id: 'composer-plan-toggle',
|
|
822
861
|
order: 999,
|
|
@@ -832,15 +871,37 @@ window.__ModuleLoader__.load({
|
|
|
832
871
|
notify: (level, text) => { try { actx.get('conversation').input.for(actx).notify(level, text); } catch { /* notice is best-effort */ } },
|
|
833
872
|
};
|
|
834
873
|
},
|
|
835
|
-
}, PlanToggle));
|
|
836
|
-
ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
874
|
+
}, PlanToggle))]);
|
|
875
|
+
feature('continueButton', () => [ctx.slots.inject('conversation.input.right', () => ctx.slots.register({
|
|
837
876
|
name: 'conversation.input.right',
|
|
838
877
|
id: 'composer-continue',
|
|
839
878
|
order: 1000,
|
|
840
879
|
locale: LOCALE_NS,
|
|
841
|
-
}, ContinueButton));
|
|
880
|
+
}, ContinueButton))]);
|
|
842
881
|
if (ctx.locale !== undefined) ctx.effect(() => ctx.locale.register(LOCALE_NS, {
|
|
843
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.',
|
|
844
905
|
'project.running': '{count} chat(s) running',
|
|
845
906
|
'project.approval': 'A chat is waiting for approval',
|
|
846
907
|
'project.plan-review': 'A plan is waiting for review',
|
|
@@ -886,6 +947,28 @@ window.__ModuleLoader__.load({
|
|
|
886
947
|
'changes.refresh': 'Refresh',
|
|
887
948
|
},
|
|
888
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': '在侧边栏右键点击对话以复制其链接。',
|
|
889
972
|
'project.running': '{count} 个对话进行中',
|
|
890
973
|
'project.approval': '有对话等待审批',
|
|
891
974
|
'project.plan-review': '有计划等待审阅',
|
|
@@ -931,29 +1014,24 @@ window.__ModuleLoader__.load({
|
|
|
931
1014
|
'changes.refresh': '刷新',
|
|
932
1015
|
},
|
|
933
1016
|
}), 'dsh-composer-plus: dictionaries');
|
|
934
|
-
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
1017
|
+
feature('projectStatus', () => [ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
935
1018
|
name: 'shell.overlay',
|
|
936
1019
|
id: 'project-status',
|
|
937
1020
|
locale: LOCALE_NS,
|
|
938
|
-
}, ProjectStatusMarkers));
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
id: 'chat-tool-grants',
|
|
942
|
-
order: 0,
|
|
943
|
-
locale: LOCALE_NS,
|
|
944
|
-
}, ChatToolGrants));
|
|
945
|
-
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) => {
|
|
946
1024
|
const t = ctx.locale.bind(LOCALE_NS);
|
|
947
1025
|
scope.effect(() => watchAttention(scope.uiSession.sessionStatus, scope.sessions.list, t,
|
|
948
1026
|
(sessionId) => scope.uiWorkspace.openSession(sessionId)), 'dsh-composer-plus: attention notifications');
|
|
949
1027
|
// First use asks the OS once; a denied permission stays denied (re-enable it in System Settings).
|
|
950
1028
|
if (typeof Notification !== 'undefined' && Notification.permission === 'default') Notification.requestPermission().catch(() => {});
|
|
951
|
-
});
|
|
952
|
-
|
|
1029
|
+
}));
|
|
1030
|
+
feature('activeProjectsFirst', () => injectScope(['workspaces', 'uiSession', 'sessions'], (scope) => {
|
|
953
1031
|
scope.effect(() => watchActiveProjects(scope.workspaces, scope.sessions.list, scope.uiSession.sessionStatus),
|
|
954
1032
|
'dsh-composer-plus: active projects first');
|
|
955
|
-
});
|
|
956
|
-
|
|
1033
|
+
}));
|
|
1034
|
+
feature('changesPanel', () => injectScope(['sidebarRight', 'sidebarRightTabs'], (scope) => {
|
|
957
1035
|
const t = ctx.locale.bind(LOCALE_NS);
|
|
958
1036
|
scope.effect(() => scope.sidebarRightTabs.register({
|
|
959
1037
|
id: CHANGES_ID,
|
|
@@ -972,8 +1050,13 @@ window.__ModuleLoader__.load({
|
|
|
972
1050
|
openLastTurn: (sessionId, { seq, turn }) => scope.sidebarRight.openResource(`dsh-resource://changes-review/session/${encodeURIComponent(sessionId)}/${seq}/${turn}`),
|
|
973
1051
|
}),
|
|
974
1052
|
}, ChangesTab)), 'dsh-composer-plus: changes tab body');
|
|
975
|
-
});
|
|
976
|
-
|
|
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) => {
|
|
977
1060
|
const status = scope.uiSession.sessionStatus;
|
|
978
1061
|
// Deferred so answering never re-enters the status notification that triggered it.
|
|
979
1062
|
const scan = () => { queueMicrotask(() => answerGranted(status.getSnapshot())); };
|
|
@@ -983,25 +1066,44 @@ window.__ModuleLoader__.load({
|
|
|
983
1066
|
scan();
|
|
984
1067
|
return () => { offStatus(); offGrants(); };
|
|
985
1068
|
}, 'dsh-composer-plus: chat tool grants');
|
|
986
|
-
});
|
|
1069
|
+
})]);
|
|
987
1070
|
if (ctx.effect !== undefined) ctx.effect(() => () => { grants.clear(); grantsChanged(); }, 'dsh-composer-plus: forget grants on unload');
|
|
988
1071
|
if (typeof fetch === 'function') loadGrants();
|
|
989
|
-
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({
|
|
990
1073
|
name: 'sidebar.workspaces.session.menu.item',
|
|
991
1074
|
id: 'copy-deeplink',
|
|
992
1075
|
order: 350,
|
|
993
1076
|
locale: LOCALE_NS,
|
|
994
|
-
}, CopyDeeplinkItem))
|
|
995
|
-
ctx.slots.inject('sidebar.workspaces.session.row.action', () => ctx.slots.register({
|
|
1077
|
+
}, CopyDeeplinkItem)), ctx.slots.inject('sidebar.workspaces.session.row.action', () => ctx.slots.register({
|
|
996
1078
|
name: 'sidebar.workspaces.session.row.action',
|
|
997
1079
|
id: 'session-context-menu',
|
|
998
1080
|
order: 0,
|
|
999
|
-
}, RowContextMenu));
|
|
1000
|
-
ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
1081
|
+
}, RowContextMenu))]);
|
|
1082
|
+
feature('inputHistory', () => [ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
1001
1083
|
name: 'conversation.composer.dock',
|
|
1002
1084
|
id: 'composer-history',
|
|
1003
1085
|
order: 20,
|
|
1004
|
-
}, 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));
|
|
1005
1107
|
},
|
|
1006
1108
|
};
|
|
1007
1109
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-composer-plus",
|
|
3
|
-
"version": "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": {
|