dsh-asuta-project-activity 0.0.0-stage → 0.1.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 +25 -2
- package/client.js +131 -0
- package/cordis.patch.yml +4 -0
- package/icon.svg +1 -0
- package/index.js +2 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +59 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asuta and contributors
|
|
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
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 项目运行状态
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
项目折叠时,只要里面还有对话运行,标题右侧就显示 DSH 原生转圈。展开项目后隐藏这个圈,保留具体对话自己的状态。
|
|
4
|
+
|
|
5
|
+
- 多个对话仍只显示一个圈,悬停提示运行中的对话数量。
|
|
6
|
+
- 读取宿主状态订阅,不依赖展开的子列表,不使用轮询。
|
|
7
|
+
- 支持未分组、置顶会话和仍有后台子代理运行的会话;归档会话不计入。
|
|
8
|
+
- 全部结束、停止或失败后自动消失,卸载时清理订阅和界面标记。
|
|
9
|
+
- 不改变展开、菜单、新建和拖动操作,不修改会话数据。
|
|
10
|
+
|
|
11
|
+
## 安装与验证
|
|
12
|
+
|
|
13
|
+
在本仓库开发时:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
dsh plugin --profile desktop add ./plugins/project-activity
|
|
17
|
+
node --test plugins/project-activity/test/*.test.mjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
桌面实例未加载新增插件时,完全退出后重新打开。
|
|
21
|
+
|
|
22
|
+
针对 DSH 0.2.1-rc.3,使用 `workspaces.list`、`sessions.list`、`uiSession.sessionStatus` 与原生 `StateDot`。当前版本没有项目标题专用插槽,通过 `data-row-key=workspace:<id>` 和 `aria-expanded` 添加装饰;DSH 升级后需复核这些契约。
|
|
23
|
+
|
|
24
|
+
## English
|
|
25
|
+
|
|
26
|
+
Show one native spinner on collapsed project headers while conversations are running. Expanding a project removes its aggregate indicator. Hover for the count. Uses host subscriptions, not polling or rendered child rows. Targets DSH 0.2.1-rc.3; workspace row attributes are a version-dependent DOM seam.
|
package/client.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH 0.2.1-rc.3 has no project-header slot. Subscribe to the workspace
|
|
3
|
+
* browser's own status source, then decorate data-row-key/aria-expanded.
|
|
4
|
+
* Folded children do not exist in the DOM, so never scan them for activity.
|
|
5
|
+
*/
|
|
6
|
+
window.__ModuleLoader__.load({
|
|
7
|
+
id: 'dsh-asuta-project-activity',
|
|
8
|
+
factory(require) {
|
|
9
|
+
const React = require('react')
|
|
10
|
+
const { createRoot } = require('react-dom/client')
|
|
11
|
+
const { StateDot } = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
12
|
+
const PLUGIN = 'dsh-asuta-project-activity'
|
|
13
|
+
const ROW = '[role="treeitem"][data-row-key^="workspace:"]'
|
|
14
|
+
const MARK = 'data-project-activity'
|
|
15
|
+
const CSS = `
|
|
16
|
+
[${MARK}] { display:inline-flex;align-items:center;justify-content:center;
|
|
17
|
+
flex:0 0 16px;width:16px;height:20px;margin-inline-start:6px;
|
|
18
|
+
color:var(--dsw-alias-label-tertiary); }
|
|
19
|
+
[data-row-key^="workspace:"][aria-expanded="true"] > [${MARK}] { display:none; }
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
function runningCounts(workspaces, list, statuses) {
|
|
23
|
+
const archived = new Set(workspaces.archivedSessionIds)
|
|
24
|
+
const byId = list.byId
|
|
25
|
+
const active = (id) => {
|
|
26
|
+
const row = byId[id]
|
|
27
|
+
if (!row || row.blank || row.origin === 'subagent' || archived.has(id)) return false
|
|
28
|
+
const status = statuses.get(id)
|
|
29
|
+
const waiting = ['approval', 'plan-review', 'question'].includes(status?.pendingInteraction?.kind)
|
|
30
|
+
if (!waiting && (status?.running ?? row.running) === true) return true
|
|
31
|
+
return (list.projectionsBySession?.[id]?.values.subagentCatalog ?? []).some(
|
|
32
|
+
(child) => (statuses.get(child.id)?.running ?? byId[child.id]?.running) === true,
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
const counts = new Map()
|
|
36
|
+
const assigned = new Set()
|
|
37
|
+
for (const workspace of workspaces.items) {
|
|
38
|
+
let count = 0
|
|
39
|
+
for (const id of new Set(workspace.sessionIds)) {
|
|
40
|
+
assigned.add(id)
|
|
41
|
+
if (active(id)) count++
|
|
42
|
+
}
|
|
43
|
+
counts.set(workspace.workspaceId, count)
|
|
44
|
+
}
|
|
45
|
+
counts.set('', Object.keys(byId).filter((id) => !assigned.has(id) && active(id)).length)
|
|
46
|
+
return counts
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function label(count) {
|
|
50
|
+
return document.documentElement.lang.toLowerCase().startsWith('zh')
|
|
51
|
+
? `有 ${count} 个对话正在运行`
|
|
52
|
+
: `${count} conversation${count === 1 ? '' : 's'} running`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function install(ctx) {
|
|
56
|
+
const sources = [ctx.workspaces.list, ctx.sessions.list, ctx.uiSession.sessionStatus]
|
|
57
|
+
const mounts = new Map()
|
|
58
|
+
let disposed = false
|
|
59
|
+
let frame = null
|
|
60
|
+
const style = document.createElement('style')
|
|
61
|
+
style.dataset.plugin = PLUGIN
|
|
62
|
+
style.dataset.pluginCss = `${PLUGIN}/activity.css`
|
|
63
|
+
style.textContent = CSS
|
|
64
|
+
document.head.appendChild(style)
|
|
65
|
+
const remove = (row) => {
|
|
66
|
+
const entry = mounts.get(row)
|
|
67
|
+
if (!entry) return
|
|
68
|
+
entry.root.unmount()
|
|
69
|
+
entry.anchor.remove()
|
|
70
|
+
mounts.delete(row)
|
|
71
|
+
}
|
|
72
|
+
const flush = () => {
|
|
73
|
+
frame = null
|
|
74
|
+
if (disposed) return
|
|
75
|
+
const counts = runningCounts(...sources.map((source) => source.getSnapshot()))
|
|
76
|
+
const rows = new Set(document.querySelectorAll(ROW))
|
|
77
|
+
for (const row of mounts.keys()) if (!rows.has(row)) remove(row)
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
const count = counts.get(row.getAttribute('data-row-key').slice('workspace:'.length)) ?? 0
|
|
80
|
+
if (row.getAttribute('aria-expanded') !== 'false' || count === 0) { remove(row); continue }
|
|
81
|
+
let entry = mounts.get(row)
|
|
82
|
+
if (entry && entry.anchor.parentElement !== row) { remove(row); entry = undefined }
|
|
83
|
+
if (!entry) {
|
|
84
|
+
const anchor = document.createElement('span')
|
|
85
|
+
anchor.setAttribute(MARK, '')
|
|
86
|
+
anchor.setAttribute('role', 'img')
|
|
87
|
+
row.appendChild(anchor)
|
|
88
|
+
entry = { anchor, root: createRoot(anchor), text: '' }
|
|
89
|
+
mounts.set(row, entry)
|
|
90
|
+
entry.root.render(React.createElement(StateDot, { state: 'ongoing' }))
|
|
91
|
+
}
|
|
92
|
+
const text = label(count)
|
|
93
|
+
if (entry.text !== text) {
|
|
94
|
+
entry.anchor.setAttribute('aria-label', text)
|
|
95
|
+
entry.anchor.title = text
|
|
96
|
+
entry.text = text
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const schedule = () => {
|
|
101
|
+
if (!disposed && frame === null) frame = window.requestAnimationFrame(flush)
|
|
102
|
+
}
|
|
103
|
+
const containsRow = (node) => node.nodeType === 1 && (node.matches(ROW) || node.querySelector(ROW) !== null)
|
|
104
|
+
const observer = new MutationObserver((records) => {
|
|
105
|
+
for (const record of records) {
|
|
106
|
+
if (record.type === 'attributes') {
|
|
107
|
+
if (record.target === document.documentElement || record.target.matches(ROW)) { schedule(); return }
|
|
108
|
+
} else if ((record.target.nodeType === 1 && record.target.matches(ROW)) ||
|
|
109
|
+
[...record.addedNodes, ...record.removedNodes].some(containsRow)) { schedule(); return }
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ['aria-expanded', 'data-row-key'] })
|
|
113
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })
|
|
114
|
+
const stops = sources.map((source) => source.subscribe(schedule))
|
|
115
|
+
flush()
|
|
116
|
+
return () => {
|
|
117
|
+
disposed = true
|
|
118
|
+
observer.disconnect()
|
|
119
|
+
for (const stop of stops) stop()
|
|
120
|
+
if (frame !== null) window.cancelAnimationFrame(frame)
|
|
121
|
+
for (const row of mounts.keys()) remove(row)
|
|
122
|
+
style.remove()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
inject: ['workspaces', 'sessions', 'uiSession'],
|
|
128
|
+
apply(ctx) { ctx.effect(() => install(ctx), 'project-activity: collapsed project indicators') },
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
})
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none"><path d="M5 14a4 4 0 0 1 4-4h11l4 5h15a4 4 0 0 1 4 4v19a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4Z" stroke="#7ca5dc" stroke-width="3"/><circle cx="25" cy="28" r="8" stroke="#7ca5dc" stroke-width="3" opacity=".3"/><path d="M25 20a8 8 0 0 1 8 8" stroke="#7ca5dc" stroke-width="3" stroke-linecap="round"/></svg>
|
package/index.js
ADDED
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-asuta-project-activity",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Native activity indicators on collapsed projects. · 在折叠的项目标题上显示对话运行状态。",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"icon": "./icon.svg",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./client": "./client.js",
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./locale/*.json": "./locale/*.json",
|
|
13
|
+
"./icon": "./icon.svg",
|
|
14
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.js",
|
|
18
|
+
"client.js",
|
|
19
|
+
"cordis.patch.yml",
|
|
20
|
+
"locale/*.json",
|
|
21
|
+
"icon.svg",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE*",
|
|
24
|
+
"NOTICE*",
|
|
25
|
+
"THIRD_PARTY_NOTICES.md"
|
|
26
|
+
],
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web",
|
|
33
|
+
"immediately": true,
|
|
34
|
+
"inject": [
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-session",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-workspace"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/Asuta/dsh-cdx-plus.git",
|
|
46
|
+
"directory": "packages/project-activity"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/Asuta/dsh-cdx-plus/tree/main/packages/project-activity",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/Asuta/dsh-cdx-plus/issues"
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"dsh-plugin",
|
|
54
|
+
"deepseek-harness"
|
|
55
|
+
],
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": "^22.19.0 || >=24",
|
|
58
|
+
"dsh": "0.2.2-rc.2"
|
|
59
|
+
},
|
|
60
|
+
"license": "MIT"
|
|
61
|
+
}
|