dsh-session-mng 0.1.0 → 0.1.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.
- package/CHANGELOG.md +16 -0
- package/README.md +12 -0
- package/ROADMAP.md +10 -0
- package/assets/screenshot-1-overview.png +0 -0
- package/assets/screenshot-4-expanded.png +0 -0
- package/client.js +218 -58
- package/docs/01-design.md +13 -0
- package/docs/02-open-source-and-marketplace.md +18 -3
- package/docs/PLAN-2026-10-26.md +2 -0
- package/docs/PLAN-2026-10-27.md +69 -0
- package/docs/RELEASE-CHECKLIST-0.1.0.md +5 -5
- package/docs/RELEASE-CHECKLIST-0.1.1.md +42 -0
- package/package.json +1 -1
- package/screenshots.json +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- 会话列表改为**工作区优先**:会话总数 >100 时默认折叠所有工作区,点击展开;≤100 保持展开。折叠时只渲染工作区头,不渲染行。
|
|
8
|
+
- 每个工作区默认渲染 50 条,支持「加载更多(剩余 N 条)」;搜索/筛选后自动回到前 50 条。
|
|
9
|
+
- 搜索命中时自动展开命中的工作区,并禁用折叠/展开全部/收起全部,提示“搜索时自动展开命中工作区”。
|
|
10
|
+
- 过滤工具栏 + 批量操作条合并为同一个 sticky 头部,滚动时筛选条件与批量操作始终可见。
|
|
11
|
+
- 选择集裁剪到当前筛选与已展开工作区的可见集:切换 tab、工作区、搜索、折叠工作区、刷新后,隐藏会话自动取消选择;`全选当前列表` 只作用于可见集。
|
|
12
|
+
- 批量条增加“选择仅作用于当前筛选与展开结果”提示;批量移动不可用时区分 live 会话与一般不可移动项。
|
|
13
|
+
- 新增「展开全部 / 收起全部」。
|
|
14
|
+
|
|
15
|
+
### Compatibility
|
|
16
|
+
|
|
17
|
+
- 与 0.1.0 相同:DSH `0.2.1-rc.2` 实测;移动需要 Node `>=22.15`;移动/删除仅支持本地 JSONL 后端。
|
|
18
|
+
|
|
3
19
|
## 0.1.0 — 2026-10-27
|
|
4
20
|
|
|
5
21
|
首个公开版本。
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# dsh-session-mng
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-session-mng)
|
|
4
|
+
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
3
7
|
**One Settings page for every DeepSeek Harness session — generate a title from the whole conversation, archive or restore, move a cold session to another workspace, and permanently delete archived sessions.**
|
|
4
8
|
|
|
5
9
|
一个设置页管理全部 DSH 会话:按整段对话生成标题、归档/恢复、跨工作区移动、彻底删除已归档会话。Host + Web Client 双半插件,无运行时依赖,不注册模型可见工具,不注册标题 provider。
|
|
@@ -11,6 +15,8 @@
|
|
|
11
15
|
## 功能
|
|
12
16
|
|
|
13
17
|
- **统一会话总览**:按工作区分组列出当前 profile 的全部会话(活跃 + 已归档)。显示标题、路径、会话 id、大小、最后活动时间、运行/已打开/已归档/已置顶/子会话标记;支持搜索、工作区筛选、排序、刷新、批量选择。
|
|
18
|
+
- **工作区优先 + 分页**:会话数 >100 时默认折叠工作区;每个工作区默认 50 条,「加载更多」继续展开;搜索自动展开命中工作区。过滤工具栏和批量条整体吸顶。
|
|
19
|
+
- **选择跟随可见集**:切换筛选/折叠/搜索/刷新后,隐藏会话自动取消选择;`全选当前列表` 只作用于当前筛选与已展开的工作区。
|
|
14
20
|
- **生成标题**:选中任意会话(**包括已归档的冷会话**),让模型总结整段对话,给出可编辑的标题提案;**确认后才写入**。活跃会话走官方 `sessionTitle.rename`,冷会话按官方 `session/title` 事件形状追加,不需要恢复会话。
|
|
15
21
|
- **归档 / 恢复**:调用官方 `workspaceRegistry.archiveSession` / `unarchiveSession`。运行中的会话先提示,用户二次确认后才停止并归档;恢复后回到原工作区原位置。
|
|
16
22
|
- **跨工作区移动**:把冷会话真正迁移到目标工作区——改写会话日志 header 的 `cwd`、把日志目录迁到目标项目目录、同步 workspace 账本。SessionId、归档状态、pin 状态不变,事件字节不变。
|
|
@@ -19,6 +25,8 @@
|
|
|
19
25
|
- **失效归档标记清理**:归档集合里有、但磁盘上已无日志的 id,会以横幅提示并允许清理。
|
|
20
26
|
- **中英双语**、浅色/深色主题跟随宿主。
|
|
21
27
|
|
|
28
|
+

|
|
29
|
+
|
|
22
30
|

|
|
23
31
|
|
|
24
32
|

