dsh-workspace-plus 0.4.0 → 0.6.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.
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-workspace-plus",
3
- "version": "0.4.0",
4
- "description": "DeepSeek Harness plugin: label several directories inside one workspace and reference them by absolute path. Type @ in the composer to pick a label, manage them from the workspace row or the workspace_dirs tool, and the label table is injected into every request.",
3
+ "version": "0.6.1",
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
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "engines": {
@@ -16,7 +16,12 @@
16
16
  "./client": {
17
17
  "default": "./lib/client.js"
18
18
  },
19
- "./package.json": "./package.json"
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"
20
25
  },
21
26
  "dsh": {
22
27
  "manifestVersion": 1,
@@ -50,7 +55,10 @@
50
55
  "deepseek-harness",
51
56
  "cordis",
52
57
  "workspace",
53
- "context-injection"
58
+ "context-injection",
59
+ "notification",
60
+ "toast",
61
+ "windows"
54
62
  ],
55
63
  "author": "Sen70s",
56
64
  "license": "MIT",