dsh-workspace-plus 0.2.0 → 0.6.0
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/README.md +572 -397
- package/cordis.patch.yml +45 -8
- package/lib/client.js +2676 -1653
- package/lib/index.js +790 -707
- package/lib/notice/click.ps1 +67 -0
- package/lib/notice/format.js +313 -0
- package/lib/notice/icon.svg +16 -0
- package/lib/notice/index.js +951 -0
- package/lib/notice/locale/en.json +6 -0
- package/lib/notice/locale/zh.json +6 -0
- package/lib/notice/package.json +6 -0
- package/lib/notice/settings.js +336 -0
- package/lib/notice/toast-host.ps1 +616 -0
- package/lib/notice/toast.js +517 -0
- package/lib/notice/win32.js +206 -0
- package/package.json +73 -65
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-workspace-plus/notice — Windows integration (ported from dsh-notice).
|
|
3
|
+
*
|
|
4
|
+
* Three operating-system services the host half needs, kept apart from the
|
|
5
|
+
* plugin's own logic so the plugin stays loadable (and its pure helpers
|
|
6
|
+
* testable) on any platform:
|
|
7
|
+
*
|
|
8
|
+
* 1. the AppUserModelID a toast must speak for. Windows only renders a toast
|
|
9
|
+
* raised by an unpackaged process when the AUMID belongs to a Start Menu
|
|
10
|
+
* entry, so the plugin prefers the DeepSeek Harness desktop app's own
|
|
11
|
+
* AUMID — that is what makes the toast read "DeepSeek Harness" and carry
|
|
12
|
+
* its icon — and falls back to the Windows PowerShell entry, which exists
|
|
13
|
+
* on every Windows install;
|
|
14
|
+
* 2. the `dsh-notice:` URL protocol, registered under HKCU (no administrator
|
|
15
|
+
* rights), which is what turns a click on a toast into a request against
|
|
16
|
+
* this plugin's own loopback route;
|
|
17
|
+
* 3. focusing the desktop window, so a click that arrives while the window is
|
|
18
|
+
* minimized still lands somewhere the user can see.
|
|
19
|
+
*
|
|
20
|
+
* @module dsh-workspace-plus/notice/win32
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { execFile } from 'node:child_process'
|
|
24
|
+
import { win32 as pathWin32 } from 'node:path'
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default AppUserModelID: the DeepSeek Harness desktop application.
|
|
28
|
+
*
|
|
29
|
+
* Electron calls `app.setAppUserModelId('com.deepseek.dsh')` and the installed
|
|
30
|
+
* shortcut publishes the same id, so a toast raised for it is attributed to the
|
|
31
|
+
* running product rather than to this plugin's helper process.
|
|
32
|
+
*/
|
|
33
|
+
export const DESKTOP_AUMID = 'com.deepseek.dsh'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fallback AppUserModelID: the "Windows PowerShell" Start Menu entry.
|
|
37
|
+
*
|
|
38
|
+
* The Windows PowerShell folder under the Start Menu gets this deterministic
|
|
39
|
+
* generated id, and it is present on every Windows 10/11 install, so a machine
|
|
40
|
+
* that runs the Harness without the desktop app can still raise toasts.
|
|
41
|
+
*/
|
|
42
|
+
export const POWERSHELL_AUMID =
|
|
43
|
+
'{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
44
|
+
|
|
45
|
+
/** AUMIDs to try, best attribution first. */
|
|
46
|
+
export function aumidCandidates() {
|
|
47
|
+
return [DESKTOP_AUMID, POWERSHELL_AUMID]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Absolute path of Windows PowerShell 5.1.
|
|
52
|
+
*
|
|
53
|
+
* The WinRT projection the toast API needs lives in .NET Framework, which only
|
|
54
|
+
* Windows PowerShell (`powershell.exe`) exposes out of the box; PowerShell 7
|
|
55
|
+
* (`pwsh`) does not ship the Windows SDK projections and would fail.
|
|
56
|
+
*
|
|
57
|
+
* @param env - environment snapshot (injectable for tests).
|
|
58
|
+
* @returns the absolute interpreter path.
|
|
59
|
+
*/
|
|
60
|
+
export function powershellPath(env = process.env) {
|
|
61
|
+
const root = typeof env.SystemRoot === 'string' && env.SystemRoot !== '' ? env.SystemRoot : 'C:\\Windows'
|
|
62
|
+
return pathWin32.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether this process can show Windows toasts at all.
|
|
67
|
+
*
|
|
68
|
+
* @param platform - `process.platform` (injectable for tests).
|
|
69
|
+
* @returns true on Windows.
|
|
70
|
+
*/
|
|
71
|
+
export function isSupported(platform = process.platform) {
|
|
72
|
+
return platform === 'win32'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Registry path of one protocol's root key. */
|
|
76
|
+
export function protocolKey(scheme) {
|
|
77
|
+
return `HKCU\\Software\\Classes\\${scheme}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The launch command Windows runs for one `dsh-notice:` activation.
|
|
82
|
+
*
|
|
83
|
+
* Quoting is explicit and complete because the value is executed by
|
|
84
|
+
* `ShellExecute`: the interpreter, the script and every argument are quoted, and
|
|
85
|
+
* `%1` is the placeholder Windows replaces with the activated URL. The URL is
|
|
86
|
+
* quoted too, because a toast arguments string may contain `&`.
|
|
87
|
+
*
|
|
88
|
+
* @param options - interpreter, script, port, token and scheme.
|
|
89
|
+
* @returns the command line stored in the registry.
|
|
90
|
+
*/
|
|
91
|
+
export function protocolCommand(options) {
|
|
92
|
+
const { powershell, script, port, token, scheme, protocolArgument = '%1' } = options
|
|
93
|
+
return (
|
|
94
|
+
`"${powershell}" -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass` +
|
|
95
|
+
` -File "${script}" -Scheme "${scheme}" -Port ${port} -Token "${token}" "${protocolArgument}"`
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** How long a registry write may take before it is treated as failed. */
|
|
100
|
+
const REG_TIMEOUT_MS = 10_000
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run one `reg.exe` verb.
|
|
104
|
+
*
|
|
105
|
+
* @param args - verb and operands.
|
|
106
|
+
* @returns the process exit code plus captured output.
|
|
107
|
+
*/
|
|
108
|
+
function runReg(args) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
execFile(
|
|
111
|
+
pathWin32.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'reg.exe'),
|
|
112
|
+
args,
|
|
113
|
+
{ windowsHide: true, timeout: REG_TIMEOUT_MS },
|
|
114
|
+
(error, stdout, stderr) => {
|
|
115
|
+
resolve({
|
|
116
|
+
ok: error === null,
|
|
117
|
+
code: error === null ? 0 : (error.code ?? 1),
|
|
118
|
+
stdout: String(stdout ?? ''),
|
|
119
|
+
stderr: String(stderr ?? ''),
|
|
120
|
+
})
|
|
121
|
+
},
|
|
122
|
+
)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register the plugin's URL protocol for the CURRENT USER.
|
|
128
|
+
*
|
|
129
|
+
* `HKCU\Software\Classes` is the per-user protocol store: no elevation, no
|
|
130
|
+
* machine-wide side effect, and removing the key is a complete undo. Windows
|
|
131
|
+
* resolves `dsh-notice:<anything>` to this command line, which is how a click on
|
|
132
|
+
* a toast reaches the running host.
|
|
133
|
+
*
|
|
134
|
+
* @param options - `scheme`, and the command line to run.
|
|
135
|
+
* @returns the outcome, never a throw.
|
|
136
|
+
*/
|
|
137
|
+
export async function registerProtocol(options) {
|
|
138
|
+
const { scheme, command } = options
|
|
139
|
+
if (!isSupported()) return { ok: false, message: 'Windows only' }
|
|
140
|
+
const key = protocolKey(scheme)
|
|
141
|
+
const steps = [
|
|
142
|
+
['add', key, '/ve', '/d', `URL:${scheme} protocol`, '/f'],
|
|
143
|
+
['add', key, '/v', 'URL Protocol', '/d', '', '/f'],
|
|
144
|
+
['add', `${key}\\shell\\open\\command`, '/ve', '/d', command, '/f'],
|
|
145
|
+
]
|
|
146
|
+
for (const args of steps) {
|
|
147
|
+
const result = await runReg(args)
|
|
148
|
+
if (!result.ok) {
|
|
149
|
+
return { ok: false, message: `reg ${args[0]} ${args[1]} failed: ${result.stderr.trim() || result.stdout.trim()}` }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { ok: true, message: `registered ${scheme}: for the current user` }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Remove this plugin's per-user protocol registration.
|
|
157
|
+
*
|
|
158
|
+
* Absence is success: the key may never have been written, or the user may have
|
|
159
|
+
* removed it by hand.
|
|
160
|
+
*
|
|
161
|
+
* @param scheme - protocol scheme to forget.
|
|
162
|
+
* @returns the outcome, never a throw.
|
|
163
|
+
*/
|
|
164
|
+
export async function unregisterProtocol(scheme) {
|
|
165
|
+
if (!isSupported()) return { ok: false, message: 'Windows only' }
|
|
166
|
+
const result = await runReg(['delete', protocolKey(scheme), '/f'])
|
|
167
|
+
return { ok: true, message: result.ok ? `removed ${scheme}:` : `${scheme}: was not registered` }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Read back the command Windows would run for this scheme, so the settings page
|
|
172
|
+
* can show whether the registration is the plugin's own and still current.
|
|
173
|
+
*
|
|
174
|
+
* @param scheme - protocol scheme to inspect.
|
|
175
|
+
* @returns the stored command line, or null when nothing is registered.
|
|
176
|
+
*/
|
|
177
|
+
export async function readProtocolCommand(scheme) {
|
|
178
|
+
if (!isSupported()) return null
|
|
179
|
+
const result = await runReg(['query', `${protocolKey(scheme)}\\shell\\open\\command`, '/ve'])
|
|
180
|
+
if (!result.ok) return null
|
|
181
|
+
const match = /REG_SZ\s+(.*)$/m.exec(result.stdout)
|
|
182
|
+
return match === null ? null : match[1].trim()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Bring the DeepSeek Harness desktop window to the front.
|
|
187
|
+
*
|
|
188
|
+
* The desktop app registers `dsh:` as a protocol and focuses its primary window
|
|
189
|
+
* for `dsh://open`; on Windows the activation starts a second instance which
|
|
190
|
+
* immediately yields to the first through the single-instance lock. Doing this
|
|
191
|
+
* from the host (rather than as the toast's own activation) lets the toast
|
|
192
|
+
* activation carry the session id instead of the focus request.
|
|
193
|
+
*
|
|
194
|
+
* @returns the outcome, never a throw.
|
|
195
|
+
*/
|
|
196
|
+
export function focusDesktopWindow() {
|
|
197
|
+
if (!isSupported()) return { ok: false, message: 'Windows only' }
|
|
198
|
+
return new Promise((resolve) => {
|
|
199
|
+
execFile(
|
|
200
|
+
'cmd.exe',
|
|
201
|
+
['/c', 'start', '', 'dsh://open'],
|
|
202
|
+
{ windowsHide: true, timeout: REG_TIMEOUT_MS },
|
|
203
|
+
(error) => resolve(error === null ? { ok: true } : { ok: false, message: String(error.message) }),
|
|
204
|
+
)
|
|
205
|
+
})
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -1,65 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-workspace-plus",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DeepSeek Harness plugin: label several directories inside one workspace and reference them by absolute path
|
|
5
|
-
"icon": "icon.svg",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"engines": {
|
|
8
|
-
"node": "^22.19.0 || >=24.0.0",
|
|
9
|
-
"dsh": ">=0.1.7-rc.2"
|
|
10
|
-
},
|
|
11
|
-
"main": "lib/index.js",
|
|
12
|
-
"exports": {
|
|
13
|
-
".": {
|
|
14
|
-
"default": "./lib/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./client": {
|
|
17
|
-
"default": "./lib/client.js"
|
|
18
|
-
},
|
|
19
|
-
"./package.json": "./package.json"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-workspace-plus",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "DeepSeek Harness plugin with two switchable sub-features: workspace+ (label several directories inside one workspace and reference them by absolute path — the @ composer source, the row button, the workspace_dirs tool) and 通知/notice (Windows native notifications for running conversations, merged from dsh-notice). Each sub-feature is its own loader row and can be toggled on the plugin page.",
|
|
5
|
+
"icon": "icon.svg",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "^22.19.0 || >=24.0.0",
|
|
9
|
+
"dsh": ">=0.1.7-rc.2"
|
|
10
|
+
},
|
|
11
|
+
"main": "lib/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"default": "./lib/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./client": {
|
|
17
|
+
"default": "./lib/client.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./notice": {
|
|
21
|
+
"default": "./lib/notice/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./notice/package.json": "./lib/notice/package.json",
|
|
24
|
+
"./notice/locale/*.json": "./lib/notice/locale/*.json"
|
|
25
|
+
},
|
|
26
|
+
"dsh": {
|
|
27
|
+
"manifestVersion": 1,
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web",
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-layout",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"lib",
|
|
42
|
+
"cordis.patch.yml",
|
|
43
|
+
"icon.svg",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"verify": "node tools/verify.mjs",
|
|
49
|
+
"prepublishOnly": "node tools/verify.mjs"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"dsh",
|
|
53
|
+
"dsh-plugin",
|
|
54
|
+
"dsh-bundle",
|
|
55
|
+
"deepseek-harness",
|
|
56
|
+
"cordis",
|
|
57
|
+
"workspace",
|
|
58
|
+
"context-injection",
|
|
59
|
+
"notification",
|
|
60
|
+
"toast",
|
|
61
|
+
"windows"
|
|
62
|
+
],
|
|
63
|
+
"author": "Sen70s",
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"repository": {
|
|
66
|
+
"type": "git",
|
|
67
|
+
"url": "git+https://github.com/Sen70s/dsh-workspace-plus.git"
|
|
68
|
+
},
|
|
69
|
+
"homepage": "https://github.com/Sen70s/dsh-workspace-plus#readme",
|
|
70
|
+
"bugs": {
|
|
71
|
+
"url": "https://github.com/Sen70s/dsh-workspace-plus/issues"
|
|
72
|
+
}
|
|
73
|
+
}
|