|
|
@@ -46,6 +54,8 @@ dsh plugin --profile <profile> add link:/绝对路径/dsh-session-mng
|
|
|
46
54
|
|
|
47
55
|
Host 半是进程内模块:装完如果看不到页面,重启一次 dsh/应用再刷新页面;Client 半刷新页面即可。
|
|
48
56
|
|
|
57
|
+
> 市场状态:已向 awesome-dsh-plugin 提交收录 PR [#9672](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/9672)(审核中;dshmarket 使用同一份目录,合并后可直接在插件市场搜索到)。
|
|
58
|
+
|
|
49
59
|
## 使用
|
|
50
60
|
|
|
51
61
|
1. 打开 **设置 → 会话管理**。
|
|
@@ -132,6 +142,8 @@ npm --cache /tmp/dsh-npm-cache pack --dry-run
|
|
|
132
142
|
|
|
133
143
|
- [实施计划(批准时原样归档)](docs/PLAN-2026-10-26.md)
|
|
134
144
|
- [0.1.0 发布检查清单](docs/RELEASE-CHECKLIST-0.1.0.md)
|
|
145
|
+
- [0.1.1 实施计划](docs/PLAN-2026-10-27.md)
|
|
146
|
+
- [0.1.1 发布检查清单](docs/RELEASE-CHECKLIST-0.1.1.md)
|
|
135
147
|
- [设计与实现说明](docs/01-design.md)
|
|
136
148
|
- [开源、发 npm 与市场收录记录](docs/02-open-source-and-marketplace.md)
|
|
137
149
|
- [路线图](ROADMAP.md)
|
package/ROADMAP.md
CHANGED
|
@@ -9,8 +9,18 @@
|
|
|
9
9
|
- 彻底删除已归档会话(范围预览 + 子代理级联 + 事务 + 账本清理)。
|
|
10
10
|
- 最近消息预览、失效归档标记清理、中英双语。
|
|
11
11
|
|
|
12
|
+
## 0.1.1 — delivered
|
|
13
|
+
|
|
14
|
+
- 工作区优先:会话数 >100 默认折叠,点击展开;每个工作区 50 条分页 + 加载更多。
|
|
15
|
+
- 搜索命中自动展开,搜索期间禁止折叠;展开全部 / 收起全部。
|
|
16
|
+
- 过滤工具栏 + 批量条合并为 sticky 头部。
|
|
17
|
+
- 选择集裁剪到当前筛选与展开可见集;批量操作不触碰隐藏会话。
|
|
18
|
+
- 批量移动提示区分 live 与一般不可移动项。
|
|
19
|
+
|
|
12
20
|
## 0.2.0 — candidates
|
|
13
21
|
|
|
22
|
+
- Host 侧 overview + paged sessions 接口:首屏只取工作区计数,按工作区懒加载 50 条,真正支撑 1000–10000 条会话。
|
|
23
|
+
- 未分组会话懒加载 + 短缓存;全局搜索复用宿主索引。
|
|
14
24
|
- 侧栏会话「...」菜单快捷入口(生成标题 / 会话管理详情),复用同一套 Host 操作。
|
|
15
25
|
- 内容全文搜索(复用 `sessionQuery.searchSessions` / `searchEvents`),不只按标题/路径/id。
|
|
16
26
|
- 导出所选会话为 Markdown / JSON(删除前的安全网;批量)。
|
|
Binary file
|
|
Binary file
|
package/client.js
CHANGED
|
@@ -20,6 +20,8 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
const NS = 'dsh-session-mng'
|
|
21
21
|
const API = '/session-mng/api'
|
|
22
22
|
const STYLE_ID = 'dsh-session-mng-style'
|
|
23
|
+
const GROUP_PAGE_SIZE = 50
|
|
24
|
+
const DEFAULT_COLLAPSE_AFTER = 100
|
|
23
25
|
|
|
24
26
|
const DICT = {
|
|
25
27
|
zh: {
|
|
@@ -43,6 +45,11 @@ window.__ModuleLoader__.load({
|
|
|
43
45
|
sortTitle: '标题',
|
|
44
46
|
showSubagents: '显示子会话',
|
|
45
47
|
selectAll: '全选当前列表',
|
|
48
|
+
expandAll: '展开全部',
|
|
49
|
+
collapseAll: '收起全部',
|
|
50
|
+
loadMore: '加载更多(剩余 {n} 条)',
|
|
51
|
+
selectionScopeHint: '选择仅作用于当前筛选与展开结果。',
|
|
52
|
+
searchAutoExpand: '搜索时自动展开命中工作区。',
|
|
46
53
|
selectRow: '选择会话 {name}',
|
|
47
54
|
justNow: '刚刚',
|
|
48
55
|
titleApplied: '标题已写入。',
|
|
@@ -152,6 +159,7 @@ window.__ModuleLoader__.load({
|
|
|
152
159
|
timeout: '模型调用超时。',
|
|
153
160
|
llmError: '模型调用失败:{message}',
|
|
154
161
|
liveHint: '已打开的会话不能移动或彻底删除;重启 DSH 后重试。',
|
|
162
|
+
moveUnavailableHint: '所选会话中存在不可移动项(如跨工作区、有子会话或状态未就绪)。',
|
|
155
163
|
archiveHint: '归档后会话会从活跃列表移到已归档,可在本页恢复。',
|
|
156
164
|
moveArchivedHint: '移动不会改变归档状态,会话仍会留在已归档。',
|
|
157
165
|
working: '处理中…',
|
|
@@ -185,6 +193,11 @@ window.__ModuleLoader__.load({
|
|
|
185
193
|
sortTitle: 'Title',
|
|
186
194
|
showSubagents: 'Show subagent sessions',
|
|
187
195
|
selectAll: 'Select all shown',
|
|
196
|
+
expandAll: 'Expand all',
|
|
197
|
+
collapseAll: 'Collapse all',
|
|
198
|
+
loadMore: 'Load {n} more',
|
|
199
|
+
selectionScopeHint: 'Selection applies to the current filter and expanded workspaces.',
|
|
200
|
+
searchAutoExpand: 'Matching workspaces expand automatically while searching.',
|
|
188
201
|
selectRow: 'Select session {name}',
|
|
189
202
|
justNow: 'just now',
|
|
190
203
|
titleApplied: 'Title applied.',
|
|
@@ -294,6 +307,7 @@ window.__ModuleLoader__.load({
|
|
|
294
307
|
timeout: 'The model call timed out.',
|
|
295
308
|
llmError: 'The model call failed: {message}',
|
|
296
309
|
liveHint: 'An open session cannot be moved or deleted; restart DSH and retry.',
|
|
310
|
+
moveUnavailableHint: 'Some selected sessions cannot be moved (cross-workspace, subagent descendants, or not ready).',
|
|
297
311
|
archiveHint: 'Archiving moves the session from the active list to the archived list; restore it here.',
|
|
298
312
|
moveArchivedHint: 'Moving does not change the archive state; the session stays archived.',
|
|
299
313
|
working: 'Working…',
|
|
@@ -341,11 +355,33 @@ window.__ModuleLoader__.load({
|
|
|
341
355
|
.dsm-note.warn{border-color:var(--dsw-alias-state-warn-primary)}
|
|
342
356
|
.dsm-note-actions{display:flex;gap:6px;flex-wrap:wrap;flex:0 0 auto}
|
|
343
357
|
.dsm-note detail{flex:1 1 100%}
|
|
358
|
+
.dsm-sticky{position:sticky;top:0;z-index:3;display:flex;flex-direction:column;gap:8px;
|
|
359
|
+
padding:6px 0 8px;background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
344
360
|
.dsm-batch{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;border-radius:7px;
|
|
345
|
-
border:1px dashed var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)
|
|
346
|
-
|
|
361
|
+
border:1px dashed var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1)}
|
|
362
|
+
.dsm-list-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:0 2px}
|
|
347
363
|
.dsm-group{border:1px solid var(--dsw-alias-border-l1);border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-1)}
|
|
348
|
-
.dsm-group-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;
|
|
364
|
+
.dsm-group-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 10px;width:100%;
|
|
365
|
+
background:var(--dsw-alias-bg-layer-2);border:none;font:inherit;color:inherit;text-align:left;cursor:pointer}
|
|
366
|
+
.dsm-group-head:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
|
|
367
|
+
.dsm-group-head:disabled{cursor:default;opacity:.85}
|
|
368
|
+
.dsm-chev{flex:0 0 18px;width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;
|
|
369
|
+
color:var(--dsw-alias-label-secondary)}
|
|
370
|
+
.dsm-row input[type="checkbox"],.dsm-check input[type="checkbox"]{
|
|
371
|
+
appearance:none;-webkit-appearance:none;width:18px;height:18px;margin:0;flex:0 0 auto;
|
|
372
|
+
border:1.5px solid var(--dsw-alias-border-l2);border-radius:4px;background:transparent;
|
|
373
|
+
display:inline-grid;place-content:center;cursor:pointer;transition:background .12s,border-color .12s}
|
|
374
|
+
.dsm-row input[type="checkbox"]::before,.dsm-check input[type="checkbox"]::before{
|
|
375
|
+
content:'';width:9px;height:5px;border-left:2px solid #fff;border-bottom:2px solid #fff;
|
|
376
|
+
transform:rotate(-45deg) scale(0);transition:transform .12s}
|
|
377
|
+
.dsm-row input[type="checkbox"]:checked,.dsm-check input[type="checkbox"]:checked{
|
|
378
|
+
background:#0f766e;border-color:#0f766e}
|
|
379
|
+
.dsm-row input[type="checkbox"]:checked::before,.dsm-check input[type="checkbox"]:checked::before{
|
|
380
|
+
transform:rotate(-45deg) scale(1)}
|
|
381
|
+
.dsm-row input[type="checkbox"]:focus-visible,.dsm-check input[type="checkbox"]:focus-visible{
|
|
382
|
+
outline:2px solid #14b8a6;outline-offset:2px}
|
|
383
|
+
.dsm-more{width:100%;justify-content:center;border-radius:0;border-left:none;border-right:none;
|
|
384
|
+
border-bottom:none;border-top:1px solid var(--dsw-alias-border-l1);min-height:30px}
|
|
349
385
|
.dsm-group-title{font-size:12px;font-weight:600;line-height:18px}
|
|
350
386
|
.dsm-group-path{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);word-break:break-all}
|
|
351
387
|
.dsm-group-meta{margin-left:auto;font-size:11px;color:var(--dsw-alias-label-secondary);white-space:nowrap}
|
|
@@ -634,14 +670,34 @@ window.__ModuleLoader__.load({
|
|
|
634
670
|
)
|
|
635
671
|
}
|
|
636
672
|
|
|
637
|
-
function Group({ group, selected, onToggle, onOpen, t }) {
|
|
673
|
+
function Group({ group, selected, onToggle, onSelect, onOpen, onMore, t, locked }) {
|
|
674
|
+
const remaining = group.rows.length - group.renderedRows.length
|
|
638
675
|
return h(
|
|
639
676
|
'section',
|
|
640
677
|
{ className: 'dsm-group' },
|
|
641
678
|
h(
|
|
642
|
-
'
|
|
643
|
-
{
|
|
644
|
-
|
|
679
|
+
'button',
|
|
680
|
+
{
|
|
681
|
+
type: 'button',
|
|
682
|
+
className: 'dsm-group-head',
|
|
683
|
+
'aria-expanded': group.expanded === true ? 'true' : 'false',
|
|
684
|
+
disabled: locked === true,
|
|
685
|
+
title: locked === true ? t('searchAutoExpand') : undefined,
|
|
686
|
+
onClick: onToggle,
|
|
687
|
+
},
|
|
688
|
+
h('span', { className: 'dsm-chev', 'aria-hidden': 'true' },
|
|
689
|
+
h('svg', {
|
|
690
|
+
viewBox: '0 0 16 16',
|
|
691
|
+
width: 16,
|
|
692
|
+
height: 16,
|
|
693
|
+
fill: 'none',
|
|
694
|
+
stroke: 'currentColor',
|
|
695
|
+
'stroke-width': 1.6,
|
|
696
|
+
'stroke-linecap': 'round',
|
|
697
|
+
'stroke-linejoin': 'round',
|
|
698
|
+
}, h('path', { d: group.expanded === true ? 'M4 6l4 4 4-4' : 'M6 4l4 4-4 4' })),
|
|
699
|
+
),
|
|
700
|
+
h('div', { style: { minWidth: 0, flex: '1 1 auto' } },
|
|
645
701
|
h('div', { className: 'dsm-group-title' }, group.title || t('workspaceUngrouped')),
|
|
646
702
|
group.path ? h('div', { className: 'dsm-group-path' }, group.path) : null,
|
|
647
703
|
group.status !== undefined && group.status !== 'ok'
|
|
@@ -650,16 +706,23 @@ window.__ModuleLoader__.load({
|
|
|
650
706
|
),
|
|
651
707
|
h('div', { className: 'dsm-group-meta' }, t('groupLine', { n: group.rows.length, size: formatBytes(group.bytes) })),
|
|
652
708
|
),
|
|
653
|
-
group.
|
|
709
|
+
group.renderedRows.map((row) =>
|
|
654
710
|
h(Row, {
|
|
655
711
|
key: row.id,
|
|
656
712
|
row,
|
|
657
713
|
selected: selected.has(row.id),
|
|
658
|
-
onToggle: () =>
|
|
714
|
+
onToggle: () => onSelect(row.id),
|
|
659
715
|
onOpen: () => onOpen(row.id),
|
|
660
716
|
t,
|
|
661
717
|
}),
|
|
662
718
|
),
|
|
719
|
+
group.expanded === true && remaining > 0
|
|
720
|
+
? h(
|
|
721
|
+
'button',
|
|
722
|
+
{ type: 'button', className: 'dsm-btn ghost dsm-more', onClick: onMore },
|
|
723
|
+
t('loadMore', { n: remaining }),
|
|
724
|
+
)
|
|
725
|
+
: null,
|
|
663
726
|
)
|
|
664
727
|
}
|
|
665
728
|
|
|
@@ -1086,6 +1149,71 @@ window.__ModuleLoader__.load({
|
|
|
1086
1149
|
// Page
|
|
1087
1150
|
// ---------------------------------------------------------------------
|
|
1088
1151
|
|
|
1152
|
+
/**
|
|
1153
|
+
* Derive the filtered / grouped / rendered view for the list.
|
|
1154
|
+
* Pure so the selection-pruning effect can use it before the loading guard.
|
|
1155
|
+
*/
|
|
1156
|
+
function computeView({ state, tab, workspaceFilter, query, sort, expanded, defaultExpanded, limits }) {
|
|
1157
|
+
const rows = state?.sessions ?? []
|
|
1158
|
+
const workspaceOrder = state?.workspaces ?? []
|
|
1159
|
+
const text = query.trim().toLowerCase()
|
|
1160
|
+
const filtering = text !== ''
|
|
1161
|
+
const filtered = rows.filter((row) => {
|
|
1162
|
+
if (tab === 'active' && row.archived) return false
|
|
1163
|
+
if (tab === 'archived' && !row.archived) return false
|
|
1164
|
+
if (workspaceFilter === 'ungrouped' && row.workspaceId !== null) return false
|
|
1165
|
+
if (workspaceFilter !== 'all' && workspaceFilter !== 'ungrouped' && row.workspaceId !== workspaceFilter) return false
|
|
1166
|
+
if (filtering) {
|
|
1167
|
+
const haystack = `${row.title}\n${row.cwd}\n${row.id}`.toLowerCase()
|
|
1168
|
+
if (!haystack.includes(text)) return false
|
|
1169
|
+
}
|
|
1170
|
+
return true
|
|
1171
|
+
})
|
|
1172
|
+
const sorted = [...filtered].sort((left, right) => {
|
|
1173
|
+
if (sort === 'created') return right.createdAt - left.createdAt
|
|
1174
|
+
if (sort === 'size') return right.sizeBytes - left.sizeBytes
|
|
1175
|
+
if (sort === 'title') return (left.title || left.cwd).localeCompare(right.title || right.cwd)
|
|
1176
|
+
return right.updatedAt - left.updatedAt
|
|
1177
|
+
})
|
|
1178
|
+
const baseGroups = []
|
|
1179
|
+
for (const workspace of workspaceOrder) {
|
|
1180
|
+
const groupRows = sorted.filter((row) => row.workspaceId === workspace.id)
|
|
1181
|
+
if (groupRows.length === 0) continue
|
|
1182
|
+
baseGroups.push({
|
|
1183
|
+
...workspace,
|
|
1184
|
+
rows: groupRows,
|
|
1185
|
+
bytes: groupRows.reduce((sum, row) => sum + row.sizeBytes, 0),
|
|
1186
|
+
})
|
|
1187
|
+
}
|
|
1188
|
+
const ungroupedRows = sorted.filter((row) => row.workspaceId === null)
|
|
1189
|
+
if (ungroupedRows.length > 0) {
|
|
1190
|
+
baseGroups.push({
|
|
1191
|
+
id: 'ungrouped',
|
|
1192
|
+
title: '',
|
|
1193
|
+
path: '',
|
|
1194
|
+
rows: ungroupedRows,
|
|
1195
|
+
bytes: ungroupedRows.reduce((sum, row) => sum + row.sizeBytes, 0),
|
|
1196
|
+
})
|
|
1197
|
+
}
|
|
1198
|
+
const groups = baseGroups.map((group) => {
|
|
1199
|
+
const isExpanded = filtering ? true : (expanded === null ? defaultExpanded : expanded.has(group.id))
|
|
1200
|
+
const limit = limits[group.id] ?? GROUP_PAGE_SIZE
|
|
1201
|
+
return {
|
|
1202
|
+
...group,
|
|
1203
|
+
expanded: isExpanded,
|
|
1204
|
+
limit,
|
|
1205
|
+
renderedRows: isExpanded ? group.rows.slice(0, limit) : [],
|
|
1206
|
+
}
|
|
1207
|
+
})
|
|
1208
|
+
const visibleRows = groups.flatMap((group) => (group.expanded ? group.rows : []))
|
|
1209
|
+
return {
|
|
1210
|
+
filteredRows: sorted,
|
|
1211
|
+
groups,
|
|
1212
|
+
visibleRows,
|
|
1213
|
+
visibleIdSet: new Set(visibleRows.map((row) => row.id)),
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1089
1217
|
function SessionManagerPage(props) {
|
|
1090
1218
|
const { ctx, t } = props
|
|
1091
1219
|
useLocaleTick(ctx)
|
|
@@ -1103,6 +1231,8 @@ window.__ModuleLoader__.load({
|
|
|
1103
1231
|
const [notice, setNotice] = React.useState(null)
|
|
1104
1232
|
const [bulkDeleteIds, setBulkDeleteIds] = React.useState(null)
|
|
1105
1233
|
const [bulkTarget, setBulkTarget] = React.useState('')
|
|
1234
|
+
const [expanded, setExpanded] = React.useState(null)
|
|
1235
|
+
const [limits, setLimits] = React.useState({})
|
|
1106
1236
|
const abortRef = React.useRef(null)
|
|
1107
1237
|
const searchRef = React.useRef(null)
|
|
1108
1238
|
const listScroll = React.useRef(0)
|
|
@@ -1176,6 +1306,37 @@ window.__ModuleLoader__.load({
|
|
|
1176
1306
|
return () => document.removeEventListener('keydown', onKeyDown, true)
|
|
1177
1307
|
}, [selectedId])
|
|
1178
1308
|
|
|
1309
|
+
React.useEffect(() => {
|
|
1310
|
+
setLimits({})
|
|
1311
|
+
}, [tab, workspaceFilter, query, includeSubagents])
|
|
1312
|
+
|
|
1313
|
+
const rows = state?.sessions ?? []
|
|
1314
|
+
const defaultExpanded = (state?.totals?.all ?? 0) <= DEFAULT_COLLAPSE_AFTER
|
|
1315
|
+
const view = React.useMemo(
|
|
1316
|
+
() => computeView({ state, tab, workspaceFilter, query, sort, expanded, defaultExpanded, limits }),
|
|
1317
|
+
[state, tab, workspaceFilter, query, sort, expanded, defaultExpanded, limits],
|
|
1318
|
+
)
|
|
1319
|
+
const viewKey = React.useMemo(
|
|
1320
|
+
() => [
|
|
1321
|
+
state?.generatedAt ?? 0,
|
|
1322
|
+
tab,
|
|
1323
|
+
workspaceFilter,
|
|
1324
|
+
query,
|
|
1325
|
+
includeSubagents ? 1 : 0,
|
|
1326
|
+
defaultExpanded ? 1 : 0,
|
|
1327
|
+
expanded === null ? 'auto' : [...expanded].sort().join(','),
|
|
1328
|
+
].join('|'),
|
|
1329
|
+
[state?.generatedAt, tab, workspaceFilter, query, includeSubagents, defaultExpanded, expanded],
|
|
1330
|
+
)
|
|
1331
|
+
React.useEffect(() => {
|
|
1332
|
+
setSelected((previous) => {
|
|
1333
|
+
if (previous.size === 0) return previous
|
|
1334
|
+
const next = new Set()
|
|
1335
|
+
for (const id of previous) if (view.visibleIdSet.has(id)) next.add(id)
|
|
1336
|
+
return next.size === previous.size ? previous : next
|
|
1337
|
+
})
|
|
1338
|
+
}, [viewKey])
|
|
1339
|
+
|
|
1179
1340
|
const run = async (operation, successText) => {
|
|
1180
1341
|
setBusy(true)
|
|
1181
1342
|
setNotice(null)
|
|
@@ -1294,53 +1455,13 @@ window.__ModuleLoader__.load({
|
|
|
1294
1455
|
return h('div', { className: 'dsm-page' }, h('p', { className: 'dsm-hint' }, h(Spinner, { t, label: t('loading') })))
|
|
1295
1456
|
}
|
|
1296
1457
|
|
|
1297
|
-
const rows = state?.sessions ?? []
|
|
1298
|
-
const workspaceOrder = state?.workspaces ?? []
|
|
1299
1458
|
const tabs = [
|
|
1300
1459
|
{ key: 'all', label: t('tabAll'), count: rows.length },
|
|
1301
1460
|
{ key: 'active', label: t('tabActive'), count: rows.filter((row) => !row.archived).length },
|
|
1302
1461
|
{ key: 'archived', label: t('tabArchived'), count: rows.filter((row) => row.archived).length },
|
|
1303
1462
|
]
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
if (tab === 'archived' && !row.archived) return false
|
|
1307
|
-
if (workspaceFilter === 'ungrouped' && row.workspaceId !== null) return false
|
|
1308
|
-
if (workspaceFilter !== 'all' && workspaceFilter !== 'ungrouped' && row.workspaceId !== workspaceFilter) return false
|
|
1309
|
-
const text = query.trim().toLowerCase()
|
|
1310
|
-
if (text !== '') {
|
|
1311
|
-
const haystack = `${row.title}\n${row.cwd}\n${row.id}`.toLowerCase()
|
|
1312
|
-
if (!haystack.includes(text)) return false
|
|
1313
|
-
}
|
|
1314
|
-
return true
|
|
1315
|
-
})
|
|
1316
|
-
const sorted = [...visible].sort((left, right) => {
|
|
1317
|
-
if (sort === 'created') return right.createdAt - left.createdAt
|
|
1318
|
-
if (sort === 'size') return right.sizeBytes - left.sizeBytes
|
|
1319
|
-
if (sort === 'title') return (left.title || left.cwd).localeCompare(right.title || right.cwd)
|
|
1320
|
-
return right.updatedAt - left.updatedAt
|
|
1321
|
-
})
|
|
1322
|
-
const groups = []
|
|
1323
|
-
for (const workspace of workspaceOrder) {
|
|
1324
|
-
const groupRows = sorted.filter((row) => row.workspaceId === workspace.id)
|
|
1325
|
-
if (groupRows.length === 0) continue
|
|
1326
|
-
groups.push({
|
|
1327
|
-
...workspace,
|
|
1328
|
-
rows: groupRows,
|
|
1329
|
-
bytes: groupRows.reduce((sum, row) => sum + row.sizeBytes, 0),
|
|
1330
|
-
})
|
|
1331
|
-
}
|
|
1332
|
-
const ungroupedRows = sorted.filter((row) => row.workspaceId === null)
|
|
1333
|
-
if (ungroupedRows.length > 0) {
|
|
1334
|
-
groups.push({
|
|
1335
|
-
id: 'ungrouped',
|
|
1336
|
-
title: t('workspaceUngrouped'),
|
|
1337
|
-
path: '',
|
|
1338
|
-
rows: ungroupedRows,
|
|
1339
|
-
bytes: ungroupedRows.reduce((sum, row) => sum + row.sizeBytes, 0),
|
|
1340
|
-
})
|
|
1341
|
-
}
|
|
1342
|
-
const visibleAllSelected = sorted.length > 0 && sorted.every((row) => selected.has(row.id))
|
|
1343
|
-
const selectedRows = rows.filter((row) => selected.has(row.id))
|
|
1463
|
+
const visibleAllSelected = view.visibleRows.length > 0 && view.visibleRows.every((row) => selected.has(row.id))
|
|
1464
|
+
const selectedRows = view.visibleRows.filter((row) => selected.has(row.id))
|
|
1344
1465
|
const overBatch = selectedRows.length > (state.limits?.maxBatch ?? 50)
|
|
1345
1466
|
const overMove = selectedRows.length > (state.limits?.maxMoveBatch ?? 20)
|
|
1346
1467
|
const selectedArchived = selectedRows.length > 0 && !overBatch && selectedRows.every((row) => row.archived)
|
|
@@ -1367,17 +1488,34 @@ window.__ModuleLoader__.load({
|
|
|
1367
1488
|
if (visibleAllSelected) {
|
|
1368
1489
|
setSelected((previous) => {
|
|
1369
1490
|
const next = new Set(previous)
|
|
1370
|
-
for (const row of
|
|
1491
|
+
for (const row of view.visibleRows) next.delete(row.id)
|
|
1371
1492
|
return next
|
|
1372
1493
|
})
|
|
1373
1494
|
} else {
|
|
1374
1495
|
setSelected((previous) => {
|
|
1375
1496
|
const next = new Set(previous)
|
|
1376
|
-
for (const row of
|
|
1497
|
+
for (const row of view.visibleRows) next.add(row.id)
|
|
1377
1498
|
return next
|
|
1378
1499
|
})
|
|
1379
1500
|
}
|
|
1380
1501
|
}
|
|
1502
|
+
const toggleGroup = (id) => {
|
|
1503
|
+
setExpanded((previous) => {
|
|
1504
|
+
const base = previous === null
|
|
1505
|
+
? new Set(view.groups.filter((group) => group.expanded).map((group) => group.id))
|
|
1506
|
+
: new Set(previous)
|
|
1507
|
+
const group = view.groups.find((item) => item.id === id)
|
|
1508
|
+
if (group?.expanded === true) base.delete(id)
|
|
1509
|
+
else base.add(id)
|
|
1510
|
+
return base
|
|
1511
|
+
})
|
|
1512
|
+
}
|
|
1513
|
+
const expandAll = () => setExpanded(new Set(view.groups.map((group) => group.id)))
|
|
1514
|
+
const collapseAll = () => setExpanded(new Set())
|
|
1515
|
+
const loadMore = (id) => {
|
|
1516
|
+
setLimits((previous) => ({ ...previous, [id]: (previous[id] ?? GROUP_PAGE_SIZE) + GROUP_PAGE_SIZE }))
|
|
1517
|
+
}
|
|
1518
|
+
const isSearch = query.trim() !== ''
|
|
1381
1519
|
|
|
1382
1520
|
if (selectedId !== null) {
|
|
1383
1521
|
const row = state.sessions.find((item) => item.id === selectedId)
|
|
@@ -1445,6 +1583,7 @@ window.__ModuleLoader__.load({
|
|
|
1445
1583
|
),
|
|
1446
1584
|
)
|
|
1447
1585
|
: null,
|
|
1586
|
+
h('div', { className: 'dsm-sticky' },
|
|
1448
1587
|
h(
|
|
1449
1588
|
'div',
|
|
1450
1589
|
{ className: 'dsm-toolbar' },
|
|
@@ -1501,7 +1640,7 @@ window.__ModuleLoader__.load({
|
|
|
1501
1640
|
t('showSubagents'),
|
|
1502
1641
|
),
|
|
1503
1642
|
),
|
|
1504
|
-
|
|
1643
|
+
view.visibleRows.length > 0
|
|
1505
1644
|
? h(
|
|
1506
1645
|
'div',
|
|
1507
1646
|
{ className: 'dsm-batch' },
|
|
@@ -1512,6 +1651,7 @@ window.__ModuleLoader__.load({
|
|
|
1512
1651
|
selectedRows.length > 0
|
|
1513
1652
|
? h(React.Fragment, null,
|
|
1514
1653
|
h('span', { className: 'dsm-hint' }, t('selectedCount', { n: selectedRows.length })),
|
|
1654
|
+
h('span', { className: 'dsm-hint' }, t('selectionScopeHint')),
|
|
1515
1655
|
overBatch ? h('span', { className: 'dsm-warn' }, t('tooMany', { max: state.limits?.maxBatch ?? 50 })) : null,
|
|
1516
1656
|
overMove ? h('span', { className: 'dsm-warn' }, t('tooMany', { max: state.limits?.maxMoveBatch ?? 20 })) : null,
|
|
1517
1657
|
h('button', {
|
|
@@ -1558,12 +1698,29 @@ window.__ModuleLoader__.load({
|
|
|
1558
1698
|
onClick: () => setSelected(new Set()),
|
|
1559
1699
|
}, t('clearSelection')),
|
|
1560
1700
|
!selectedMovable && selectedRows.length > 0
|
|
1561
|
-
? h('span', { className: 'dsm-hint' },
|
|
1701
|
+
? h('span', { className: 'dsm-hint' },
|
|
1702
|
+
selectedRows.some((row) => row.live || row.running) ? t('liveHint') : t('moveUnavailableHint'))
|
|
1562
1703
|
: null,
|
|
1563
1704
|
)
|
|
1564
1705
|
: null,
|
|
1565
1706
|
)
|
|
1566
1707
|
: null,
|
|
1708
|
+
),
|
|
1709
|
+
h('div', { className: 'dsm-list-actions' },
|
|
1710
|
+
h('button', {
|
|
1711
|
+
type: 'button',
|
|
1712
|
+
className: 'dsm-btn ghost',
|
|
1713
|
+
disabled: busy || isSearch || view.groups.length === 0 || view.groups.every((group) => group.expanded),
|
|
1714
|
+
onClick: expandAll,
|
|
1715
|
+
}, t('expandAll')),
|
|
1716
|
+
h('button', {
|
|
1717
|
+
type: 'button',
|
|
1718
|
+
className: 'dsm-btn ghost',
|
|
1719
|
+
disabled: busy || isSearch || view.groups.length === 0 || view.groups.every((group) => !group.expanded),
|
|
1720
|
+
onClick: collapseAll,
|
|
1721
|
+
}, t('collapseAll')),
|
|
1722
|
+
isSearch ? h('span', { className: 'dsm-hint' }, t('searchAutoExpand')) : null,
|
|
1723
|
+
),
|
|
1567
1724
|
bulkDeleteIds !== null
|
|
1568
1725
|
? h(DeleteScope, {
|
|
1569
1726
|
ctx,
|
|
@@ -1574,14 +1731,17 @@ window.__ModuleLoader__.load({
|
|
|
1574
1731
|
onConfirmed: (ids) => void deleteRows(ids),
|
|
1575
1732
|
})
|
|
1576
1733
|
: null,
|
|
1577
|
-
groups.length > 0
|
|
1578
|
-
? groups.map((group) =>
|
|
1734
|
+
view.groups.length > 0
|
|
1735
|
+
? view.groups.map((group) =>
|
|
1579
1736
|
h(Group, {
|
|
1580
1737
|
key: group.id,
|
|
1581
1738
|
group,
|
|
1582
1739
|
selected,
|
|
1583
|
-
onToggle:
|
|
1740
|
+
onToggle: () => toggleGroup(group.id),
|
|
1741
|
+
onSelect: toggle,
|
|
1584
1742
|
onOpen: openDetail,
|
|
1743
|
+
onMore: () => loadMore(group.id),
|
|
1744
|
+
locked: isSearch,
|
|
1585
1745
|
t,
|
|
1586
1746
|
}),
|
|
1587
1747
|
)
|
package/docs/01-design.md
CHANGED
|
@@ -202,3 +202,16 @@ targetDir = dirname(locate({id, cwd: target.path}))
|
|
|
202
202
|
- workspace 缓存 seam 是版本敏感点;升级 DSH 后先跑测试与实机验收。
|
|
203
203
|
- 侧栏标题在冷会话重命名后可能要刷新页面才更新(与宿主投影缓存有关)。
|
|
204
204
|
- 0.2.0 候选:侧栏菜单、内容搜索、导出、pin、整枝移动、体检/修复。
|
|
205
|
+
|
|
206
|
+
## 13. 列表视图与选择语义(0.1.1)
|
|
207
|
+
|
|
208
|
+
0.1.1 在 Client 内新增纯函数 `computeView`,把“过滤 → 排序 → 分组 → 折叠 → 每组分页 → 可见集”集中到一处,避免选择集与过滤条件各算各的。
|
|
209
|
+
|
|
210
|
+
- **默认折叠**:`totals.all > 100` 时 `expanded === null` 解释为全部折叠;`<= 100` 解释为全部展开。用户点击后 `expanded` 变成显式 Set。
|
|
211
|
+
- **每组分页**:`limits[groupId]` 默认 50,`加载更多` 每次 +50;只影响 `renderedRows`,不影响 `visibleRows`。
|
|
212
|
+
- **可见集**:`visibleRows = 过滤结果 ∩ 已展开工作区`;搜索时所有命中工作区强制展开(并禁用折叠按钮)。
|
|
213
|
+
- **选择裁剪**:`viewKey` 由 `generatedAt + tab + workspaceFilter + query + includeSubagents + defaultExpanded + expanded` 组成;变化后 `selected = selected ∩ visibleIdSet`,隐藏会话自动取消选择。
|
|
214
|
+
- **批量操作**:只使用 `visibleRows` 中的已选行;`全选当前列表` 只选可见集;批量条提示“选择仅作用于当前筛选与展开结果”。
|
|
215
|
+
- **吸附**:过滤工具栏与批量条包在同一个 `.dsm-sticky`(`position: sticky; top: 0`)中,滚动时整体吸在设置弹窗内容区顶部;批量条不再是独立 sticky。
|
|
216
|
+
|
|
217
|
+
0.2.0 会把 `state` 拆成“工作区概览 + 按工作区分页 sessions”,`computeView` 的过滤/分组逻辑保留,但数据来源从全量数组改为服务端页 + 缓存。
|
|
@@ -42,7 +42,11 @@ gh repo edit ZilongYang/dsh-session-mng \
|
|
|
42
42
|
--add-topic dsh-plugin --add-topic deepseek-harness --add-topic dsh --add-topic session
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
结果(2026-10-27):
|
|
46
|
+
- 公开仓库:https://github.com/ZilongYang/dsh-session-mng
|
|
47
|
+
- 首次 push:main,本地提交 `df3730a`(功能)+ `2f324e6`(文档)
|
|
48
|
+
- tag:`v0.1.0` 已推送;GitHub Release:https://github.com/ZilongYang/dsh-session-mng/releases/tag/v0.1.0
|
|
49
|
+
- topics:`dsh-plugin` / `deepseek-harness` / `dsh` / `session`
|
|
46
50
|
|
|
47
51
|
## 3. npm
|
|
48
52
|
|
|
@@ -60,7 +64,13 @@ curl -s https://registry.npmjs.org/dsh-session-mng/0.1.0 | head -c 300 # 以
|
|
|
60
64
|
- 发布后 packument 可能有约 10 分钟负缓存;以版本端点 / tarball 直链为准。
|
|
61
65
|
- tarball 应包含 28 个文件:`index.js`、`lib/*`、`client.js`、`cordis.patch.yml`、`locale/*.json`、`icon.svg`、`screenshots.json`、`assets/*`、`README.md`、`CHANGELOG.md`、`ROADMAP.md`、`LICENSE`、`docs/*`;不含 `test/`。
|
|
62
66
|
|
|
63
|
-
|
|
67
|
+
结果(2026-10-27):
|
|
68
|
+
- `npm publish` 返回 `PUT 202 Accepted`,npm 先创建了公开占位版本 `0.0.0-stage`(`Temporary package placeholder for staged publishing`),数分钟后公开 packument 出现 `0.1.0`:
|
|
69
|
+
- `curl https://registry.npmjs.org/dsh-session-mng/0.1.0` → **200**
|
|
70
|
+
- `npm view dsh-session-mng version dist.fileCount` → `0.1.0`,`28`
|
|
71
|
+
- `dist.tarball` 直链 200;`npm pack dsh-session-mng@0.1.0` 可下载 28 个文件
|
|
72
|
+
- 注意:`0.0.0-stage` 占位版本仍会出现在 packument 的 versions 列表里,`dist-tags.latest` 是 `0.1.0`;安装时默认取 latest,不受影响。不要把它当作真实版本。
|
|
73
|
+
- 该账号的 token 不能读 `npm profile` / `npm access`(E403),但 publish 可用;后续如需 `npm stage` 子命令,需要 npm CLI ≥11.15.0(当前 11.9.0 没有 `stage`)。
|
|
64
74
|
|
|
65
75
|
## 4. awesome-dsh-plugin / dshmarket
|
|
66
76
|
|
|
@@ -84,7 +94,12 @@ PR 描述建议包含:
|
|
|
84
94
|
|
|
85
95
|
时间:仓库创建满 24h 后再提 PR(或先提,CI 自动重跑变绿)。
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
结果(2026-10-27):
|
|
98
|
+
- PR:https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/9672
|
|
99
|
+
- 只新增一个文件:`data/plugins/ZilongYang__dsh-session-mng.yml`
|
|
100
|
+
- CI 首次运行:entry 格式 / README 一致性 / awesome-lint / 回归测试已通过,site build 进行中;仓库创建于当天,1 天年龄门槛可能先红,workflow 会自动重跑。
|
|
101
|
+
- 已验证 `assets/icon-180.png` 与三张截图的 raw.githubusercontent.com 直链返回 200。
|
|
102
|
+
- PR 描述包含:npm 链接、分类、`dsh.bundle`、截图/图标、与现有条目的差异、实测版本与测试。
|
|
88
103
|
|
|
89
104
|
## 5. 市场验证
|
|
90
105
|
|
package/docs/PLAN-2026-10-26.md
CHANGED
|
@@ -570,3 +570,5 @@ description:
|
|
|
570
570
|
7. **截图使用 demo 数据 + 折叠侧栏**,避免把真实工作区名、会话标题、路径带进公开仓库;三张图分别对应总览、标题提案、删除范围。
|
|
571
571
|
8. **计划里的「子代理级联删除」保留**:实现通过 `sessionQuery.traceSession` 收集后代,确认面板显示根/子数量与总字节;未在真实用户数据上执行,只用测试会话与 demo 预览验证。
|
|
572
572
|
9. **发布步骤尚未执行**:本地 git 初始化/公开仓库、npm publish、awesome-dsh-plugin PR 待用户明确同意后逐项进行;当前 `docs/02-open-source-and-marketplace.md` 与 `docs/RELEASE-CHECKLIST-0.1.0.md` 已写好 runbook 与待勾选项。
|
|
573
|
+
|
|
574
|
+
10. **发布结果(2026-10-27)**:GitHub `ZilongYang/dsh-session-mng` 已公开并推送 `v0.1.0`;npm `dsh-session-mng@0.1.0` 已发布(`npm publish` 返回 202,出现 `0.0.0-stage` 占位版本,数分钟后 `0.1.0` 公开可见);awesome-dsh-plugin PR [#9672](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/9672) 已提交,等待 CI/维护者。
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# dsh-session-mng 0.1.1 实施计划(会话列表可用性修复)
|
|
2
|
+
|
|
3
|
+
> 日期:2026-10-27。来源:子龙在 0.1.0 交付后提出的三个问题。已确认的选择:
|
|
4
|
+
> 1. 大列表:**0.1.1 快修 + 0.2.0 分页**;
|
|
5
|
+
> 2. 选择联动:**裁剪到可见集**;
|
|
6
|
+
> 3. 吸附:**过滤区 + 批量区一体吸附**。
|
|
7
|
+
|
|
8
|
+
## 1. 问题
|
|
9
|
+
|
|
10
|
+
1. 默认平铺全部会话;202 条时 state 约 103 KB,10000 条会到 MB 级、DOM 一万行,首屏和滚动都不可接受。
|
|
11
|
+
2. 只有批量条 sticky,过滤区滚走后看不到当前 tab/工作区/搜索条件。
|
|
12
|
+
3. 已选集合独立于过滤条件;切换筛选后批量操作仍可能作用于不可见会话。
|
|
13
|
+
|
|
14
|
+
## 2. 0.1.1 范围(客户端改动,Host API 不变)
|
|
15
|
+
|
|
16
|
+
### 2.1 工作区优先 + 客户端分页
|
|
17
|
+
|
|
18
|
+
- `总会话数 > 100` 时默认全部工作区折叠;`<= 100` 保持展开。
|
|
19
|
+
- 点击工作区标题展开/收起;提供「展开全部 / 收起全部」。
|
|
20
|
+
- 每个工作区默认渲染 50 条,`加载更多(剩余 N)` 每次 +50。
|
|
21
|
+
- 搜索/筛选命中时自动展开命中的工作区;搜索期间禁用折叠并提示“搜索时自动展开命中工作区”。
|
|
22
|
+
- 分组头显示会话数、总大小;折叠时只渲染头部,不渲染行。
|
|
23
|
+
|
|
24
|
+
### 2.2 吸附
|
|
25
|
+
|
|
26
|
+
- 过滤工具栏 + 批量条合并为一个 `.dsm-sticky`:`position: sticky; top: 0`,背景 `--dsw-alias-bg-base`,底部分隔线;批量条不再是独立 sticky。
|
|
27
|
+
- 列表操作条(展开全部/收起全部、选择范围提示)放在 sticky 块下方,随内容滚动。
|
|
28
|
+
|
|
29
|
+
### 2.3 选择裁剪到可见集
|
|
30
|
+
|
|
31
|
+
- 可见集 = 当前 tab/工作区/搜索过滤后的行 ∩ 已展开的工作区(搜索时自动展开)。
|
|
32
|
+
- 过滤、折叠、搜索、刷新、切换“显示子会话”后,`selected = selected ∩ 可见集`;为空自动清空。
|
|
33
|
+
- 批量操作只使用可见的已选行;`全选当前列表` 只选当前可见集。
|
|
34
|
+
- 批量条加提示:“选择仅作用于当前筛选与展开结果。”
|
|
35
|
+
|
|
36
|
+
## 3. 非目标(留给 0.2.0)
|
|
37
|
+
|
|
38
|
+
- 不做 Host 侧 overview/paged sessions 接口;首屏仍然拉全量 state(0.1.1 只压 DOM,不压 payload)。
|
|
39
|
+
- 不做未分组懒加载、全局内容搜索、虚拟滚动。
|
|
40
|
+
- 10000 条量级的真正分页、计数和全局搜索在 0.2.0 做。
|
|
41
|
+
|
|
42
|
+
## 4. 验收
|
|
43
|
+
|
|
44
|
+
- 202 条真实数据默认折叠;展开单个工作区只渲染 50 行;加载更多正常。
|
|
45
|
+
- 搜索命中时自动展开;折叠在搜索期间禁用。
|
|
46
|
+
- 过滤/折叠/搜索/刷新后已选集合被裁剪;批量操作不触碰隐藏行。
|
|
47
|
+
- 滚动时过滤区 + 批量条一起吸顶;详情打开/返回滚动位置正常。
|
|
48
|
+
- `node --test` 全绿;`npm pack` 正常;实机 UI 截图更新。
|
|
49
|
+
- 0.1.1 发布后验证版本端点;市场条目无需改 YAML(npm 版本自动更新)。
|
|
50
|
+
|
|
51
|
+
## 5. 发布清单
|
|
52
|
+
|
|
53
|
+
- 更新 `package.json` 0.1.1、`CHANGELOG.md`、`ROADMAP.md`、README。
|
|
54
|
+
- 新增 `docs/RELEASE-CHECKLIST-0.1.1.md`。
|
|
55
|
+
- Git commit / tag `v0.1.1` / GitHub Release。
|
|
56
|
+
- `npm publish` 0.1.1;验证 `registry.npmjs.org/dsh-session-mng/0.1.1`。
|
|
57
|
+
- dshmarket/awesome 条目无需重新 PR;确认市场版本随 npm 更新。
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 事后差异(补记,2026-10-27)
|
|
62
|
+
|
|
63
|
+
1. **默认折叠阈值定为 100**:计划只写“>100 默认折叠”,实现按 `totals.all <= 100` 展开、`>100` 折叠;当前 202 条会默认折叠。
|
|
64
|
+
2. **自动展开只对搜索生效**,不是对所有 tab/工作区筛选生效。计划原文写“搜索/筛选命中”,实现选择“搜索命中自动展开;tab/工作区筛选保留折叠状态,由用户点击”。这样避免切 tab 时整页突然铺开;如需“工作区筛选自动展开”可在 0.2.0 数据流重构时一起做。
|
|
65
|
+
3. **可见集 = 过滤结果 ∩ 已展开工作区**;分页只影响渲染,不影响可见集。因此「全选当前列表」会选中已展开工作区中所有过滤行(包括尚未点“加载更多”的行),批量条显示真实数量。这样做是为了避免“分页把已选行变成隐藏行”的歧义。
|
|
66
|
+
4. **批量移动提示拆分**:选中 live 会话时显示原来的“已打开不能移动/删除”;其他不可移动原因(跨工作区、有子会话、状态未就绪)显示新的通用提示。
|
|
67
|
+
5. **新增第 4 张截图** `screenshot-4-expanded.png`,展示工作区展开、选择 2 条活跃会话、sticky 过滤 + 批量条;市场可展示 4 张。
|
|
68
|
+
6. **实现中修了一个行选择事件绑定 bug**:Group 的行 checkbox 最初误调用工作区折叠函数(`toggleGroup`),已拆成 `onSelect` / `onToggle` 两个 prop;该 bug 只存在于工作区,未进入 0.1.0 发布。
|
|
69
|
+
7. **0.1.1 仍是客户端快修**:首屏继续拉全量 state(202 条约 103 KB;10000 条约 5 MB),真正按工作区懒加载/分页在 0.2.0 做。这一点在验收时明确,不把 0.1.1 当成 10000 条的解决方案。
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
- [x] 仓库内没有真实会话 id、用户路径、token、Gmail 提交邮箱。
|
|
51
51
|
- [x] 截图使用 `/Users/demo/...`、`session-demo-*` 假数据。
|
|
52
52
|
- [x] 本地 `git init -b main` + commit `df3730a`(34 个文件,6406 行);`private/` 已被忽略、未入库。
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [ ] 市场 `plugins.json` / dshmarket
|
|
57
|
-
- [
|
|
53
|
+
- [x] 公开仓库 `ZilongYang/dsh-session-mng` + topics(`dsh-plugin` / `deepseek-harness` / `dsh` / `session`);tag `v0.1.0` 已推送。
|
|
54
|
+
- [x] npm `dsh-session-mng@0.1.0` 发布并验证版本端点(200;28 files)。`npm publish` 返回 202,npm 先创建 `0.0.0-stage` 占位版本,数分钟后 `0.1.0` 公开可见。
|
|
55
|
+
- [x] awesome-dsh-plugin PR:[#9672](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/9672)(一个文件;CI pending)。
|
|
56
|
+
- [ ] 市场 `plugins.json` / dshmarket 搜索可见(等待 PR 合并;CI site build 进行中)。
|
|
57
|
+
- [x] GitHub tag `v0.1.0` 已推送;Release 可选(市场优先 npm)。
|
|
58
58
|
|
|
59
59
|
## F. 发版后回填
|
|
60
60
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# dsh-session-mng 0.1.1 发布检查清单
|
|
2
|
+
|
|
3
|
+
> 记录时间:2026-10-27。0.1.1 是客户端可用性修复;Host API 未变。发布完成后回填 npm/GitHub 结果。
|
|
4
|
+
|
|
5
|
+
## A. 代码
|
|
6
|
+
|
|
7
|
+
- [x] `package.json` 版本 `0.1.1`。
|
|
8
|
+
- [x] `node --check index.js client.js lib/*.js` 通过。
|
|
9
|
+
- [x] `node --test` 26 个测试全绿(Host/标题/移动/删除未改)。
|
|
10
|
+
- [x] 新增纯函数 `computeView`:过滤、排序、分组、折叠、每组分页、可见集计算集中在一处。
|
|
11
|
+
- [x] 行选择与工作区折叠事件分开绑定(`onSelect` / `onToggle`),避免勾选行时折叠工作区。
|
|
12
|
+
- [x] 选择裁剪 effect 使用 `viewKey`,覆盖 tab / 工作区 / 搜索 / 展开集合 / 刷新。
|
|
13
|
+
- [x] 批量移动提示区分 live 与一般不可移动项(新增 `moveUnavailableHint`)。
|
|
14
|
+
|
|
15
|
+
## B. 实机 UI(真实 202 条数据只读)
|
|
16
|
+
|
|
17
|
+
- [x] 202 条会话:12 个工作区默认全部折叠,行数 0,批量条隐藏。
|
|
18
|
+
- [x] 展开 `dsh-session-mng`:2 行,批量条出现;勾选行 → `已选 1 个` + “选择仅作用于当前筛选与展开结果”。
|
|
19
|
+
- [x] 切到「已归档」tab:选择裁剪到 0,批量条隐藏。
|
|
20
|
+
- [x] 切回「全部」并展开、勾选一行后切换工作区筛选:选择裁剪到 0,批量条隐藏。
|
|
21
|
+
- [x] 展开 `zilong`(81 条):先渲染 50 行,显示“加载更多(剩余 31 条)”;点击后 81 行。
|
|
22
|
+
- [x] 搜索“DSH 会话管理”:命中工作区自动展开,`aria-expanded=true`,工作区头 `disabled=true`,展开/收起按钮 disabled,显示搜索提示。
|
|
23
|
+
- [x] 搜索无匹配:空态“没有匹配的会话”,选择清空,批量条隐藏。
|
|
24
|
+
- [x] 折叠已选中的工作区:选择裁剪到 0,批量条隐藏。
|
|
25
|
+
- [x] 滚动到 700px:sticky 头仍在 top≈157px(设置弹窗内容区顶部),过滤区与批量条均可见。
|
|
26
|
+
- [x] 详情打开/返回滚动位置仍正常(0.1.0 修复未回归)。
|
|
27
|
+
|
|
28
|
+
## C. 截图
|
|
29
|
+
|
|
30
|
+
- [x] `screenshot-1-overview.png`:120 条 demo 会话、3 个工作区默认折叠(工作区优先)。
|
|
31
|
+
- [x] `screenshot-4-expanded.png`:展开 Atlas,选中 2 条活跃会话,sticky 过滤 + 批量条。
|
|
32
|
+
- [x] `screenshot-2-title.png` / `screenshot-3-delete.png` 保留 0.1.0 的 demo 截图。
|
|
33
|
+
- [x] 所有截图使用 `/Users/demo/...` + `session-demo-*` 假数据,侧栏折叠,不含真实会话。
|
|
34
|
+
- [x] `screenshots.json` 更新为 4 张。
|
|
35
|
+
|
|
36
|
+
## D. 发布
|
|
37
|
+
|
|
38
|
+
- [x] `npm --cache /tmp/dsh-npm-cache pack --dry-run`:31 个文件 / 560.8 kB / unpacked 766.6 kB;包含 4 张截图。
|
|
39
|
+
- [ ] `npm publish` 0.1.1;验证 `registry.npmjs.org/dsh-session-mng/0.1.1`。
|
|
40
|
+
- [ ] Git commit + `git push origin main` + tag `v0.1.1` + GitHub Release。
|
|
41
|
+
- [ ] 确认 npm `latest` = 0.1.1;marketplace 条目无需改 YAML(npm 版本自动更新)。
|
|
42
|
+
- [ ] 在 `docs/02-open-source-and-marketplace.md` 记录 0.1.1 发布结果。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-session-mng",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Session management for DeepSeek Harness: one Settings page for every session — whole-session title generation, archive/restore, cross-workspace move, and permanent deletion of archived sessions.",
|
|
6
6
|
"license": "MIT",
|