dsh-up-session 1.0.3 → 1.0.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # 更新日志
2
2
 
3
+ ## 1.0.4 - 2026-11-07
4
+
5
+ ### 新增
6
+
7
+ - 置顶区和最近区的会话行可打开宿主会话菜单:右键或键盘菜单键可置顶、重命名、分叉、复制 Session ID、在文件管理器中显示和归档。菜单项与工作区列表中的同一条会话一致。
8
+
9
+ ### 修复
10
+
11
+ - 将 DSH 最低兼容版本提高到 `0.2.2-alpha.3`,避免旧版 DSH 因缺少会话菜单接口而在安装插件后无法启动。
12
+
3
13
  ## 1.0.3 - 2026-10-26
4
14
 
5
15
  ### 变更
package/README.en.md CHANGED
@@ -15,7 +15,9 @@ A simple DeepSeek Harness sidebar plugin: it inserts two groups, "Pinned" and "R
15
15
 
16
16
  ## Installation and compatibility
17
17
 
18
- The DSH compatibility range is `>=0.2.1-rc.2`, allowing 0.2.1-rc.2, later prereleases of that version, and higher stable versions to install; functionality still needs to be verified against the DSH version in use.
18
+ **Upgrade to DSH 0.2.2-alpha.3 or later. This plugin uses the session menu API introduced in that version.**
19
+
20
+ Plugin version 1.0.4 requires DSH `>=0.2.2-alpha.3`. Installing it on an earlier DSH version may prevent DSH from starting.
19
21
 
20
22
  **Web**
21
23
 
@@ -29,10 +31,28 @@ Restart DSH after installation. If you use a custom profile, replace `web` with
29
31
 
30
32
  Open **Plugins** in the sidebar, click **Add plugin**, enter `dsh-up-session`, and click **Enable now** after installation.
31
33
 
34
+ ### Installing 1.0.3 on older DSH versions
35
+
36
+ If your DSH version is older than 0.2.2-alpha.3 and falls within plugin version 1.0.3's compatibility range (`>=0.2.1-rc.2`), install `dsh-up-session@1.0.3`.
37
+
38
+ **Web**
39
+
40
+ ```sh
41
+ dsh plugin --profile web add dsh-up-session@1.0.3
42
+ ```
43
+
44
+ Restart DSH after installation. If you use a custom profile, replace `web` with its name.
45
+
46
+ **Desktop**
47
+
48
+ Open **Plugins** in the sidebar, click **Add plugin**, enter `dsh-up-session@1.0.3`, and click **Enable now** after installation.
49
+
32
50
  ## Pinned section
33
51
 
34
52
  Gathers the pinned sessions of every workspace, newest activity first, and collapses like the host's own sections. Hovering a row reveals its archive and unpin buttons.
35
53
 
54
+ Right-clicking a pinned or recent row, or focusing it and pressing the menu key or Shift+F10, opens the same session menu as a row in the workspace list: pin or unpin, rename, fork, copy the Session ID, reveal in the file manager, and archive. The items are the host's own contributions, so they stay in step with the same session further down the list. Rename is prefilled with the session's own title; an untitled session is not prefilled with the "Untitled" label.
55
+
36
56
  Both Pinned and Recent sit at the top of the workspaces list as its first two groups and scroll together with the workspace groups below. While the workspace search is open, they step aside so the list can show the results.
37
57
 
38
58
  ## Recent section
@@ -43,7 +63,7 @@ Sits between the pinned section and the workspace rows, lists the sessions that
43
63
  - once every session is on screen the button reads "Show less",
44
64
  - clicking "Show less" folds the list back to the latest five.
45
65
 
46
- Hovering a row reveals its archive and pin buttons. Pinned sessions appear only in the pinned section, so no conversation is listed twice.
66
+ Hovering a row reveals its archive and pin buttons. The context menu is the same as in the pinned section. Pinned sessions appear only in the pinned section, so no conversation is listed twice.
47
67
 
48
68
  ## Settings
49
69
 
package/README.md CHANGED
@@ -15,7 +15,9 @@
15
15
 
16
16
  ## 安装与兼容
17
17
 
