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 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
- # Temporary Holding Version
1
+ # 项目运行状态
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ })
@@ -0,0 +1,4 @@
1
+ - insert:
2
+ - id: project-activity
3
+ name: 'dsh-asuta-project-activity'
4
+ config: {}
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
@@ -0,0 +1,2 @@
1
+ /** The host entry owns this client-only bundle. */
2
+ export function apply() {}
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Project activity",
4
+ "description": "Show a native spinner on collapsed project headers while conversations are running."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "项目运行状态",
4
+ "description": "项目折叠时,在标题右侧显示原生转圈,提示里面还有对话正在运行。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "dsh-asuta-project-activity",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }