dsh-workspace-toolkit 0.0.2
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/LICENSE +21 -0
- package/README.md +115 -0
- package/client.js +606 -0
- package/cordis.patch.yml +7 -0
- package/index.js +8 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gege9527
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# dsh-workspace-toolkit
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-workspace-toolkit)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/deepseek-ai/dsh)
|
|
6
|
+
|
|
7
|
+
> DSH workspace enhancement toolkit: add "Open in File Explorer" and "Batch Archive Sessions" actions to every workspace row in the DSH sidebar.
|
|
8
|
+
>
|
|
9
|
+
> DSH 工作区增强工具包:在资源管理器侧边栏的每个工作区行注入“在文件资源管理器中打开”和“批量归档会话”快捷操作。
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **在文件资源管理器中打开 / Open in File Explorer**
|
|
14
|
+
- 出现在每个 workspace 行右侧的“⋮”菜单中。
|
|
15
|
+
- 调用 DSH 原生 API `session.openWorkspacePath(path)`,由后端跨平台打开目录:
|
|
16
|
+
- Windows → 文件资源管理器
|
|
17
|
+
- macOS → Finder
|
|
18
|
+
- Linux → 默认文件管理器(xdg-open)
|
|
19
|
+
- 若路径尚未获取,会提示先将鼠标悬停到该工作区上以加载 hover card。
|
|
20
|
+
|
|
21
|
+
- **批量归档会话… / Batch Archive Sessions…**
|
|
22
|
+
- 从工作区菜单打开会话列表面板。
|
|
23
|
+
- 列出该工作区下所有非空白、未归档的会话(运行中的会话半透明显示,由用户自行决定是否归档)。
|
|
24
|
+
- 支持全选 / 取消全选、显示更新时间、顺序调用 `uiWorkspace.archiveSession(sessionId)`,单个失败即暂停并提醒,避免并发覆盖 archive set。
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- [Node.js](https://nodejs.org/) >= 18
|
|
29
|
+
- DSH CLI / Web Client(本插件针对 DSH 工作区侧边栏的 DOM 结构实现)
|
|
30
|
+
- Peer dependency: `@deepseek-ai/dsh-tools >= 0.0.1-rc.1`
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### As a DSH plugin(推荐 / Recommended)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 替换 <your-profile> 为实际 profile 名
|
|
38
|
+
dsh plugin --profile <your-profile> add dsh-workspace-toolkit
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
添加后刷新或重启 DSH Web 客户端即可生效。
|
|
42
|
+
|
|
43
|
+
### From npm / 从 npm 安装
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install dsh-workspace-toolkit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
在 DSH profile 中按本地插件引用(示例):
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
# your-profile.yml
|
|
53
|
+
plugins:
|
|
54
|
+
- id: workspace-toolkit
|
|
55
|
+
package: ./node_modules/dsh-workspace-toolkit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> 实际路径和配置方式请遵循你使用的 DSH 版本文档。
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
1. 打开 DSH Web 客户端左侧的工作区(Workspace)侧边栏。
|
|
63
|
+
2. 将鼠标悬停在任一工作区行上,等待 hover card 出现,插件会自动记录该工作区路径。
|
|
64
|
+
3. 点击工作区行右侧的 `⋮`(Workspace actions)菜单。
|
|
65
|
+
4. 选择:
|
|
66
|
+
- **在文件资源管理器中打开 / Open in File Explorer** — 直接打开目录。
|
|
67
|
+
- **批量归档会话… / Batch Archive Sessions…** — 勾选需要归档的会话,点击“归档所选”。
|
|
68
|
+
|
|
69
|
+
## File Structure
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
index.js # Host half entry point. No server-side state is required.
|
|
73
|
+
client.js # Browser half. Observes workspace rows/menus, injects menu items, renders the batch-archive dialog.
|
|
74
|
+
cordis.patch.yml # Cordis bundle patch used by DSH to load the browser plugin.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
- **DOM observation**: 监听 `document.body` 下的 hover card 与 `[role="menu"]`,识别 workspace 菜单后注入两个自定义 `menuitem`。
|
|
80
|
+
- **Workspace path**: 从 DSH hover card 的 `aria-label` 或路径文本提取实际目录路径,并缓存到对应 workspace 行上。
|
|
81
|
+
- **Batch archive**: 打开弹窗时读取 `ctx.workspaces.list` 与 `ctx.sessions.list` 的快照,筛选出可归档会话,按顺序逐个归档。
|
|
82
|
+
- **Internationalization**: 文案根据浏览器 `navigator.language` 在中文与英文间自动切换;暂时无法跟随 DSH 内部语言热切换。
|
|
83
|
+
|
|
84
|
+
## Limitations
|
|
85
|
+
|
|
86
|
+
- 本插件不修改 DSH core,依赖 workspace 行与菜单的 DOM 结构;若 DSH 更新后 DOM 变化,插件可能需要同步更新。
|
|
87
|
+
- 批量归档弹窗基于 `ctx.workspaces.list` 和 `ctx.sessions.list` 渲染,不直接操作会话行 DOM,因此相对稳定。
|
|
88
|
+
- 菜单文案基于 `navigator.language` 简单判断,无法跟随 DSH 内部语言切换。
|
|
89
|
+
- 必须先悬停工作区使 hover card 出现,否则“在资源管理器中打开”可能无法获取路径。
|
|
90
|
+
- 批量归档使用顺序调用以避免并发覆盖;单个失败会暂停并弹窗提示。
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 克隆仓库后可直接在本地引用测试
|
|
96
|
+
git clone https://github.com/<your-username>/dsh-workspace-toolkit.git
|
|
97
|
+
cd dsh-workspace-toolkit
|
|
98
|
+
dsh plugin --profile <your-profile> add .
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
本项目不含构建步骤,源码即分发文件。修改 `client.js` 后刷新 DSH Web 客户端即可看到效果。
|
|
102
|
+
|
|
103
|
+
## Publish Checklist
|
|
104
|
+
|
|
105
|
+
在发布到 npm / 推送 GitHub 前,请确认:
|
|
106
|
+
|
|
107
|
+
- [ ] 修改 `package.json` 中的 `repository.url` 和 `bugs.url` 为你自己的仓库地址。
|
|
108
|
+
- [ ] 修改 `package.json` 中的 `author` 字段(如需要)。
|
|
109
|
+
- [ ] 运行 `npm version <patch|minor|major>` 提升版本号。
|
|
110
|
+
- [ ] 运行 `npm publish` 发布到 npm(需先登录 `npm login`)。
|
|
111
|
+
- [ ] 在 GitHub 创建同名仓库并推送代码。
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
[MIT](LICENSE)
|
package/client.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
// Browser half of dsh-workspace-toolkit.
|
|
2
|
+
// Workspace utility toolkit: open workspace path in file explorer, batch archive
|
|
3
|
+
// sessions, and future file/session operations.
|
|
4
|
+
// Injects items into each workspace row's ellipsis menu using DOM observation.
|
|
5
|
+
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-workspace-toolkit',
|
|
8
|
+
factory: function (require) {
|
|
9
|
+
var module = { exports: {} }
|
|
10
|
+
var exports = module.exports
|
|
11
|
+
|
|
12
|
+
var NS = 'dsh-workspace-toolkit'
|
|
13
|
+
|
|
14
|
+
// ---- Lightweight locale (DSH locale switching is not reachable from an external plugin). ----
|
|
15
|
+
function isZh() {
|
|
16
|
+
var lang = (typeof navigator !== 'undefined' && navigator.language) || 'en'
|
|
17
|
+
return lang.indexOf('zh') === 0
|
|
18
|
+
}
|
|
19
|
+
var LABELS = {
|
|
20
|
+
openInExplorer: isZh() ? '在文件资源管理器中打开' : 'Open in File Explorer',
|
|
21
|
+
pathNotFound: isZh() ? '未找到工作区路径,请先将鼠标悬停在该工作区上。' : 'Workspace path not found. Please hover over the workspace first.',
|
|
22
|
+
openFailed: isZh() ? '打开失败:' : 'Failed to open: ',
|
|
23
|
+
batchArchive: isZh() ? '批量归档会话…' : 'Batch Archive Sessions…',
|
|
24
|
+
batchArchiveTitle: isZh() ? '批量归档会话' : 'Batch Archive Sessions',
|
|
25
|
+
selectAll: isZh() ? '全选' : 'Select all',
|
|
26
|
+
archiveSelected: isZh() ? '归档所选' : 'Archive selected',
|
|
27
|
+
cancel: isZh() ? '取消' : 'Cancel',
|
|
28
|
+
close: isZh() ? '关闭' : 'Close',
|
|
29
|
+
noSessions: isZh() ? '该工作区下没有可归档的会话。' : 'No archivable sessions in this workspace.',
|
|
30
|
+
loading: isZh() ? '加载中…' : 'Loading…',
|
|
31
|
+
archiveFailed: isZh() ? '归档失败:' : 'Archive failed: ',
|
|
32
|
+
archivedCount: isZh() ? '已归档 {n} 个会话' : 'Archived {n} sessions',
|
|
33
|
+
selectedCount: isZh() ? '已选择 {n} 个' : '{n} selected',
|
|
34
|
+
workspaceNotFound: isZh() ? '未找到该工作区,请重试。' : 'Workspace not found. Please try again.'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---- Workspace row / path tracking ----
|
|
38
|
+
var lastMouseX = 0
|
|
39
|
+
var lastMouseY = 0
|
|
40
|
+
var pendingWorkspace = null // { workspaceId, path, title, expires }
|
|
41
|
+
|
|
42
|
+
function isWorkspaceMenuButton(btn) {
|
|
43
|
+
var label = btn.getAttribute('aria-label') || ''
|
|
44
|
+
return label.indexOf('Workspace actions') !== -1 ||
|
|
45
|
+
(label.indexOf('工作区') !== -1 && label.indexOf('的操作') !== -1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function findWorkspaceRow(el) {
|
|
49
|
+
while (el && el !== document.body && el !== document.documentElement) {
|
|
50
|
+
if (el.getAttribute && el.getAttribute('role') === 'treeitem') return el
|
|
51
|
+
el = el.parentElement
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function looksLikePath(text) {
|
|
57
|
+
return /^([/~]|[a-zA-Z]:\\)/.test(text.trim())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractPathFromHoverCard(card) {
|
|
61
|
+
var ariaLabel = card.getAttribute('aria-label')
|
|
62
|
+
if (ariaLabel) {
|
|
63
|
+
var idx = ariaLabel.indexOf(': ')
|
|
64
|
+
if (idx !== -1) {
|
|
65
|
+
var realPath = ariaLabel.slice(idx + 2).trim()
|
|
66
|
+
if (looksLikePath(realPath)) return realPath
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var pathEl = card.querySelector('[class*="hoverPath"]')
|
|
70
|
+
if (pathEl) return (pathEl.textContent || '').trim()
|
|
71
|
+
var divs = card.querySelectorAll('div')
|
|
72
|
+
for (var i = 0; i < divs.length; i++) {
|
|
73
|
+
var text = (divs[i].textContent || '').trim()
|
|
74
|
+
if (looksLikePath(text)) return text
|
|
75
|
+
}
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Workspace cache populated from ctx.workspaces.list
|
|
80
|
+
var workspaceItems = []
|
|
81
|
+
var archivedSessionIds = []
|
|
82
|
+
|
|
83
|
+
function extractTitleFromAria(label) {
|
|
84
|
+
var m = label.match(/工作区“([^”]+)”的操作/)
|
|
85
|
+
if (m) return m[1]
|
|
86
|
+
m = label.match(/Workspace actions for (.+)/)
|
|
87
|
+
if (m) return m[1]
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function findWorkspaceItemByTitle(title) {
|
|
92
|
+
for (var i = 0; i < workspaceItems.length; i++) {
|
|
93
|
+
if (workspaceItems[i].title === title) return workspaceItems[i]
|
|
94
|
+
}
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findWorkspaceItemById(id) {
|
|
99
|
+
for (var i = 0; i < workspaceItems.length; i++) {
|
|
100
|
+
if (workspaceItems[i].workspaceId === id) return workspaceItems[i]
|
|
101
|
+
}
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Session cache populated from ctx.sessions.list
|
|
106
|
+
var sessionById = {}
|
|
107
|
+
var sessionIds = []
|
|
108
|
+
var sessionCurrent = null
|
|
109
|
+
var sessionPhase = 'idle'
|
|
110
|
+
|
|
111
|
+
// Pre-fetch the workspace path whenever a hover card appears.
|
|
112
|
+
var hoverObserver = new MutationObserver(function (mutations) {
|
|
113
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
114
|
+
var added = mutations[i].addedNodes
|
|
115
|
+
for (var j = 0; j < added.length; j++) {
|
|
116
|
+
var node = added[j]
|
|
117
|
+
if (node.nodeType !== 1) continue
|
|
118
|
+
var style = window.getComputedStyle(node)
|
|
119
|
+
if (style.position !== 'fixed') continue
|
|
120
|
+
var className = typeof node.className === 'string' ? node.className : ''
|
|
121
|
+
var hasHoverPath = node.querySelector('[class*="hoverPath"]') !== null
|
|
122
|
+
if (className.indexOf('card') === -1 && !hasHoverPath) continue
|
|
123
|
+
var path = extractPathFromHoverCard(node)
|
|
124
|
+
if (!path) continue
|
|
125
|
+
var target = document.elementFromPoint(lastMouseX, lastMouseY)
|
|
126
|
+
var row = target ? findWorkspaceRow(target) : null
|
|
127
|
+
if (row) {
|
|
128
|
+
row.setAttribute('data-dsh-open-workspace-path', path)
|
|
129
|
+
var item = null
|
|
130
|
+
for (var k = 0; k < workspaceItems.length; k++) {
|
|
131
|
+
if (workspaceItems[k].path === path) { item = workspaceItems[k]; break }
|
|
132
|
+
}
|
|
133
|
+
if (item) row.setAttribute('data-dsh-open-workspace-id', item.workspaceId)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
document.addEventListener('mousemove', function (e) {
|
|
140
|
+
lastMouseX = e.clientX
|
|
141
|
+
lastMouseY = e.clientY
|
|
142
|
+
}, true)
|
|
143
|
+
|
|
144
|
+
document.addEventListener('pointerdown', function (e) {
|
|
145
|
+
var target = e.target
|
|
146
|
+
if (!(target instanceof Element)) return
|
|
147
|
+
var btn = target.closest('button[aria-label]')
|
|
148
|
+
if (!btn) return
|
|
149
|
+
var label = btn.getAttribute('aria-label') || ''
|
|
150
|
+
var isSessionMenu = label.indexOf('Session actions') !== -1 ||
|
|
151
|
+
(label.indexOf('会话') !== -1 && label.indexOf('的操作') !== -1)
|
|
152
|
+
if (isSessionMenu) {
|
|
153
|
+
pendingWorkspace = null
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
if (!isWorkspaceMenuButton(btn)) return
|
|
157
|
+
var row = findWorkspaceRow(btn)
|
|
158
|
+
if (!row) return
|
|
159
|
+
var title = extractTitleFromAria(label)
|
|
160
|
+
var item = title ? findWorkspaceItemByTitle(title) : null
|
|
161
|
+
var path = (item && item.path)
|
|
162
|
+
|| row.getAttribute('data-dsh-open-workspace-path')
|
|
163
|
+
|| null
|
|
164
|
+
var workspaceId = (item && item.workspaceId)
|
|
165
|
+
|| row.getAttribute('data-dsh-open-workspace-id')
|
|
166
|
+
|| null
|
|
167
|
+
// 只在真正的单个工作区行菜单注入;跳过会话菜单和最顶层工作区菜单。
|
|
168
|
+
if (!workspaceId) return
|
|
169
|
+
pendingWorkspace = {
|
|
170
|
+
workspaceId: workspaceId,
|
|
171
|
+
path: path,
|
|
172
|
+
title: title || (item && item.title) || '',
|
|
173
|
+
expires: Date.now() + 10000
|
|
174
|
+
}
|
|
175
|
+
}, true)
|
|
176
|
+
|
|
177
|
+
// ---- Open path via Typert RPC over HTTP ----
|
|
178
|
+
function makeRpcId() {
|
|
179
|
+
return Date.now().toString(36) + Math.random().toString(36).slice(2)
|
|
180
|
+
}
|
|
181
|
+
function callOpenWorkspacePath(path) {
|
|
182
|
+
var token = typeof location !== 'undefined' ? new URLSearchParams(location.search).get('token') : null
|
|
183
|
+
var url = '/api/session/openWorkspacePath' + (token ? '?token=' + encodeURIComponent(token) : '')
|
|
184
|
+
var rpcId = makeRpcId()
|
|
185
|
+
return fetch(url, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: { 'Content-Type': 'application/json' },
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
type: 'client-request',
|
|
190
|
+
rpcId: rpcId,
|
|
191
|
+
method: 'session/openWorkspacePath',
|
|
192
|
+
payload: { args: { request: { path } } }
|
|
193
|
+
})
|
|
194
|
+
}).then(function (res) {
|
|
195
|
+
if (!res.ok) throw new Error('HTTP ' + res.status + ' ' + res.statusText)
|
|
196
|
+
return res.json()
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- Batch archive dialog ----
|
|
201
|
+
var archiveOverlay = null
|
|
202
|
+
var archivePanel = null
|
|
203
|
+
|
|
204
|
+
function formatTime(t) {
|
|
205
|
+
if (!t) return ''
|
|
206
|
+
var d = new Date(t)
|
|
207
|
+
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0') + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function template(fmt, vars) {
|
|
211
|
+
return fmt.replace(/\{([^{}]+)\}/g, function (_, key) {
|
|
212
|
+
return vars[key] !== undefined ? String(vars[key]) : ''
|
|
213
|
+
})
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function closeArchiveDialog() {
|
|
217
|
+
if (archiveOverlay && archiveOverlay.parentNode) {
|
|
218
|
+
archiveOverlay.parentNode.removeChild(archiveOverlay)
|
|
219
|
+
}
|
|
220
|
+
archiveOverlay = null
|
|
221
|
+
archivePanel = null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function openArchiveDialog() {
|
|
225
|
+
closeArchiveDialog()
|
|
226
|
+
if (!pendingWorkspace || !pendingWorkspace.workspaceId) {
|
|
227
|
+
window.alert(LABELS.workspaceNotFound)
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
var item = findWorkspaceItemById(pendingWorkspace.workspaceId)
|
|
231
|
+
if (!item) {
|
|
232
|
+
window.alert(LABELS.workspaceNotFound)
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
var archivedSet = {}
|
|
237
|
+
for (var i = 0; i < archivedSessionIds.length; i++) archivedSet[archivedSessionIds[i]] = true
|
|
238
|
+
|
|
239
|
+
// 延迟读取 sessions 快照,避免应用启动/订阅时因 inactive context 抛错。
|
|
240
|
+
if (ctx.sessions && ctx.sessions.list) {
|
|
241
|
+
try {
|
|
242
|
+
var sessionSnap = ctx.sessions.list.getSnapshot()
|
|
243
|
+
sessionIds = sessionSnap.ids || []
|
|
244
|
+
sessionCurrent = sessionSnap.current || null
|
|
245
|
+
sessionById = sessionSnap.byId || {}
|
|
246
|
+
sessionPhase = sessionSnap.phase || 'idle'
|
|
247
|
+
} catch (err) {
|
|
248
|
+
// inactive context 等情况下静默跳过
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
var workspaceSessionIds = item.sessionIds || []
|
|
253
|
+
var candidates = []
|
|
254
|
+
for (var j = 0; j < workspaceSessionIds.length; j++) {
|
|
255
|
+
var sid = workspaceSessionIds[j]
|
|
256
|
+
var s = sessionById[sid]
|
|
257
|
+
if (!s) continue
|
|
258
|
+
if (s.blank) continue
|
|
259
|
+
if (archivedSet[sid]) continue
|
|
260
|
+
candidates.push({ id: sid, title: s.displayTitle || s.title || sid, updatedAt: s.updatedAt, running: s.running })
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
archiveOverlay = document.createElement('div')
|
|
264
|
+
archiveOverlay.className = 'dsh-batch-archive-overlay'
|
|
265
|
+
archivePanel = document.createElement('div')
|
|
266
|
+
archivePanel.className = 'dsh-batch-archive-panel'
|
|
267
|
+
archivePanel.setAttribute('role', 'dialog')
|
|
268
|
+
archivePanel.setAttribute('aria-modal', 'true')
|
|
269
|
+
archivePanel.setAttribute('aria-label', LABELS.batchArchiveTitle)
|
|
270
|
+
|
|
271
|
+
var header = document.createElement('div')
|
|
272
|
+
header.className = 'dsh-batch-archive-header'
|
|
273
|
+
var titleEl = document.createElement('div')
|
|
274
|
+
titleEl.className = 'dsh-batch-archive-title'
|
|
275
|
+
titleEl.textContent = LABELS.batchArchiveTitle + ' — ' + item.title
|
|
276
|
+
var closeBtn = document.createElement('button')
|
|
277
|
+
closeBtn.type = 'button'
|
|
278
|
+
closeBtn.className = 'dsh-batch-archive-close'
|
|
279
|
+
closeBtn.setAttribute('aria-label', LABELS.close)
|
|
280
|
+
closeBtn.textContent = '×'
|
|
281
|
+
closeBtn.addEventListener('click', closeArchiveDialog)
|
|
282
|
+
header.appendChild(titleEl)
|
|
283
|
+
header.appendChild(closeBtn)
|
|
284
|
+
archivePanel.appendChild(header)
|
|
285
|
+
|
|
286
|
+
var body = document.createElement('div')
|
|
287
|
+
body.className = 'dsh-batch-archive-body'
|
|
288
|
+
|
|
289
|
+
var selectedIds = []
|
|
290
|
+
var listContainer = null
|
|
291
|
+
var selectAllCheckbox = null
|
|
292
|
+
var statusEl = null
|
|
293
|
+
var archiveBtn = null
|
|
294
|
+
|
|
295
|
+
function updateState() {
|
|
296
|
+
if (statusEl) statusEl.textContent = template(LABELS.selectedCount, { n: selectedIds.length })
|
|
297
|
+
if (archiveBtn) archiveBtn.disabled = selectedIds.length === 0
|
|
298
|
+
if (selectAllCheckbox && listContainer) {
|
|
299
|
+
var boxes = listContainer.querySelectorAll('input[type="checkbox"][data-session-id]')
|
|
300
|
+
var allChecked = boxes.length > 0
|
|
301
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
302
|
+
if (!boxes[i].checked) { allChecked = false; break }
|
|
303
|
+
}
|
|
304
|
+
selectAllCheckbox.checked = allChecked
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (candidates.length === 0) {
|
|
309
|
+
var emptyEl = document.createElement('div')
|
|
310
|
+
emptyEl.className = 'dsh-batch-archive-empty'
|
|
311
|
+
emptyEl.textContent = LABELS.noSessions
|
|
312
|
+
body.appendChild(emptyEl)
|
|
313
|
+
} else {
|
|
314
|
+
var toolbar = document.createElement('div')
|
|
315
|
+
toolbar.className = 'dsh-batch-archive-toolbar'
|
|
316
|
+
var selectAllLabel = document.createElement('label')
|
|
317
|
+
selectAllLabel.className = 'dsh-batch-archive-selectall'
|
|
318
|
+
selectAllCheckbox = document.createElement('input')
|
|
319
|
+
selectAllCheckbox.type = 'checkbox'
|
|
320
|
+
var selectAllText = document.createElement('span')
|
|
321
|
+
selectAllText.textContent = LABELS.selectAll
|
|
322
|
+
selectAllLabel.appendChild(selectAllCheckbox)
|
|
323
|
+
selectAllLabel.appendChild(selectAllText)
|
|
324
|
+
statusEl = document.createElement('span')
|
|
325
|
+
statusEl.className = 'dsh-batch-archive-status'
|
|
326
|
+
toolbar.appendChild(selectAllLabel)
|
|
327
|
+
toolbar.appendChild(statusEl)
|
|
328
|
+
body.appendChild(toolbar)
|
|
329
|
+
|
|
330
|
+
listContainer = document.createElement('div')
|
|
331
|
+
listContainer.className = 'dsh-batch-archive-list'
|
|
332
|
+
for (var c = 0; c < candidates.length; c++) {
|
|
333
|
+
(function (candidate) {
|
|
334
|
+
var row = document.createElement('label')
|
|
335
|
+
row.className = 'dsh-batch-archive-row'
|
|
336
|
+
if (candidate.running) row.className += ' dsh-batch-archive-running'
|
|
337
|
+
var checkbox = document.createElement('input')
|
|
338
|
+
checkbox.type = 'checkbox'
|
|
339
|
+
checkbox.setAttribute('data-session-id', candidate.id)
|
|
340
|
+
checkbox.addEventListener('change', function () {
|
|
341
|
+
var idx = selectedIds.indexOf(candidate.id)
|
|
342
|
+
if (checkbox.checked && idx === -1) selectedIds.push(candidate.id)
|
|
343
|
+
if (!checkbox.checked && idx !== -1) selectedIds.splice(idx, 1)
|
|
344
|
+
updateState()
|
|
345
|
+
})
|
|
346
|
+
var labelSpan = document.createElement('span')
|
|
347
|
+
labelSpan.className = 'dsh-batch-archive-name'
|
|
348
|
+
labelSpan.textContent = candidate.title
|
|
349
|
+
var timeSpan = document.createElement('span')
|
|
350
|
+
timeSpan.className = 'dsh-batch-archive-time'
|
|
351
|
+
timeSpan.textContent = formatTime(candidate.updatedAt)
|
|
352
|
+
row.appendChild(checkbox)
|
|
353
|
+
row.appendChild(labelSpan)
|
|
354
|
+
row.appendChild(timeSpan)
|
|
355
|
+
listContainer.appendChild(row)
|
|
356
|
+
})(candidates[c])
|
|
357
|
+
}
|
|
358
|
+
body.appendChild(listContainer)
|
|
359
|
+
|
|
360
|
+
selectAllCheckbox.addEventListener('change', function () {
|
|
361
|
+
var boxes = listContainer.querySelectorAll('input[type="checkbox"][data-session-id]')
|
|
362
|
+
for (var i = 0; i < boxes.length; i++) {
|
|
363
|
+
var sid = boxes[i].getAttribute('data-session-id')
|
|
364
|
+
boxes[i].checked = selectAllCheckbox.checked
|
|
365
|
+
var idx = selectedIds.indexOf(sid)
|
|
366
|
+
if (selectAllCheckbox.checked && idx === -1) selectedIds.push(sid)
|
|
367
|
+
if (!selectAllCheckbox.checked && idx !== -1) selectedIds.splice(idx, 1)
|
|
368
|
+
}
|
|
369
|
+
updateState()
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
archivePanel.appendChild(body)
|
|
374
|
+
|
|
375
|
+
var footer = document.createElement('div')
|
|
376
|
+
footer.className = 'dsh-batch-archive-footer'
|
|
377
|
+
archiveBtn = document.createElement('button')
|
|
378
|
+
archiveBtn.type = 'button'
|
|
379
|
+
archiveBtn.className = 'dsh-batch-archive-primary'
|
|
380
|
+
archiveBtn.textContent = LABELS.archiveSelected
|
|
381
|
+
archiveBtn.disabled = true
|
|
382
|
+
archiveBtn.addEventListener('click', function () {
|
|
383
|
+
if (selectedIds.length === 0) return
|
|
384
|
+
if (typeof ctx === 'undefined' || !ctx.uiWorkspace || typeof ctx.uiWorkspace.archiveSession !== 'function') {
|
|
385
|
+
window.alert(LABELS.archiveFailed + 'uiWorkspace.archiveSession not available')
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
archiveBtn.disabled = true
|
|
389
|
+
if (cancelBtn) cancelBtn.disabled = true
|
|
390
|
+
archiveBtn.textContent = LABELS.loading
|
|
391
|
+
|
|
392
|
+
var failedOnce = false
|
|
393
|
+
function archiveNext(i) {
|
|
394
|
+
if (i >= selectedIds.length) {
|
|
395
|
+
if (!failedOnce) {
|
|
396
|
+
window.alert(template(LABELS.archivedCount, { n: selectedIds.length }))
|
|
397
|
+
closeArchiveDialog()
|
|
398
|
+
} else {
|
|
399
|
+
archiveBtn.disabled = false
|
|
400
|
+
if (cancelBtn) cancelBtn.disabled = false
|
|
401
|
+
archiveBtn.textContent = LABELS.archiveSelected
|
|
402
|
+
}
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
ctx.uiWorkspace.archiveSession(selectedIds[i]).then(function () {
|
|
406
|
+
archiveNext(i + 1)
|
|
407
|
+
}).catch(function (err) {
|
|
408
|
+
failedOnce = true
|
|
409
|
+
console.error('[' + NS + '] batch archive error at ' + selectedIds[i] + ':', err)
|
|
410
|
+
window.alert(LABELS.archiveFailed + (err && err.message ? err.message : String(err)))
|
|
411
|
+
archiveBtn.disabled = false
|
|
412
|
+
if (cancelBtn) cancelBtn.disabled = false
|
|
413
|
+
archiveBtn.textContent = LABELS.archiveSelected
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
archiveNext(0)
|
|
417
|
+
})
|
|
418
|
+
var cancelBtn = document.createElement('button')
|
|
419
|
+
cancelBtn.type = 'button'
|
|
420
|
+
cancelBtn.className = 'dsh-batch-archive-secondary'
|
|
421
|
+
cancelBtn.textContent = LABELS.cancel
|
|
422
|
+
cancelBtn.addEventListener('click', closeArchiveDialog)
|
|
423
|
+
footer.appendChild(cancelBtn)
|
|
424
|
+
footer.appendChild(archiveBtn)
|
|
425
|
+
archivePanel.appendChild(footer)
|
|
426
|
+
|
|
427
|
+
archiveOverlay.appendChild(archivePanel)
|
|
428
|
+
document.body.appendChild(archiveOverlay)
|
|
429
|
+
updateState()
|
|
430
|
+
|
|
431
|
+
archiveOverlay.addEventListener('click', function (e) {
|
|
432
|
+
if (e.target === archiveOverlay) closeArchiveDialog()
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---- Menu injection ----
|
|
437
|
+
var EXPLORER_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><polyline points="2 12 12 12 16 8"></polyline></svg>'
|
|
438
|
+
var ARCHIVE_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8v13H3V8"></path><path d="M1 3h22v5H1z"></path><path d="M10 12h4"></path></svg>'
|
|
439
|
+
|
|
440
|
+
function makeMenuItem(ref, label, iconSvg, markerAttr, onClick, extraClass) {
|
|
441
|
+
var item = document.createElement('button')
|
|
442
|
+
item.type = 'button'
|
|
443
|
+
item.setAttribute('role', 'menuitem')
|
|
444
|
+
item.setAttribute(markerAttr, 'true')
|
|
445
|
+
item.className = ref.className + (extraClass ? ' ' + extraClass : '')
|
|
446
|
+
var refChildren = ref.children
|
|
447
|
+
var iconClass = refChildren[0] ? refChildren[0].className : ''
|
|
448
|
+
var labelClass = refChildren[1] ? refChildren[1].className : ''
|
|
449
|
+
var iconSpan = document.createElement('span')
|
|
450
|
+
if (iconClass) iconSpan.className = iconClass
|
|
451
|
+
iconSpan.innerHTML = iconSvg
|
|
452
|
+
var labelSpan = document.createElement('span')
|
|
453
|
+
if (labelClass) labelSpan.className = labelClass
|
|
454
|
+
labelSpan.textContent = label
|
|
455
|
+
item.appendChild(iconSpan)
|
|
456
|
+
item.appendChild(labelSpan)
|
|
457
|
+
item.addEventListener('click', function (e) {
|
|
458
|
+
e.stopPropagation()
|
|
459
|
+
onClick()
|
|
460
|
+
})
|
|
461
|
+
return item
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function injectMenuItem(menu) {
|
|
465
|
+
if (menu.querySelector('[data-dsh-open-workspace-item]') && menu.querySelector('[data-dsh-batch-archive-item]')) return
|
|
466
|
+
var ref = menu.querySelector('[role="menuitem"]')
|
|
467
|
+
if (!ref) return
|
|
468
|
+
|
|
469
|
+
if (!menu.querySelector('[data-dsh-open-workspace-item]')) {
|
|
470
|
+
var explorerItem = makeMenuItem(ref, LABELS.openInExplorer, EXPLORER_ICON_SVG, 'data-dsh-open-workspace-item', function () {
|
|
471
|
+
console.log('[' + NS + '] open in explorer clicked, pendingWorkspace=', pendingWorkspace)
|
|
472
|
+
if (!pendingWorkspace || !pendingWorkspace.path) {
|
|
473
|
+
window.alert(LABELS.pathNotFound)
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
console.log('[' + NS + '] calling openWorkspacePath with path:', pendingWorkspace.path)
|
|
477
|
+
callOpenWorkspacePath(pendingWorkspace.path)
|
|
478
|
+
.then(function (result) {
|
|
479
|
+
console.log('[' + NS + '] openWorkspacePath result:', result)
|
|
480
|
+
var ok = result.ok !== false || result.opened === true
|
|
481
|
+
if (!ok) {
|
|
482
|
+
console.error('[' + NS + '] openWorkspacePath failed:', result.error)
|
|
483
|
+
window.alert(LABELS.openFailed + (result.error ? result.error.message : ''))
|
|
484
|
+
}
|
|
485
|
+
})
|
|
486
|
+
.catch(function (err) {
|
|
487
|
+
console.error('[' + NS + '] openWorkspacePath error:', err)
|
|
488
|
+
window.alert(LABELS.openFailed + (err && err.message ? err.message : String(err)))
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
if (ref.parentElement) ref.parentElement.insertBefore(explorerItem, ref)
|
|
492
|
+
else menu.appendChild(explorerItem)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (!menu.querySelector('[data-dsh-batch-archive-item]')) {
|
|
496
|
+
var archiveItem = makeMenuItem(ref, LABELS.batchArchive, ARCHIVE_ICON_SVG, 'data-dsh-batch-archive-item', function () {
|
|
497
|
+
openArchiveDialog()
|
|
498
|
+
})
|
|
499
|
+
if (ref.parentElement) {
|
|
500
|
+
var sibling = menu.querySelector('[data-dsh-open-workspace-item]') || ref
|
|
501
|
+
if (sibling.nextSibling) ref.parentElement.insertBefore(archiveItem, sibling.nextSibling)
|
|
502
|
+
else ref.parentElement.appendChild(archiveItem)
|
|
503
|
+
} else {
|
|
504
|
+
menu.appendChild(archiveItem)
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
var menuObserver = new MutationObserver(function (mutations) {
|
|
510
|
+
if (!pendingWorkspace || Date.now() > pendingWorkspace.expires) return
|
|
511
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
512
|
+
var added = mutations[i].addedNodes
|
|
513
|
+
for (var j = 0; j < added.length; j++) {
|
|
514
|
+
var node = added[j]
|
|
515
|
+
if (node.nodeType !== 1) continue
|
|
516
|
+
var menu = (node.getAttribute && node.getAttribute('role') === 'menu') ? node : null
|
|
517
|
+
if (!menu && node.querySelector) menu = node.querySelector('[role="menu"]')
|
|
518
|
+
if (menu) injectMenuItem(menu)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
// ---- Styles for the archive dialog ----
|
|
524
|
+
function injectStyles() {
|
|
525
|
+
if (document.getElementById('dsh-batch-archive-styles')) return
|
|
526
|
+
var style = document.createElement('style')
|
|
527
|
+
style.id = 'dsh-batch-archive-styles'
|
|
528
|
+
style.textContent = [
|
|
529
|
+
'.dsh-batch-archive-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask-1, rgba(0,0,0,0.45)); z-index: 2147483640; display: flex; align-items: center; justify-content: center; }',
|
|
530
|
+
'.dsh-batch-archive-panel { background: var(--dsw-alias-bg-layer-2, #fff); color: var(--dsw-alias-label-primary, #1f2328); border: 1px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.1)); border-radius: 14px; box-shadow: var(--dsw-elevation-prominent, 0 10px 40px rgba(0,0,0,0.25)); width: min(480px, calc(100vw - 32px)); max-height: min(640px, calc(100vh - 48px)); display: flex; flex-direction: column; overflow: hidden; }',
|
|
531
|
+
'.dsh-batch-archive-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); }',
|
|
532
|
+
'.dsh-batch-archive-title { font-size: 15px; font-weight: 600; line-height: 1.35; color: var(--dsw-alias-label-primary, inherit); }',
|
|
533
|
+
'.dsh-batch-archive-close { background: transparent; border: none; color: var(--dsw-alias-label-tertiary, #6b7280); font-size: 20px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: 6px; }',
|
|
534
|
+
'.dsh-batch-archive-close:hover { color: var(--dsw-alias-label-primary, #111827); background: var(--dsw-alias-interactive-bg-hover, transparent); }',
|
|
535
|
+
'.dsh-batch-archive-body { padding: 12px 18px; overflow-y: auto; flex: 1; }',
|
|
536
|
+
'.dsh-batch-archive-empty { padding: 24px 0; text-align: center; color: var(--dsw-alias-label-secondary, #6b7280); }',
|
|
537
|
+
'.dsh-batch-archive-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; font-size: 13px; color: var(--dsw-alias-label-secondary, #6b7280); }',
|
|
538
|
+
'.dsh-batch-archive-selectall { display: flex; align-items: center; gap: 6px; cursor: pointer; color: var(--dsw-alias-label-secondary, #374151); }',
|
|
539
|
+
'.dsh-batch-archive-selectall input[type="checkbox"] { accent-color: var(--dsw-alias-brand-primary, #2563eb); width: 14px; height: 14px; margin: 0; }',
|
|
540
|
+
'.dsh-batch-archive-status { color: var(--dsw-alias-label-tertiary, #6b7280); }',
|
|
541
|
+
'.dsh-batch-archive-list { display: flex; flex-direction: column; gap: 2px; }',
|
|
542
|
+
'.dsh-batch-archive-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 8px; cursor: pointer; user-select: none; font-size: 13px; color: var(--dsw-alias-label-primary, inherit); }',
|
|
543
|
+
'.dsh-batch-archive-row:hover { background: var(--dsw-alias-interactive-bg-hover, #f3f4f6); }',
|
|
544
|
+
'.dsh-batch-archive-row.dsh-batch-archive-running { opacity: 0.6; }',
|
|
545
|
+
'.dsh-batch-archive-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
|
|
546
|
+
'.dsh-batch-archive-time { color: var(--dsw-alias-label-tertiary, #6b7280); font-size: 12px; flex-shrink: 0; }',
|
|
547
|
+
'.dsh-batch-archive-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 18px; border-top: 1px solid var(--dsw-alias-border-l2, #e5e7eb); }',
|
|
548
|
+
'.dsh-batch-archive-primary, .dsh-batch-archive-secondary { padding: 7px 14px; border-radius: 8px; font-size: 13px; cursor: pointer; border: 1px solid transparent; }',
|
|
549
|
+
'.dsh-batch-archive-primary { background: var(--dsw-alias-button-primary-fill, #111827); color: var(--dsw-alias-label-primary-foreground, #fff); }',
|
|
550
|
+
'.dsh-batch-archive-primary:hover:not(:disabled) { background: var(--dsw-alias-button-primary-hover, #374151); }',
|
|
551
|
+
'.dsh-batch-archive-primary:disabled { opacity: 0.5; cursor: not-allowed; }',
|
|
552
|
+
'.dsh-batch-archive-secondary { background: transparent; color: var(--dsw-alias-label-secondary, #374151); border-color: var(--dsw-alias-border-l2, #d1d5db); }',
|
|
553
|
+
'.dsh-batch-archive-secondary:hover { background: var(--dsw-alias-interactive-bg-hover, #f3f4f6); }'
|
|
554
|
+
].join('\n')
|
|
555
|
+
document.head.appendChild(style)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Handle Escape to close the dialog.
|
|
559
|
+
document.addEventListener('keydown', function (e) {
|
|
560
|
+
if (e.key === 'Escape' && archiveOverlay) {
|
|
561
|
+
e.stopPropagation()
|
|
562
|
+
closeArchiveDialog()
|
|
563
|
+
}
|
|
564
|
+
}, true)
|
|
565
|
+
|
|
566
|
+
// ---- Plugin entry ----
|
|
567
|
+
var ctx = null
|
|
568
|
+
|
|
569
|
+
function apply(pluginCtx) {
|
|
570
|
+
ctx = pluginCtx
|
|
571
|
+
injectStyles()
|
|
572
|
+
if (ctx.workspaces && ctx.workspaces.list) {
|
|
573
|
+
try {
|
|
574
|
+
var refreshWorkspaces = function () {
|
|
575
|
+
try {
|
|
576
|
+
var snap = ctx.workspaces.list.getSnapshot()
|
|
577
|
+
workspaceItems = snap.items.map(function (w) {
|
|
578
|
+
return { workspaceId: w.workspaceId, path: w.path, title: w.title, sessionIds: w.sessionIds || [] }
|
|
579
|
+
})
|
|
580
|
+
archivedSessionIds = snap.archivedSessionIds || []
|
|
581
|
+
} catch (err) {
|
|
582
|
+
console.warn('[' + NS + '] failed to read workspaces snapshot:', err)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
refreshWorkspaces()
|
|
586
|
+
ctx.workspaces.list.subscribe(refreshWorkspaces)
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.warn('[' + NS + '] workspaces service not usable:', err)
|
|
589
|
+
}
|
|
590
|
+
} else {
|
|
591
|
+
console.warn('[' + NS + '] ctx.workspaces not available')
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// sessions/uiWorkspace 只在批量归档弹窗中使用;快照延迟到打开弹窗时读取,
|
|
595
|
+
// 避免在 inactive context 中反复订阅、刷屏报错。
|
|
596
|
+
|
|
597
|
+
hoverObserver.observe(document.body, { childList: true, subtree: true })
|
|
598
|
+
menuObserver.observe(document.body, { childList: true, subtree: true })
|
|
599
|
+
console.log('[' + NS + '] initialized, workspaces:', workspaceItems.length, 'sessions:', sessionIds.length)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
exports.apply = apply
|
|
603
|
+
exports.inject = ['workspaces', 'sessions', 'uiWorkspace']
|
|
604
|
+
return module.exports
|
|
605
|
+
}
|
|
606
|
+
})
|
package/cordis.patch.yml
ADDED
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-workspace-toolkit",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "DSH 工作区增强工具包:在资源管理器中打开、批量归档会话,以及后续文件/会话操作功能",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./client": "./client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"platform": "web",
|
|
18
|
+
"immediately": true,
|
|
19
|
+
"inject": []
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"index.js",
|
|
24
|
+
"client.js",
|
|
25
|
+
"cordis.patch.yml",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/gege9527/dsh-workspace-toolkit.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/gege9527/dsh-workspace-toolkit/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/gege9527/dsh-workspace-toolkit#readme",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@deepseek-ai/dsh-tools": ">=0.0.1-rc.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@deepseek-ai/dsh-tools": {
|
|
46
|
+
"optional": false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"keywords": [
|
|
50
|
+
"dsh",
|
|
51
|
+
"deepseek",
|
|
52
|
+
"dsh-plugin",
|
|
53
|
+
"workspace",
|
|
54
|
+
"file-explorer",
|
|
55
|
+
"session",
|
|
56
|
+
"archive"
|
|
57
|
+
]
|
|
58
|
+
}
|