18
- DSH 兼容版本声明为 `>=0.2.1-rc.2`,允许 0.2.1-rc.2、同版本号下更新的预发布版,以及更高的正式版安装;具体功能仍需与所用 DSH 版本验证。
18
+ **建议升级到 DSH 0.2.2-alpha.3 以上版本,本插件使用该版本提供的会话菜单接口。**
19
+
20
+ 插件 1.0.4 的 DSH 兼容版本为 `>=0.2.2-alpha.3`,在更早的 DSH 版本上安装可能导致 DSH 无法启动。
19
21
 
20
22
  **Web 端**
21
23
 
@@ -29,10 +31,28 @@ dsh plugin --profile web add dsh-up-session
29
31
 
30
32
  打开侧边栏「插件」,点击「添加插件」,输入 `dsh-up-session`,安装完成后点击「立即启用」。
31
33
 
34
+ ### 在旧版 DSH 上安装 1.0.3
35
+
36
+ 如果你的 DSH 版本早于 0.2.2-alpha.3,且符合插件 1.0.3 的兼容范围 `>=0.2.1-rc.2`,请指定版本安装 `dsh-up-session@1.0.3`。
37
+
38
+ **Web 端**
39
+
40
+ ```sh
41
+ dsh plugin --profile web add dsh-up-session@1.0.3
42
+ ```
43
+
44
+ 安装后重启 DSH。如果使用自定义 profile,将命令中的 `web` 替换为对应名称。
45
+
46
+ **桌面端**
47
+
48
+ 打开侧边栏「插件」,点击「添加插件」,输入 `dsh-up-session@1.0.3`,安装完成后点击「立即启用」。
49
+
32
50
  ## 置顶区
33
51
 
34
52
  汇总所有工作区里被置顶的会话,按最近更新时间排序,可展开收起。每行悬停时出现归档和取消置顶两个按钮。
35
53
 
54
+ 在置顶区或最近区的会话行上右键,或聚焦该行后按菜单键 / Shift+F10,会打开与工作区列表相同的会话菜单(置顶或取消置顶、重命名、分叉会话、复制 Session ID、在文件管理器中显示、归档)。菜单项由宿主注册,因此和下面工作区里的同一条会话保持一致。重命名对话框预填的是会话自己的标题,未命名会话不会被填成「未命名」。
55
+
36
56
  置顶区和最近区都位于「工作区」列表顶部,是该列表的前两个分组,会和下方的工作区分组一起滚动;使用「工作区」的搜索功能时,两个区暂时隐藏,把列表让给搜索结果。
37
57
 
38
58
  ## 最近区
@@ -43,7 +63,7 @@ dsh plugin --profile web add dsh-up-session
43
63
  - 全部会话都显示完后,按钮变为「收起」;
44
64
  - 点击「收起」把列表收回到最新的 5 条。
45
65
 
46
- 每行悬停时出现归档和置顶两个按钮。已置顶的会话只出现在置顶区,两区不会重复显示同一个会话。
66
+ 每行悬停时出现归档和置顶两个按钮。右键菜单与置顶区相同。已置顶的会话只出现在置顶区,两区不会重复显示同一个会话。
47
67
 
48
68
  ## 设置
49
69
 
