dsh-quick-restart 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +74 -2
- package/cordis.patch.yml +4 -0
- package/lib/client.js +101 -0
- package/lib/index.js +145 -0
- package/package.json +42 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-quick-restart 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,75 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-quick-restart
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
给 **DeepSeek Harness(DSH)** 标题栏加两个按钮:**重载插件代码**(不用关程序)和 **重启 DSH**。
|
|
6
|
+
|
|
7
|
+
> 中文为主,末尾有简短的 English 说明。
|
|
8
|
+
|
|
9
|
+
## 它解决什么问题
|
|
10
|
+
|
|
11
|
+
DSH 的第三方插件改了代码之后,默认要**完全退出再打开**才生效(刷新页面没用,热重载也不会自动发生)。
|
|
12
|
+
这个插件在标题栏菜单栏加两个按钮,把这件事变成点一下:
|
|
13
|
+
|
|
14
|
+
- **重载** —— 只重新加载第三方插件,主进程和会话都不动,实测 8/8 成功;
|
|
15
|
+
- **重启** —— 关掉并重开 DSH(需要另外安装 [`dsh-hot-restart`](https://github.com/InariNGT-Ofiicial/dsh-hot-restart) 提供重启引擎,没装就只有"重载"可用)。
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
### 方式一:从 GitHub Release 装(已发布)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
https://github.com/a994903574/dsh-quick-restart/releases/download/v0.1.1/dsh-quick-restart-0.1.1.tgz
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
把这个地址填进 DSH 的插件安装入口即可(和装其它第三方插件一样)。
|
|
26
|
+
|
|
27
|
+
### 方式二:从 npm 市场装(尚未发布,等账号开通)
|
|
28
|
+
|
|
29
|
+
DSH 设置页 →「插件」→ 搜索 `dsh-quick-restart` → 安装 → **完全退出 DSH 再打开**(首次必须,按钮要用宿主启动时的注入表)。
|
|
30
|
+
|
|
31
|
+
或者手动:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd <你的 DSH profile 目录>
|
|
35
|
+
pnpm add dsh-quick-restart
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
并在 profile 的 `package.json` 里把它加进 `dsh.profile.bundles`。
|
|
39
|
+
|
|
40
|
+
## 用起来什么效果
|
|
41
|
+
|
|
42
|
+
标题栏菜单栏(文件/编辑那一条的右侧)多出两个小按钮:**重载**、**重启**。
|
|
43
|
+
点"重载"后,按钮会短暂显示结果;如果有插件重载失败,鼠标悬停能看到是哪个包、什么错误。
|
|
44
|
+
|
|
45
|
+
## 边界(老实说)
|
|
46
|
+
|
|
47
|
+
- **只重载第三方插件**。`@deepseek-ai/*`(宿主内置,在 app.asar 里)和 `cordis:*`(虚拟分组)会被跳过——碰它们必然失败。
|
|
48
|
+
- **前端 bundle 不会跟着换**。插件如果带前端界面,重载后界面那段还是旧的,得重启程序。这是宿主机制,不是本插件的问题。
|
|
49
|
+
- **重启按钮依赖 `dsh-hot-restart`**。没装也能用"重载",只是重启那颗点了没反应。
|
|
50
|
+
- 仅测试于 **Windows 桌面版**。其他平台未验证。
|
|
51
|
+
|
|
52
|
+
## 卸载
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm remove dsh-quick-restart
|
|
56
|
+
```
|
|
57
|
+
再从 `dsh.profile.bundles` 里删掉对应条目,重启 DSH。
|
|
58
|
+
|
|
59
|
+
## 它是怎么做的(给好奇的人)
|
|
60
|
+
|
|
61
|
+
桌面壳的 `index.html` 直接读安装包里的静态 dist,不走 `renderIndex`,所以 `webServer.tapIndex` 无效。
|
|
62
|
+
唯一可用的通道是宿主启动时**一次性收集**的 `webserver/index-inject` 注入表——所以插件里不能写对象级 `inject = ['webServer']`,
|
|
63
|
+
否则 `apply()` 会推迟到 webServer 就绪之后,错过那次收集,界面上永远看不到按钮。这一点已在代码注释里写明。
|
|
64
|
+
|
|
65
|
+
重载本身是:取 loader 的 entries → 清掉该包在 `loader.internal.loadCache` 里的模块 → 重新 import → 用新插件替换旧的 fiber。
|
|
66
|
+
|
|
67
|
+
## English (short)
|
|
68
|
+
|
|
69
|
+
**dsh-quick-restart** adds two titlebar buttons to DeepSeek Harness: **reload** third-party plugins without restarting the app, and **restart** DSH
|
|
70
|
+
(the latter requires `dsh-hot-restart`). Built-in `@deepseek-ai/*` plugins and front-end bundles are intentionally not hot-reloaded — that's a host limitation.
|
|
71
|
+
Tested on Windows desktop only. MIT licensed.
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var BAR = "[data-windows-menu]"
|
|
3
|
+
var ID_RELOAD = "dsh-quick-restart-reload"
|
|
4
|
+
var ID_RESTART = "dsh-quick-restart-restart"
|
|
5
|
+
// 样式照抄外壳菜单栏原生 button 规则(app.asar lib/preload-app.cjs: installWindowsMenu)
|
|
6
|
+
var CSS = "height:28px;padding:0 10px;border:0;border-radius:6px;background:transparent;" +
|
|
7
|
+
"color:var(--dsw-alias-label-secondary,inherit);font-family:inherit;font-size:13px;" +
|
|
8
|
+
"font-weight:400;line-height:28px;cursor:default;-webkit-app-region:no-drag"
|
|
9
|
+
function menuBar() {
|
|
10
|
+
try {
|
|
11
|
+
var hosts = document.querySelectorAll(BAR)
|
|
12
|
+
for (var i = 0; i < hosts.length; i++) {
|
|
13
|
+
var shadow = hosts[i].shadowRoot
|
|
14
|
+
if (!shadow) continue
|
|
15
|
+
var bar = shadow.querySelector("[role=menubar]")
|
|
16
|
+
if (bar) return bar
|
|
17
|
+
}
|
|
18
|
+
} catch (e) { }
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
// 隐藏 dsh-hot-restart 自带的浮动按钮(我们只借用它的重启引擎,界面用标题栏这颗)
|
|
22
|
+
function hideTheirs() {
|
|
23
|
+
try {
|
|
24
|
+
var id = "dsh-hot-restart-hide"
|
|
25
|
+
if (document.getElementById(id)) return
|
|
26
|
+
var st = document.createElement("style")
|
|
27
|
+
st.id = id
|
|
28
|
+
st.textContent = "#dsh-hr-btn{display:none !important}"
|
|
29
|
+
;(document.head || document.documentElement).appendChild(st)
|
|
30
|
+
} catch (e) { }
|
|
31
|
+
}
|
|
32
|
+
function hoverStyle(bar) {
|
|
33
|
+
try {
|
|
34
|
+
var root = bar.getRootNode()
|
|
35
|
+
if (!root || typeof root.querySelector !== "function") return
|
|
36
|
+
if (root.querySelector("#dsh-quick-restart-hover")) return
|
|
37
|
+
var st = document.createElement("style")
|
|
38
|
+
st.id = "dsh-quick-restart-hover"
|
|
39
|
+
st.textContent = "#" + ID_RELOAD + ":hover,#" + ID_RESTART + ":hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.18)) !important;color:var(--dsw-alias-label-primary,inherit) !important}"
|
|
40
|
+
;(root.nodeType === 11 ? root : (document.head || document.documentElement)).appendChild(st)
|
|
41
|
+
} catch (e) { }
|
|
42
|
+
}
|
|
43
|
+
function fire(kind, btn, text) {
|
|
44
|
+
btn.disabled = true
|
|
45
|
+
btn.textContent = kind === "reload" ? "重载中…" : "重启中…"
|
|
46
|
+
function done(msg) { btn.textContent = msg; setTimeout(function () { btn.textContent = text; btn.disabled = false }, 4000) }
|
|
47
|
+
if (kind === "reload") {
|
|
48
|
+
fetch("/dsh-quick-restart/reload", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ confirm: true }) })
|
|
49
|
+
.then(function (r) { return r.json() })
|
|
50
|
+
.then(function (j) {
|
|
51
|
+
if (!j.ok) throw new Error(j.error || "failed")
|
|
52
|
+
var list = j.result || []
|
|
53
|
+
var bad = list.filter(function (x) { return !x.ok })
|
|
54
|
+
if (bad.length) { btn.title = bad.map(function (b) { return b.pkg + ": " + b.error }).join("\n") }
|
|
55
|
+
done(bad.length ? ("失败 " + bad.length + "/" + list.length) : ("已重载 " + list.length + " 个"))
|
|
56
|
+
})
|
|
57
|
+
.catch(function (e) { done("失败: " + String((e && e.message) || e)) })
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
// 重启:借 dsh-hot-restart 的引擎(vbs 助手,已验证过)
|
|
61
|
+
fetch("/dsh-hot-restart/restart", {
|
|
62
|
+
method: "POST",
|
|
63
|
+
credentials: "same-origin",
|
|
64
|
+
headers: { "content-type": "application/json", "x-dsh-hot-restart": "1" },
|
|
65
|
+
body: JSON.stringify({ at: Date.now(), ua: navigator.userAgent })
|
|
66
|
+
})
|
|
67
|
+
.then(function (r) { return r.json().catch(function () { return {} }).then(function (j) { return { ok: r.ok, j: j } }) })
|
|
68
|
+
.then(function (x) {
|
|
69
|
+
if (!x.ok || !x.j || x.j.ok !== true) throw new Error((x.j && x.j.error) || "HTTP")
|
|
70
|
+
btn.textContent = "重启中…"
|
|
71
|
+
})
|
|
72
|
+
.catch(function (e) { done("失败: " + String((e && e.message) || e)) })
|
|
73
|
+
}
|
|
74
|
+
function makeButton(id, text, kind) {
|
|
75
|
+
var b = document.createElement("button")
|
|
76
|
+
b.type = "button"
|
|
77
|
+
b.id = id
|
|
78
|
+
b.setAttribute("role", "menuitem")
|
|
79
|
+
b.tabIndex = -1
|
|
80
|
+
b.setAttribute("style", CSS)
|
|
81
|
+
b.textContent = text
|
|
82
|
+
b.title = kind === "reload" ? "重载插件代码,不动主进程" : "关闭并重开 DSH(dsh-hot-restart 引擎)"
|
|
83
|
+
b.addEventListener("pointerdown", function (e) { e.preventDefault() })
|
|
84
|
+
b.addEventListener("mousedown", function (e) { e.preventDefault() })
|
|
85
|
+
b.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); fire(kind, b, text) })
|
|
86
|
+
return b
|
|
87
|
+
}
|
|
88
|
+
function mount() {
|
|
89
|
+
hideTheirs()
|
|
90
|
+
var bar = menuBar()
|
|
91
|
+
if (!bar) return false
|
|
92
|
+
hoverStyle(bar)
|
|
93
|
+
if (bar.querySelector("#" + ID_RELOAD)) return true
|
|
94
|
+
bar.appendChild(makeButton(ID_RELOAD, "重载插件", "reload"))
|
|
95
|
+
bar.appendChild(makeButton(ID_RESTART, "重启", "restart"))
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
var tries = 0
|
|
99
|
+
var timer = setInterval(function () { tries += 1; hideTheirs(); if (mount() || tries > 240) clearInterval(timer) }, 500)
|
|
100
|
+
mount()
|
|
101
|
+
})();
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// dsh-quick-restart — 宿主侧:注入标题栏脚本 + 两条路由(重载 / 重启)
|
|
2
|
+
// 关键:**不写对象级 inject**。桌面端(Electron)的注入表是宿主启动时一次性收集的
|
|
3
|
+
// (collectIndexInjections → IPC → 渲染层逐行应用),对象级 inject 会把 apply() 推迟到
|
|
4
|
+
// webServer 就绪之后,订阅晚于那次收集,界面就永远不出现。
|
|
5
|
+
// 另外:桌面壳的 index.html 从安装包静态 dist 读盘,不经过 renderIndex(),tapIndex 无效;
|
|
6
|
+
// 唯一通道是 webserver/index-inject 的结构化 script 行。
|
|
7
|
+
import { spawn } from 'node:child_process'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
import { dirname, join } from 'node:path'
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
11
|
+
|
|
12
|
+
export const name = 'dsh-quick-restart'
|
|
13
|
+
|
|
14
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const SELF = 'dsh-quick-restart'
|
|
16
|
+
const CLIENT_ROUTE = '/dsh-quick-restart/client.js'
|
|
17
|
+
const JSON_HEADERS = { 'content-type': 'application/json; charset=utf-8' }
|
|
18
|
+
// 内联加载器:自己建 <script src> 并吞掉 onerror —— 路由在就加载,不在也不影响页面启动
|
|
19
|
+
const LOADER = 'var s=document.createElement("script");s.src="' + CLIENT_ROUTE + '";s.onerror=function(){};document.body.appendChild(s);'
|
|
20
|
+
|
|
21
|
+
function pkgOf(spec) {
|
|
22
|
+
if (!spec) return null
|
|
23
|
+
const parts = String(spec).split('/')
|
|
24
|
+
return parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readBody(req) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
let raw = ''
|
|
30
|
+
req.on('data', (c) => { raw += c; if (raw.length > 1e6) req.destroy() })
|
|
31
|
+
req.on('end', () => { try { resolve(raw ? JSON.parse(raw) : {}) } catch { resolve({}) } })
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function json(res, code, obj) {
|
|
36
|
+
try { res.writeHead(code, JSON_HEADERS); res.end(JSON.stringify(obj)) } catch { /* 忽略 */ }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function apply(ctx, config = {}) {
|
|
40
|
+
try { applyInner(ctx, config) } catch (e) {
|
|
41
|
+
try { ctx.logger?.error?.('dsh-quick-restart 装载失败: ' + String((e && e.stack) || e)) } catch { /* 忽略 */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function applyInner(ctx, config = {}) {
|
|
46
|
+
const appExe = config.appExe || process.execPath
|
|
47
|
+
const log = (m) => { try { ctx.logger?.info?.(m) } catch { /* 忽略 */ } }
|
|
48
|
+
let port = 0
|
|
49
|
+
const webPort = () => { try { if (Number.isSafeInteger(ctx.webServer?.port)) return ctx.webServer.port } catch { /* 忽略 */ } return port }
|
|
50
|
+
|
|
51
|
+
// ① 注入表(必须尽早订阅,且不能依赖 webServer)
|
|
52
|
+
try {
|
|
53
|
+
ctx.on('webserver/index-inject', (table) => {
|
|
54
|
+
if (!Array.isArray(table)) return
|
|
55
|
+
if (table.some((row) => row && typeof row.text === 'string' && row.text.indexOf(CLIENT_ROUTE) >= 0)) return
|
|
56
|
+
table.push({ kind: 'script', placement: 'body', text: LOADER })
|
|
57
|
+
})
|
|
58
|
+
} catch (e) { log('index-inject 订阅失败: ' + String((e && e.message) || e)) }
|
|
59
|
+
|
|
60
|
+
function resolveUrl(internal, specifier, parentURL) {
|
|
61
|
+
const attrs = {}
|
|
62
|
+
if (internal.version === 'v2' && typeof internal.resolveSync === 'function') return internal.resolveSync(parentURL, { specifier, attributes: attrs })
|
|
63
|
+
if (typeof internal.resolve === 'function') return internal.resolve(specifier, parentURL, attrs)
|
|
64
|
+
if (typeof internal.resolveSync === 'function') return internal.resolveSync(parentURL, { specifier, attributes: attrs })
|
|
65
|
+
throw new Error('loader.internal 没有解析器')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function invalidateDir(internal, url) {
|
|
69
|
+
let dir = dirname(fileURLToPath(url))
|
|
70
|
+
let root = null
|
|
71
|
+
for (;;) {
|
|
72
|
+
if (existsSync(join(dir, 'package.json'))) { root = dir; break }
|
|
73
|
+
const parent = dirname(dir)
|
|
74
|
+
if (parent === dir) break
|
|
75
|
+
dir = parent
|
|
76
|
+
}
|
|
77
|
+
const prefix = root ? root.replace(/\\/g, '/').toLowerCase() : null
|
|
78
|
+
let n = 0
|
|
79
|
+
try {
|
|
80
|
+
for (const key of [...internal.loadCache.keys()]) {
|
|
81
|
+
const k = String(key).replace(/\\/g, '/').toLowerCase()
|
|
82
|
+
if (prefix ? k.indexOf(prefix) >= 0 : false) { try { Map.prototype.delete.call(internal.loadCache, key); n += 1 } catch { /* 忽略 */ } }
|
|
83
|
+
}
|
|
84
|
+
} catch { /* 忽略 */ }
|
|
85
|
+
return n
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function reloadOne(loader, internal, entry) {
|
|
89
|
+
const specifier = entry?.options?.name
|
|
90
|
+
const parentURL = entry?.parent?.tree?.ctx?.baseUrl ?? ctx.baseUrl
|
|
91
|
+
const oldFiber = entry?.fiber
|
|
92
|
+
const oldPlugin = oldFiber?.runtime?.callback
|
|
93
|
+
if (!oldPlugin) throw new Error('没有活动的 fiber')
|
|
94
|
+
const resolved = await resolveUrl(internal, specifier, parentURL)
|
|
95
|
+
const url = typeof resolved === 'string' ? resolved : (resolved && resolved.url ? String(resolved.url) : String(resolved))
|
|
96
|
+
invalidateDir(internal, url)
|
|
97
|
+
const newPlugin = loader.unwrapExports(await loader.import(url, () => []))
|
|
98
|
+
if (!newPlugin) throw new Error('重新 import 没有导出插件')
|
|
99
|
+
oldFiber.parent.registry.delete(oldPlugin)
|
|
100
|
+
oldFiber.parent.registry.plugin(newPlugin, oldFiber._config, () => [])
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function reloadTargets(only) {
|
|
104
|
+
const loader = ctx.get('loader')
|
|
105
|
+
const internal = loader?.internal
|
|
106
|
+
if (!loader || typeof loader.entries !== 'function') throw new Error('loader 不可用')
|
|
107
|
+
if (!internal || !internal.loadCache) throw new Error('loader.internal 不可用,无法清缓存')
|
|
108
|
+
const seen = new Set()
|
|
109
|
+
const out = []
|
|
110
|
+
for (const entry of [...loader.entries()]) {
|
|
111
|
+
if (entry?.disabled || entry?.options?.group || !entry?.fiber?.runtime) continue
|
|
112
|
+
const pkg = pkgOf(entry?.options?.name)
|
|
113
|
+
if (!pkg || pkg === SELF) continue
|
|
114
|
+
// 宿主内置插件(@deepseek-ai/*)在 app.asar 里,热重载必然失败,别去碰
|
|
115
|
+
if (pkg.startsWith('@deepseek-ai/')) continue
|
|
116
|
+
if (pkg.startsWith('cordis:')) continue // 虚拟分组条目,没有可重载的模块
|
|
117
|
+
if (only && only.length && !only.includes(pkg)) continue
|
|
118
|
+
if (seen.has(pkg)) continue
|
|
119
|
+
seen.add(pkg)
|
|
120
|
+
try { await reloadOne(loader, internal, entry); out.push({ pkg, ok: true }) }
|
|
121
|
+
catch (e) { out.push({ pkg, ok: false, error: String((e && e.message) || e) }) }
|
|
122
|
+
}
|
|
123
|
+
return out
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ② 路由(等 webServer)
|
|
127
|
+
ctx.inject(['webServer'], (c) => {
|
|
128
|
+
try { port = Number(c.webServer?.port) || 0 } catch { /* 忽略 */ }
|
|
129
|
+
const route = (path, handler) => c.effect(() => c.webServer.register({ kind: 'exact', path, handler }))
|
|
130
|
+
route(CLIENT_ROUTE, (req, res) => {
|
|
131
|
+
try {
|
|
132
|
+
const js = readFileSync(join(HERE, 'client.js'), 'utf8')
|
|
133
|
+
res.writeHead(200, { 'content-type': 'application/javascript; charset=utf-8', 'cache-control': 'no-store' })
|
|
134
|
+
res.end(js)
|
|
135
|
+
} catch (e) { res.writeHead(404); res.end('missing client') }
|
|
136
|
+
})
|
|
137
|
+
route('/dsh-quick-restart/info', (req, res) => json(res, 200, { ok: true, name: SELF, pid: process.pid, port: webPort() }))
|
|
138
|
+
route('/dsh-quick-restart/reload', async (req, res) => {
|
|
139
|
+
const b = await readBody(req)
|
|
140
|
+
if (b.confirm !== true) return json(res, 400, { ok: false, error: '需要 confirm:true' })
|
|
141
|
+
try { const result = await reloadTargets(Array.isArray(b.packages) ? b.packages : null); log('quick-restart 重载: ' + JSON.stringify(result)); return json(res, 200, { ok: true, result }) }
|
|
142
|
+
catch (e) { return json(res, 500, { ok: false, error: String((e && e.message) || e) }) }
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quick-restart",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "DeepSeek Harness 插件:标题栏加两个按钮——重载第三方插件(不用重启宿主)、重启 DSH。",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"deepseek",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"dsh",
|
|
10
|
+
"dsh-plugin",
|
|
11
|
+
"plugin",
|
|
12
|
+
"restart",
|
|
13
|
+
"hot-reload"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"main": "lib/index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./lib/index.js",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"lib",
|
|
23
|
+
"cordis.patch.yml",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"dsh": {
|
|
31
|
+
"bundle": {
|
|
32
|
+
"patch": "./cordis.patch.yml"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/a994903574/dsh-quick-restart.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/a994903574/dsh-quick-restart#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/a994903574/dsh-quick-restart/issues"
|
|
42
|
+
},
|
|
43
|
+
"author": "a994903574"
|
|
44
|
+
}
|