package/client.js CHANGED
@@ -7,8 +7,8 @@ window.__ModuleLoader__.load({
7
7
 
8
8
  /** Dictionary namespace owned by this plugin. */
9
9
  const NS = 'upSession';
10
- /** Services required to read pin state, render, and pin, unpin, or archive. */
11
- const inject = ['slots', 'locale', 'uiWorkspace'];
10
+ /** Services required to read pin state, render, pin, unpin, archive, and open the host session menu. */
11
+ const inject = ['slots', 'locale', 'uiWorkspace', 'contextMenus'];
12
12
 
13
13
  /** Simplified Chinese dictionary and key source of truth. */
14
14
  const zh = {
@@ -204,7 +204,7 @@ window.__ModuleLoader__.load({
204
204
  'border:0;text-align:left;width:100%}',
205
205
  '.dshUS-row:hover{background:var(--dsw-alias-interactive-bg-hover)}',
206
206
  // Matches the sidebar's own selected row (same surface, same label weight).
207
- '.dshUS-current{background:var(--dsw-alias-interactive-bg-hover);',
207
+ '.dshUS-current,.dshUS-menuOpen{background:var(--dsw-alias-interactive-bg-hover);',
208
208
  'color:var(--dsw-alias-label-primary)}',
209
209
  '.dshUS-row:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid ',
210
210
  'var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4d6bfe));outline-offset:-2px}',
@@ -290,7 +290,8 @@ window.__ModuleLoader__.load({
290
290
  'color:var(--dsw-alias-label-caption,currentColor)}',
291
291
  '.dshUS-row:hover .dshUS-actions,',
292
292
  '.dshUS-row:focus-visible .dshUS-actions,',
293
- '.dshUS-row:focus-within .dshUS-actions{display:inline-flex}',
293
+ '.dshUS-row:focus-within .dshUS-actions,',
294
+ '.dshUS-menuOpen .dshUS-actions{display:inline-flex}',
294
295
  '.dshUS-action:hover{color:var(--dsw-alias-label-primary)}',
295
296
  '.dshUS-action:focus-visible{outline:var(--dsw-focus-ring-width,2px) solid ',
296
297
  'var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,#4d6bfe));outline-offset:1px}',
@@ -303,7 +304,9 @@ window.__ModuleLoader__.load({
303
304
  '.dshUS-row:focus-within .dshUS-time,',
304
305
  '.dshUS-row:hover .dshUS-pinMark,',
305
306
  '.dshUS-row:focus-visible .dshUS-pinMark,',
306
- '.dshUS-row:focus-within .dshUS-pinMark{display:none}',
307
+ '.dshUS-row:focus-within .dshUS-pinMark,',
308
+ '.dshUS-menuOpen .dshUS-time,',
309
+ '.dshUS-menuOpen .dshUS-pinMark{display:none}',
307
310
  // A touch surface never hovers, so the actions stay visible there and the
308
311
  // time and marker stay hidden with them.
309
312
  '@media (hover:none){.dshUS-actions{display:inline-flex}',
@@ -544,6 +547,8 @@ window.__ModuleLoader__.load({
544
547
  const NO_STATUSES = new Map();
545
548
  /** Stand-in for a host without the status hook, so the sections still render. */
546
549
  const useNoSessionStatus = (selector) => selector(NO_STATUSES);
550
+ /** Used when the host has no context-menu target store; the row still opens the menu. */
551
+ const useNoContextMenuTarget = () => undefined;
547
552
 
548
553
  /**
549
554
  * The pending-interaction kind the sidebar renders, or undefined for a kind
@@ -917,6 +922,8 @@ window.__ModuleLoader__.load({
917
922
  return {
918
923
  id: summary.id,
919
924
  title: summary.displayTitle || untitled,
925
+ // The host menu's rename draft is the raw title, not the untitled label.
926
+ menuTitle: summary.blank ? '' : summary.title?.trim() ?? '',
920
927
  workspace: owner?.title ?? '',
921
928
  // A known owner names its Workspace; once the list has loaded, a Session
922
929
  // no Workspace accounts for sits in the "Ungrouped" bucket (`''`).
@@ -1028,6 +1035,8 @@ window.__ModuleLoader__.load({
1028
1035
  useSessionStatus,
1029
1036
  usePanelInfo,
1030
1037
  settingsEvents,
1038
+ openMenu,
1039
+ useContextMenuTarget,
1031
1040
  } = props;
1032
1041
  const pinnedSessionIds = useWorkspaces((state) => state.pinnedSessionIds);
1033
1042
  const archivedSessionIds = useWorkspaces((state) => state.archivedSessionIds);
@@ -1149,10 +1158,18 @@ window.__ModuleLoader__.load({
1149
1158
  t,
1150
1159
  });
1151
1160
 
1161
+ // The host menu's captured target, so the row that opened it stays marked
1162
+ // after the pointer leaves. Undefined until a session menu is open.
1163
+ const readMenuTarget = useContextMenuTarget ?? useNoContextMenuTarget;
1164
+ const menuSessionId = readMenuTarget(
1165
+ (target) => (target?.kind === 'session' ? target.value.sessionId : undefined),
1166
+ );
1152
1167
  const shared = {
1153
1168
  currentId,
1154
1169
  onOpen: openRow,
1155
1170
  onArchive: raiseArchive,
1171
+ onOpenMenu: openMenu,
1172
+ menuSessionId,
1156
1173
  onOpenSettings: () => setSettingsOpen(true),
1157
1174
  settingsOpen,
1158
1175
  t,
@@ -1240,29 +1257,62 @@ window.__ModuleLoader__.load({
1240
1257
  * keyboard focus. A pinned row keeps the host's own pin marker where the
1241
1258
  * time would end, so a pinned row looks pinned at rest exactly as the
1242
1259
  * sidebar's rows do.
1243
- * @param props - the row, the current Session id, the open handler, the trailing buttons, the pin marker, and the translator.
1260
+ * @param props - the row, the current Session id, the open handler, the host menu opener, the trailing buttons, the pin marker, and the translator.
1244
1261
  * @returns the row element.
1245
1262
  */
1246
- function SessionRow({ row, currentId, onOpen, actions, pinned, t }) {
1263
+ function SessionRow({ row, currentId, onOpen, onOpenMenu, menuOpen, actions, pinned, t }) {
1247
1264
  const now = Date.now();
1248
1265
  // A live status is the row's own leading cell, exactly as the sidebar
1249
1266
  // draws it; an idle row draws none, so quiet rows keep the section's left
1250
1267
  // edge instead of the sidebar's deeper indent.
1251
1268
  const statusLabel = row.status === undefined ? undefined : statusText(row.status, t);
1269
+ // Same capture the host's session rows use: right-click, the Context Menu
1270
+ // key, or Shift+F10 open the shared session menu at the pointer or the row.
1271
+ const showMenu = (point, event) => {
1272
+ if (typeof onOpenMenu !== 'function') return;
1273
+ event.preventDefault();
1274
+ event.stopPropagation();
1275
+ if (event.currentTarget instanceof HTMLElement) event.currentTarget.focus({ preventScroll: true });
1276
+ onOpenMenu(row.id, row.menuTitle ?? '', point);
1277
+ };
1278
+ const className = ['dshUS-row', row.id === currentId ? 'dshUS-current' : '', menuOpen ? 'dshUS-menuOpen' : '']
1279
+ .filter(Boolean)
1280
+ .join(' ');
1252
1281
  return h(
1253
1282
  'div',
1254
1283
  {
1255
- className: row.id === currentId ? 'dshUS-row dshUS-current' : 'dshUS-row',
1284
+ className,
1256
1285
  role: 'button',
1257
1286
  tabIndex: 0,
1258
1287
  'aria-current': row.id === currentId ? 'true' : undefined,
1288
+ 'aria-haspopup': 'menu',
1259
1289
  title: row.workspace === '' ? row.title : `${row.title} · ${row.workspace}`,
1260
1290
  'aria-label':
1261
1291
  statusLabel === undefined
1262
1292
  ? t('openNamed', { title: row.title })
1263
1293
  : t('openNamedStatus', { title: row.title, status: statusLabel }),
1264
1294
  onClick: () => onOpen(row),
1295
+ onContextMenu: (event) => {
1296
+ const keyboard = event.clientX === 0 && event.clientY === 0;
1297
+ const rect = keyboard ? event.currentTarget.getBoundingClientRect() : undefined;
1298
+ showMenu(
1299
+ { x: rect?.left ?? event.clientX, y: rect?.bottom ?? event.clientY },
1300
+ event,
1301
+ );
1302
+ },
1265
1303
  onKeyDown: (event) => {
1304
+ if (
1305
+ !event.repeat &&
1306
+ !event.nativeEvent.isComposing &&
1307
+ !event.ctrlKey &&
1308
+ !event.altKey &&
1309
+ !event.metaKey &&
1310
+ (event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey))
1311
+ ) {
1312
+ const rect = event.currentTarget.getBoundingClientRect();
1313
+ showMenu({ x: rect.left, y: rect.bottom }, event);
1314
+ return;
1315
+ }
1266
1316
  if (event.key !== 'Enter' && event.key !== ' ') return;
1267
1317
  event.preventDefault();
1268
1318
  onOpen(row);
@@ -1337,6 +1387,8 @@ window.__ModuleLoader__.load({
1337
1387
  onOpen,
1338
1388
  onArchive,
1339
1389
  onUnpin,
1390
+ onOpenMenu,
1391
+ menuSessionId,
1340
1392
  onOpenSettings,
1341
1393
  settingsOpen,
1342
1394
  t,
@@ -1368,6 +1420,8 @@ window.__ModuleLoader__.load({
1368
1420
  row,
1369
1421
  currentId,
1370
1422
  onOpen,
1423
+ onOpenMenu,
1424
+ menuOpen: menuSessionId === row.id,
1371
1425
  t,
1372
1426
  // Every row here is pinned, so every row wears the marker.
1373
1427
  pinned: true,
@@ -1399,6 +1453,8 @@ window.__ModuleLoader__.load({
1399
1453
  onOpen,
1400
1454
  onArchive,
1401
1455
  onPin,
1456
+ onOpenMenu,
1457
+ menuSessionId,
1402
1458
  onOpenSettings,
1403
1459
  settingsOpen,
1404
1460
  t,
@@ -1447,6 +1503,8 @@ window.__ModuleLoader__.load({
1447
1503
  row,
1448
1504
  currentId,
1449
1505
  onOpen,
1506
+ onOpenMenu,
1507
+ menuOpen: menuSessionId === row.id,
1450
1508
  t,
1451
1509
  // No row here is pinned, so no row wears the marker and the
1452
1510
  // time is the row's trailing cell, flush with its edge.
@@ -1988,6 +2046,18 @@ window.__ModuleLoader__.load({
1988
2046
  }
1989
2047
  },
1990
2048
  stopAndArchive: (sessionId) => ctx.uiWorkspace.archiveSession(sessionId, { stopActivity: true }),
2049
+ // The sidebar's own session menu (pin, rename, fork, copy id, Finder, archive).
2050
+ // Opening it here reuses every contribution registered for target "session".
2051
+ openMenu: (sessionId, displayTitle, point) => {
2052
+ ctx.contextMenus.open(
2053
+ {
2054
+ kind: 'session',
2055
+ value: { sessionId, displayTitle },
2056
+ },
2057
+ point,
2058
+ );
2059
+ },
2060
+ hooks: { contextMenuTarget: ctx.contextMenus.active },
1991
2061
  });
1992
2062
 
1993
2063
  ctx.slots.inject('shell.overlay', () =>
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "dsh-up-session",
4
- "description": "Adds pinned and recent groups at the top of the workspace list in the sidebar, gathering pinned and recent sessions, with a settings popup in each group header."
4
+ "description": "Adds pinned and recent groups at the top of the workspace list in the sidebar, gathering pinned and recent sessions. Rows open the same context menu as the workspace list, and each group header has a settings popup."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
3
  "title": "dsh-up-session",
4
- "description": "在侧边栏工作区列表顶部增加置顶区和最近区,汇总各工作区的置顶与最近会话,并在标题栏提供设置入口。"
4
+ "description": "在侧边栏工作区列表顶部增加置顶区和最近区,汇总各工作区的置顶与最近会话;会话行可打开与工作区相同的右键菜单,标题栏提供设置入口。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-up-session",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
- "description": "Adds pinned and recent session groups at the top of the workspace list in the DSH sidebar, with a small settings popup.",
5
+ "description": "Adds pinned and recent session groups at the top of the workspace list in the DSH sidebar. Rows open the host session menu, and each group header has a settings popup.",
6
6
  "license": "MIT",
7
7
  "author": "kyrsieger",
8
8
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "CHANGELOG.md"
43
43
  ],
44
44
  "engines": {
45
- "dsh": ">=0.2.1-rc.2"
45
+ "dsh": ">=0.2.2-alpha.3"
46
46
  },
47
47
  "dsh": {
48
48
  "bundle": {