@xmanrui/dsh-im 3.0.7 → 3.1.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.en.md +25 -2
- package/README.md +25 -2
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/lib/client.js +542 -47
- package/lib/index.js +232 -225
- package/package.json +3 -2
- package/plugin-src/client/i18n.js +56 -0
- package/plugin-src/client/index.js +19 -2
- package/plugin-src/client/styles.js +34 -1
- package/plugin-src/client/update-panel.js +367 -0
- package/plugin-src/host/channels/dingtalk/index.mjs +1 -1
- package/plugin-src/host/channels/dingtalk/production.mjs +3 -11
- package/plugin-src/host/channels/discord/index.mjs +1 -1
- package/plugin-src/host/channels/feishu/index.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +3 -13
- package/plugin-src/host/channels/office/production.mjs +3 -3
- package/plugin-src/host/channels/qq/index.mjs +1 -1
- package/plugin-src/host/channels/qq/production.mjs +3 -11
- package/plugin-src/host/channels/shared/production.mjs +3 -11
- package/plugin-src/host/channels/slack/index.mjs +1 -1
- package/plugin-src/host/channels/slack/production.mjs +4 -3
- package/plugin-src/host/channels/telegram/index.mjs +1 -1
- package/plugin-src/host/channels/wecom/index.mjs +1 -1
- package/plugin-src/host/channels/wecom/production.mjs +3 -11
- package/plugin-src/host/channels/weixin/index.mjs +1 -1
- package/plugin-src/host/channels/weixin/production.mjs +3 -11
- package/plugin-src/host/channels/whatsapp/index.mjs +1 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +3 -11
- package/plugin-src/host/harness-connection.mjs +15 -0
- package/plugin-src/host/index.mjs +10 -1
- package/plugin-src/host/update-rpc.mjs +50 -0
- package/plugin-src/host/update-runtime.mjs +338 -0
- package/plugin-src/host/update-service.mjs +385 -0
- package/scripts/verify-package.mjs +12 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +5 -1
- package/src/channels/feishu/bridge.mjs +11 -3
- package/src/channels/feishu/feishu-cards.mjs +2 -0
- package/src/channels/qq/qq-bridge.mjs +5 -1
- package/src/channels/shared/bot-workspace-store.mjs +3 -0
- package/src/channels/shared/harness-client.mjs +252 -281
- package/src/channels/shared/harness-mux.mjs +123 -0
- package/src/channels/shared/history-command.mjs +193 -0
- package/src/channels/shared/i18n-en/shared-b.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +40 -0
- package/src/channels/shared/text-harness-bridge.mjs +5 -1
- package/src/channels/shared/workspace-command.mjs +1 -0
- package/src/channels/wecom/wecom-bridge.mjs +5 -1
- package/src/channels/weixin/weixin-bridge.mjs +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "把九种 IM 机器人和公网 AI Office 接入本机 DeepSeek Harness。 Connect nine IM channels and a public AI Office to a local DeepSeek Harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"https-proxy-agent": "5.0.1",
|
|
103
103
|
"react": "18.3.1",
|
|
104
104
|
"react-dom": "18.3.1",
|
|
105
|
-
"react-test-renderer": "18.3.1"
|
|
105
|
+
"react-test-renderer": "18.3.1",
|
|
106
|
+
"semver": "7.8.5"
|
|
106
107
|
}
|
|
107
108
|
}
|
|
@@ -9,6 +9,62 @@ const EN = Object.freeze({
|
|
|
9
9
|
'IM 渠道': 'IM channels',
|
|
10
10
|
'让 DeepSeek Harness 触手可及': 'Connecting DeepSeek Harness',
|
|
11
11
|
'当前版本': 'Current version',
|
|
12
|
+
'DSH-IM 更新': 'DSH-IM update',
|
|
13
|
+
'检查更新': 'Check for updates',
|
|
14
|
+
'重新检查': 'Check again',
|
|
15
|
+
'刷新状态': 'Refresh status',
|
|
16
|
+
'更新至': 'Update to',
|
|
17
|
+
'安装更新': 'Install update',
|
|
18
|
+
'正在更新…': 'Updating…',
|
|
19
|
+
'待手动重启': 'Restart needed',
|
|
20
|
+
'运行版本': 'Running version',
|
|
21
|
+
'已安装版本': 'Installed version',
|
|
22
|
+
'npm 最新版本': 'Latest on npm',
|
|
23
|
+
'目标 profile': 'Target profile',
|
|
24
|
+
'目标版本': 'Target version',
|
|
25
|
+
'更新来源': 'Update source',
|
|
26
|
+
'页面版本': 'Page version',
|
|
27
|
+
'无法确认': 'Unknown',
|
|
28
|
+
'发现新版本': 'Update available',
|
|
29
|
+
'已是最新版本': 'Up to date',
|
|
30
|
+
'当前版本无需更新': 'No newer version available',
|
|
31
|
+
'已获取 npm 最新版本': 'Latest npm version retrieved',
|
|
32
|
+
'检查 npm 最新版本,不会自动安装。': 'Check the latest npm version. Nothing is installed automatically.',
|
|
33
|
+
'正在从 npm 检查最新版本…': 'Checking npm for the latest version…',
|
|
34
|
+
'正在安装,请稍候…': 'Installing, please wait…',
|
|
35
|
+
'正在校验安装结果…': 'Verifying the installation…',
|
|
36
|
+
'已安装,待手动重启': 'Installed — manual restart needed',
|
|
37
|
+
'更新已生效': 'Updated version is active',
|
|
38
|
+
'更新失败': 'Update failed',
|
|
39
|
+
'更新请求失败': 'Update request failed',
|
|
40
|
+
'仅更新 DSH-IM。安装完成后需手动重启后台;本功能不会自动重启或主动刷新页面。': 'Only DSH-IM will be updated. Restart the backend manually after installation. This feature does not automatically restart it or initiate a page refresh.',
|
|
41
|
+
'当前是源码或链接安装,只能检查版本;请手动更新源码,或迁移到 npm 安装。': 'This is a source or linked installation. You can check versions, but must update the source manually or migrate to an npm installation.',
|
|
42
|
+
'无法确认当前 profile,请在对应的 Harness 环境中手动更新。': 'The current profile could not be identified. Update manually in the corresponding Harness environment.',
|
|
43
|
+
'当前运行环境不支持按钮安装,请手动更新插件。': 'This runtime cannot install through this button. Update the plugin manually.',
|
|
44
|
+
'当前 npm 源配置与官方源不一致,请先检查 registry 配置。': 'The npm registry configuration differs from the official registry. Check your registry settings first.',
|
|
45
|
+
'当前 Host 的 Node.js 版本不满足新版要求,请先更新运行环境。': 'The Host Node.js version does not meet the new version’s requirements. Update the runtime first.',
|
|
46
|
+
'新版本已安装,请在方便时手动重启当前 Harness 或 Desktop。': 'The new version is installed. Manually restart this Harness or Desktop when convenient.',
|
|
47
|
+
'上次安装结果无法确认,请先检查此 profile 的插件安装状态。': 'The previous installation result is uncertain. Check this profile’s plugin installation first.',
|
|
48
|
+
'无法访问 npm 或请求超时,请稍后重新检查。': 'npm is unreachable or the request timed out. Check again later.',
|
|
49
|
+
'npm 返回的版本信息无效,暂时无法更新。': 'npm returned invalid release information. Updating is unavailable.',
|
|
50
|
+
'版本确认已过期,请重新检查后再安装。': 'The version confirmation expired. Check again before installing.',
|
|
51
|
+
'插件安装状态已发生变化,请重新检查。': 'The plugin installation changed. Check again.',
|
|
52
|
+
'此 profile 正在更新,请稍后查看状态。': 'This profile is already updating. Check its status shortly.',
|
|
53
|
+
'安装失败,请检查当前安装状态后重试。': 'Installation failed. Check the current installation before retrying.',
|
|
54
|
+
'安装结果校验失败,请手动检查插件版本。': 'Installation verification failed. Check the plugin version manually.',
|
|
55
|
+
'无法安全保存更新状态,请先检查当前安装结果。': 'Update state could not be saved safely. Check the current installation result before retrying.',
|
|
56
|
+
'上次更新已中断,请检查安装状态后重试。': 'The previous update was interrupted. Check the installation before retrying.',
|
|
57
|
+
'更新服务已关闭,请手动重新打开设置页。': 'The update service has closed. Reopen settings manually.',
|
|
58
|
+
'更新请求无效,请重新检查版本。': 'The update request is invalid. Check the version again.',
|
|
59
|
+
'当前插件安装不完整或与 profile 不符,请手动检查安装配置。': 'The plugin installation is incomplete or does not match this profile. Check the installation configuration manually.',
|
|
60
|
+
'无法确认当前 npm 源配置,暂时不能安装更新。': 'The npm registry configuration could not be verified. Installation is unavailable.',
|
|
61
|
+
'安装超时,请先确认当前安装状态,再决定是否重试。': 'Installation timed out. Check the current installation before retrying.',
|
|
62
|
+
'更新请求失败,请重试。': 'The update request failed. Try again.',
|
|
63
|
+
'更新服务返回了无法识别的响应。': 'The update service returned an unrecognized response.',
|
|
64
|
+
'当前 Host 不支持更新接口,请先手动更新插件并重启。': 'This Host does not support the update API. Update the plugin manually and restart first.',
|
|
65
|
+
'关闭窗口不会取消安装。请勿同时在其他窗口管理此 profile 的插件。': 'Closing this window does not cancel installation. Do not manage this profile’s plugins in another window at the same time.',
|
|
66
|
+
'页面版本与运行版本不同,请手动刷新页面;若仍不一致,请手动重启 Harness 或 Desktop。': 'The page and running versions differ. Refresh the page manually; if they still differ, manually restart Harness or Desktop.',
|
|
67
|
+
'请在机器人空闲时安装;安装会修改当前 profile 的依赖,完成后需手动重启。': 'Install while bots are idle. This changes the current profile’s dependencies and requires a manual restart afterward.',
|
|
12
68
|
'AI Office': 'AI Office',
|
|
13
69
|
'(实验功能)': '(Experimental)',
|
|
14
70
|
'AI Office 设置': 'AI Office settings',
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
replacePageLocation,
|
|
49
49
|
} from './loopback-recovery.js';
|
|
50
50
|
import { installImStyles } from './styles.js';
|
|
51
|
+
import { UpdatePanel, UPDATE_RPC_CHANNEL } from './update-panel.js';
|
|
51
52
|
import { WorkspaceDirectoryPickerContext } from './workspace-editor.js';
|
|
52
53
|
|
|
53
54
|
export const name = 'im-settings';
|
|
@@ -155,17 +156,22 @@ export function IMSettingsTab({
|
|
|
155
156
|
weixinRpcCall,
|
|
156
157
|
whatsappRpcCall,
|
|
157
158
|
officeRpcCall,
|
|
159
|
+
updateRpcCall,
|
|
158
160
|
workspaceDirectoryPicker,
|
|
159
161
|
browserLocation = globalThis.location,
|
|
160
162
|
navigateToRecoveryUrl = replacePageLocation,
|
|
161
163
|
}) {
|
|
162
164
|
const [selected, setSelected] = React.useState('weixin');
|
|
163
165
|
const [loopbackRecovery, setLoopbackRecovery] = React.useState(null);
|
|
166
|
+
const [runningVersion, setRunningVersion] = React.useState(IM_PLUGIN_VERSION);
|
|
164
167
|
const githubTooltipId = React.useId();
|
|
165
168
|
const active = CHANNELS.find((channel) => channel.id === selected) ?? CHANNELS[0];
|
|
166
169
|
const reportLoopbackRecovery = React.useCallback((recovery) => {
|
|
167
170
|
setLoopbackRecovery((current) => current?.url === recovery.url ? current : recovery);
|
|
168
171
|
}, []);
|
|
172
|
+
const reportUpdateStatus = React.useCallback((snapshot) => {
|
|
173
|
+
setRunningVersion(snapshot.runningVersion);
|
|
174
|
+
}, []);
|
|
169
175
|
const rpcCalls = React.useMemo(() => createLoopbackAwareRpcCalls({
|
|
170
176
|
dingtalkRpcCall,
|
|
171
177
|
discordRpcCall,
|
|
@@ -177,6 +183,7 @@ export function IMSettingsTab({
|
|
|
177
183
|
weixinRpcCall,
|
|
178
184
|
whatsappRpcCall,
|
|
179
185
|
officeRpcCall,
|
|
186
|
+
updateRpcCall,
|
|
180
187
|
}, {
|
|
181
188
|
location: browserLocation,
|
|
182
189
|
onRecovery: reportLoopbackRecovery,
|
|
@@ -190,6 +197,7 @@ export function IMSettingsTab({
|
|
|
190
197
|
reportLoopbackRecovery,
|
|
191
198
|
slackRpcCall,
|
|
192
199
|
telegramRpcCall,
|
|
200
|
+
updateRpcCall,
|
|
193
201
|
wecomRpcCall,
|
|
194
202
|
weixinRpcCall,
|
|
195
203
|
whatsappRpcCall,
|
|
@@ -200,8 +208,14 @@ export function IMSettingsTab({
|
|
|
200
208
|
h('div', { className: 'dim-brand' },
|
|
201
209
|
h('div', { className: 'dim-brandHeading' },
|
|
202
210
|
h('strong', { className: 'dim-brandName' }, 'DSH-IM'),
|
|
203
|
-
h('span', { className: 'dim-brandVersion' }, `v${
|
|
211
|
+
h('span', { className: 'dim-brandVersion' }, `v${runningVersion}`)),
|
|
204
212
|
h('p', null, '让 DeepSeek Harness 触手可及')),
|
|
213
|
+
h('div', { className: 'dim-titleActions' },
|
|
214
|
+
h(UpdatePanel, {
|
|
215
|
+
rpcCall: rpcCalls.updateRpcCall,
|
|
216
|
+
clientVersion: IM_PLUGIN_VERSION,
|
|
217
|
+
onStatus: reportUpdateStatus,
|
|
218
|
+
}),
|
|
205
219
|
h('span', { className: 'dim-githubAction' },
|
|
206
220
|
h('a', {
|
|
207
221
|
className: 'dim-githubLink',
|
|
@@ -217,7 +231,7 @@ export function IMSettingsTab({
|
|
|
217
231
|
id: githubTooltipId,
|
|
218
232
|
className: 'dim-githubTooltip',
|
|
219
233
|
role: 'tooltip',
|
|
220
|
-
}, '帮助与反馈 · 前往 GitHub')),
|
|
234
|
+
}, '帮助与反馈 · 前往 GitHub'))),
|
|
221
235
|
),
|
|
222
236
|
h('div', { className: 'dim-layout' },
|
|
223
237
|
h('nav', { className: 'dim-rail', role: 'tablist', 'aria-label': 'IM 渠道' },
|
|
@@ -318,6 +332,8 @@ export function apply(ctx) {
|
|
|
318
332
|
ctx.connection.rpc.call(SLACK_RPC_CHANNEL, endpoint, payload, signal);
|
|
319
333
|
const officeRpcCall = (endpoint, payload, signal) =>
|
|
320
334
|
ctx.connection.rpc.call(OFFICE_RPC_CHANNEL, endpoint, payload, signal);
|
|
335
|
+
const updateRpcCall = (endpoint, payload, signal) =>
|
|
336
|
+
ctx.connection.rpc.call(UPDATE_RPC_CHANNEL, endpoint, payload, signal);
|
|
321
337
|
const workspaceDirectoryPicker = Object.freeze({
|
|
322
338
|
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal),
|
|
323
339
|
pickDirectory: () => ctx.workspaces.pickDirectory(),
|
|
@@ -340,6 +356,7 @@ export function apply(ctx) {
|
|
|
340
356
|
weixinRpcCall,
|
|
341
357
|
whatsappRpcCall,
|
|
342
358
|
officeRpcCall,
|
|
359
|
+
updateRpcCall,
|
|
343
360
|
workspaceDirectoryPicker,
|
|
344
361
|
}),
|
|
345
362
|
}, IMSettingsTab));
|
|
@@ -17,6 +17,32 @@ const CSS = String.raw`
|
|
|
17
17
|
.dim-brandName { color: var(--dsw-alias-label-primary, #1f2329); font-size: 20px; line-height: 24px; font-weight: 800; letter-spacing: .04em; }
|
|
18
18
|
.dim-brandVersion { color: var(--dsw-alias-label-tertiary, #8f959e); font: 500 10px/16px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0; }
|
|
19
19
|
.dim-title p { margin: 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 18px; font-weight: 500; white-space: nowrap; }
|
|
20
|
+
.dim-titleActions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
|
|
21
|
+
.dim-updateButton { min-height: 30px; display: inline-flex; align-items: center; justify-content: center; gap: 3px; padding: 5px 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; line-height: 18px; font-weight: 560; cursor: pointer; }
|
|
22
|
+
.dim-updateButton:hover:not(:disabled) { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
23
|
+
.dim-updateButton:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
24
|
+
.dim-updateButton:disabled { opacity: .55; cursor: default; }
|
|
25
|
+
.dim-updateTrigger { white-space: nowrap; }
|
|
26
|
+
.dim-updateBackdrop { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 17 21 / 42%); }
|
|
27
|
+
.dim-updateBackdrop, .dim-updateBackdrop * { box-sizing: border-box; }
|
|
28
|
+
.dim-updateDialog { width: min(480px, 100%); max-height: calc(100vh - 48px); overflow-y: auto; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 16px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 20px 70px rgb(0 0 0 / 20%); text-align: left; }
|
|
29
|
+
.dim-updateDialog:focus { outline: none; }
|
|
30
|
+
.dim-updateDialog h3 { margin: 22px 24px 8px; font-size: 18px; line-height: 25px; font-weight: 680; }
|
|
31
|
+
.dim-updateDescription { margin: 0 24px; color: var(--dsw-alias-label-secondary, #646a73); font-size: 13px; line-height: 20px; }
|
|
32
|
+
.dim-updateBody { padding: 18px 24px 20px; }
|
|
33
|
+
.dim-updateVersions { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 18px; margin: 0 0 18px; font-size: 12px; line-height: 18px; }
|
|
34
|
+
.dim-updateVersions dt { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
35
|
+
.dim-updateVersions dd { min-width: 0; margin: 0; overflow-wrap: anywhere; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
36
|
+
.dim-updateStatus { padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 10px; background: var(--dsw-alias-bg-layer-1, #f7f8fa); font-size: 13px; line-height: 20px; }
|
|
37
|
+
.dim-updateStatus strong { font-weight: 600; }
|
|
38
|
+
.dim-updateStatus p { margin: 6px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 19px; overflow-wrap: anywhere; }
|
|
39
|
+
.dim-updateStatusError { border-color: color-mix(in srgb, var(--dsw-alias-state-danger-primary, #d92d20) 25%, var(--dsw-alias-border-l2, #dfe1e5)); }
|
|
40
|
+
.dim-updateHint, .dim-updateError { margin: 12px 0 0; font-size: 12px; line-height: 19px; overflow-wrap: anywhere; }
|
|
41
|
+
.dim-updateHint { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
42
|
+
.dim-updateError { color: var(--dsw-alias-state-danger-primary, #d92d20); }
|
|
43
|
+
.dim-updateFooter { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding: 14px 24px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
44
|
+
.dim-updateFooter .dim-updateButton:first-child { margin-right: auto; }
|
|
45
|
+
.dim-updatePrimary, .dim-updatePrimary:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
20
46
|
.dim-githubAction { position: relative; display: inline-flex; flex: none; }
|
|
21
47
|
.dim-githubLink { min-height: 30px; display: inline-flex; align-items: center; gap: 5px; flex: none; padding: 0 10px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 12px; line-height: normal; font-weight: 560; text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; }
|
|
22
48
|
.dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
@@ -25,7 +51,7 @@ const CSS = String.raw`
|
|
|
25
51
|
.dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
26
52
|
.dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
27
53
|
.dim-layout { display: grid; grid-template-columns: 174px 1px minmax(0, 1fr); gap: 24px; align-items: start; }
|
|
28
|
-
.dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding:
|
|
54
|
+
.dim-rail { max-height: 520px; display: grid; align-content: start; gap: 8px; overflow-y: auto; padding: 0 4px 1px 1px; scrollbar-width: thin; scrollbar-color: var(--dsw-alias-border-l2, #dfe1e5) transparent; }
|
|
29
55
|
.dim-rail::-webkit-scrollbar { width: 4px; }
|
|
30
56
|
.dim-rail::-webkit-scrollbar-thumb { border-radius: 99px; background: var(--dsw-alias-border-l2, #dfe1e5); }
|
|
31
57
|
.dim-channel { width: 100%; min-height: 48px; display: grid; grid-template-columns: 30px minmax(0, 1fr); align-items: center; gap: 10px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2, #eef0f3); border-radius: 14px; color: inherit; background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 2px 8px rgb(31 35 41 / 3%); font: inherit; text-align: left; cursor: pointer; transition: border-color .16s ease, background .16s ease, box-shadow .16s ease; }
|
|
@@ -284,6 +310,13 @@ const CSS = String.raw`
|
|
|
284
310
|
@media (max-width: 560px) {
|
|
285
311
|
.dim-title { flex-direction: column; gap: 10px; }
|
|
286
312
|
.dim-title p { white-space: normal; }
|
|
313
|
+
.dim-titleActions { justify-content: flex-start; }
|
|
314
|
+
.dim-updateBackdrop { padding: 12px; }
|
|
315
|
+
.dim-updateDialog { max-height: calc(100vh - 24px); }
|
|
316
|
+
.dim-updateDialog h3 { margin: 18px 18px 8px; }
|
|
317
|
+
.dim-updateDescription { margin: 0 18px; }
|
|
318
|
+
.dim-updateBody { padding: 16px 18px; }
|
|
319
|
+
.dim-updateFooter { padding: 12px 18px; }
|
|
287
320
|
.dim-githubTooltip { right: auto; left: 0; }
|
|
288
321
|
.dim-rail { grid-template-columns: minmax(0, 1fr); }
|
|
289
322
|
.dim-loopbackRecovery { align-items: stretch; flex-direction: column; gap: 12px; }
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
import { h } from './i18n.js';
|
|
5
|
+
import { createPollScheduler } from './lifecycle.js';
|
|
6
|
+
|
|
7
|
+
export const UPDATE_RPC_CHANNEL = '/dsh-im';
|
|
8
|
+
|
|
9
|
+
const ACTIVE_STATES = new Set(['installing', 'verifying']);
|
|
10
|
+
const BLOCKED_REASONS = Object.freeze({
|
|
11
|
+
'source-install': '当前是源码或链接安装,只能检查版本;请手动更新源码,或迁移到 npm 安装。',
|
|
12
|
+
'unknown-profile': '无法确认当前 profile,请在对应的 Harness 环境中手动更新。',
|
|
13
|
+
'unsupported-runtime': '当前运行环境不支持按钮安装,请手动更新插件。',
|
|
14
|
+
'registry-conflict': '当前 npm 源配置与官方源不一致,请先检查 registry 配置。',
|
|
15
|
+
'incompatible-node': '当前 Host 的 Node.js 版本不满足新版要求,请先更新运行环境。',
|
|
16
|
+
'pending-restart': '新版本已安装,请在方便时手动重启当前 Harness 或 Desktop。',
|
|
17
|
+
'recovery-required': '上次安装结果无法确认,请先检查此 profile 的插件安装状态。',
|
|
18
|
+
});
|
|
19
|
+
const ERROR_MESSAGES = Object.freeze({
|
|
20
|
+
'check-failed': '无法访问 npm 或请求超时,请稍后重新检查。',
|
|
21
|
+
'update-failed': '更新请求失败,请重试。',
|
|
22
|
+
'invalid-release': 'npm 返回的版本信息无效,暂时无法更新。',
|
|
23
|
+
'check-expired': '版本确认已过期,请重新检查后再安装。',
|
|
24
|
+
'installation-changed': '插件安装状态已发生变化,请重新检查。',
|
|
25
|
+
'update-busy': '此 profile 正在更新,请稍后查看状态。',
|
|
26
|
+
'install-failed': '安装失败,请检查当前安装状态后重试。',
|
|
27
|
+
'verify-failed': '安装结果校验失败,请手动检查插件版本。',
|
|
28
|
+
'state-unavailable': '无法安全保存更新状态,请先检查当前安装结果。',
|
|
29
|
+
interrupted: '上次更新已中断,请检查安装状态后重试。',
|
|
30
|
+
disposed: '更新服务已关闭,请手动重新打开设置页。',
|
|
31
|
+
'bad-request': '更新请求无效,请重新检查版本。',
|
|
32
|
+
'invalid-installation': '当前插件安装不完整或与 profile 不符,请手动检查安装配置。',
|
|
33
|
+
'executor-unavailable': BLOCKED_REASONS['unsupported-runtime'],
|
|
34
|
+
'registry-check-failed': '无法确认当前 npm 源配置,暂时不能安装更新。',
|
|
35
|
+
'install-interrupted': '上次更新已中断,请检查安装状态后重试。',
|
|
36
|
+
'install-timeout': '安装超时,请先确认当前安装状态,再决定是否重试。',
|
|
37
|
+
'invalid-version': 'npm 返回的版本信息无效,暂时无法更新。',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function unwrapSnapshot(result) {
|
|
41
|
+
if (result?.ok === false) {
|
|
42
|
+
const error = new Error(result.error?.message || '更新请求失败,请重试。');
|
|
43
|
+
error.code = result.error?.code;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const value = result?.value;
|
|
47
|
+
if (result?.ok !== true || typeof value?.runningVersion !== 'string'
|
|
48
|
+
|| typeof value?.canInstall !== 'boolean') {
|
|
49
|
+
throw new Error('更新服务返回了无法识别的响应。');
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function presentError(error) {
|
|
55
|
+
const code = error?.rpcError?.code ?? error?.code ?? '';
|
|
56
|
+
const message = error?.rpcError?.message ?? error?.message ?? '';
|
|
57
|
+
if (code === 'update-unavailable'
|
|
58
|
+
|| /(?:not[-_ ]found|unimplemented|unknown[-_ ](?:endpoint|channel)|no[-_ ]handler)/i.test(code)
|
|
59
|
+
|| /(?:not found|unknown (?:endpoint|channel)|not registered|no .*handler|HTTP 404)/i.test(message)) {
|
|
60
|
+
return '当前 Host 不支持更新接口,请先手动更新插件并重启。';
|
|
61
|
+
}
|
|
62
|
+
return BLOCKED_REASONS[code] ?? ERROR_MESSAGES[code]
|
|
63
|
+
?? (message.slice(0, 400) || '更新请求失败,请重试。');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function summary(snapshot, action, error) {
|
|
67
|
+
if (action === 'checking') return '正在从 npm 检查最新版本…';
|
|
68
|
+
if (action === 'starting' || snapshot?.job?.state === 'installing') return '正在安装,请稍候…';
|
|
69
|
+
if (snapshot?.job?.state === 'verifying') return '正在校验安装结果…';
|
|
70
|
+
if (snapshot?.job?.state === 'restart-required' || snapshot?.blockedReason === 'pending-restart') {
|
|
71
|
+
return '已安装,待手动重启';
|
|
72
|
+
}
|
|
73
|
+
if (snapshot?.job?.state === 'completed') return '更新已生效';
|
|
74
|
+
if (snapshot?.job?.state === 'failed') return '更新失败';
|
|
75
|
+
if (snapshot?.job?.state === 'interrupted') return '上次更新已中断,请检查安装状态后重试。';
|
|
76
|
+
if (error) return '更新请求失败';
|
|
77
|
+
if (snapshot?.canInstall) return '发现新版本';
|
|
78
|
+
if (snapshot?.checkedAt && snapshot.latestVersion === snapshot.runningVersion) return '已是最新版本';
|
|
79
|
+
if (snapshot?.blockedReason === 'no-update') return '当前版本无需更新';
|
|
80
|
+
if (snapshot?.checkedAt) return '已获取 npm 最新版本';
|
|
81
|
+
return '检查 npm 最新版本,不会自动安装。';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function retainDialogFocus(event) {
|
|
85
|
+
event?.currentTarget?.closest?.('.dim-updateDialog')?.focus?.({ preventScroll: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function UpdateDialog({ children, onClose }) {
|
|
89
|
+
const dialogRef = React.useRef(null);
|
|
90
|
+
const titleId = React.useId();
|
|
91
|
+
const descriptionId = React.useId();
|
|
92
|
+
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
const previous = globalThis.document?.activeElement;
|
|
95
|
+
dialogRef.current?.focus?.();
|
|
96
|
+
return () => { if (previous?.isConnected) previous.focus?.(); };
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const content = h('div', {
|
|
100
|
+
className: 'dim-updateBackdrop',
|
|
101
|
+
onMouseDown: (event) => { if (event.target === event.currentTarget) onClose(); },
|
|
102
|
+
},
|
|
103
|
+
h('section', {
|
|
104
|
+
ref: dialogRef,
|
|
105
|
+
className: 'dim-updateDialog',
|
|
106
|
+
role: 'dialog',
|
|
107
|
+
'aria-modal': 'true',
|
|
108
|
+
'aria-labelledby': titleId,
|
|
109
|
+
'aria-describedby': descriptionId,
|
|
110
|
+
tabIndex: -1,
|
|
111
|
+
onKeyDown: (event) => {
|
|
112
|
+
if (event.key === 'Escape') {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
onClose();
|
|
116
|
+
}
|
|
117
|
+
if (event.key !== 'Tab') return;
|
|
118
|
+
const buttons = dialogRef.current?.querySelectorAll?.('button:not(:disabled)');
|
|
119
|
+
if (!buttons?.length) return;
|
|
120
|
+
const first = buttons[0];
|
|
121
|
+
const last = buttons[buttons.length - 1];
|
|
122
|
+
const active = globalThis.document?.activeElement;
|
|
123
|
+
if (event.shiftKey && (active === first || active === dialogRef.current)) {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
last.focus();
|
|
126
|
+
} else if (!event.shiftKey && (active === last || active === dialogRef.current)) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
first.focus();
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
h('h3', { id: titleId }, 'DSH-IM 更新'),
|
|
133
|
+
h('p', { id: descriptionId, className: 'dim-updateDescription' },
|
|
134
|
+
'仅更新 DSH-IM。安装完成后需手动重启后台;本功能不会自动重启或主动刷新页面。'),
|
|
135
|
+
children));
|
|
136
|
+
return typeof document !== 'undefined' && document.body
|
|
137
|
+
? createPortal(content, document.body)
|
|
138
|
+
: content;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
142
|
+
const [snapshot, setSnapshot] = React.useState(null);
|
|
143
|
+
const [action, setAction] = React.useState('status');
|
|
144
|
+
const [error, setError] = React.useState(null);
|
|
145
|
+
const [open, setOpen] = React.useState(false);
|
|
146
|
+
const [uncertainInstall, setUncertainInstall] = React.useState(false);
|
|
147
|
+
const mounted = React.useRef(false);
|
|
148
|
+
const busy = React.useRef(false);
|
|
149
|
+
const readController = React.useRef(null);
|
|
150
|
+
const pollReadController = React.useRef(null);
|
|
151
|
+
const installRequest = React.useRef(null);
|
|
152
|
+
const onStatusRef = React.useRef(onStatus);
|
|
153
|
+
onStatusRef.current = onStatus;
|
|
154
|
+
|
|
155
|
+
const accept = React.useCallback((next) => {
|
|
156
|
+
setSnapshot(next);
|
|
157
|
+
onStatusRef.current?.(next);
|
|
158
|
+
}, []);
|
|
159
|
+
const acceptAuthoritative = (next) => {
|
|
160
|
+
// Abort synchronously: effect cleanup alone can lose to an already queued reply.
|
|
161
|
+
pollReadController.current?.abort();
|
|
162
|
+
setUncertainInstall(false);
|
|
163
|
+
accept(next);
|
|
164
|
+
};
|
|
165
|
+
const invoke = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
166
|
+
if (typeof rpcCall !== 'function') {
|
|
167
|
+
const unavailable = new Error('当前 Host 不支持更新接口,请先手动更新插件并重启。');
|
|
168
|
+
unavailable.code = 'update-unavailable';
|
|
169
|
+
throw unavailable;
|
|
170
|
+
}
|
|
171
|
+
return unwrapSnapshot(await rpcCall(endpoint, payload, signal));
|
|
172
|
+
}, [rpcCall]);
|
|
173
|
+
|
|
174
|
+
React.useEffect(() => {
|
|
175
|
+
mounted.current = true;
|
|
176
|
+
busy.current = true;
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
void invoke('update.status', {}, controller.signal).then((next) => {
|
|
179
|
+
if (!controller.signal.aborted) accept(next);
|
|
180
|
+
}).catch((cause) => {
|
|
181
|
+
if (!controller.signal.aborted) setError(presentError(cause));
|
|
182
|
+
}).finally(() => {
|
|
183
|
+
if (!controller.signal.aborted) {
|
|
184
|
+
busy.current = false;
|
|
185
|
+
setAction(null);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return () => {
|
|
189
|
+
mounted.current = false;
|
|
190
|
+
controller.abort();
|
|
191
|
+
readController.current?.abort();
|
|
192
|
+
};
|
|
193
|
+
}, [accept, invoke]);
|
|
194
|
+
|
|
195
|
+
const activeJob = ACTIVE_STATES.has(snapshot?.job?.state);
|
|
196
|
+
const restartRequired = snapshot?.job?.state === 'restart-required'
|
|
197
|
+
|| snapshot?.blockedReason === 'pending-restart';
|
|
198
|
+
const shouldPoll = activeJob || uncertainInstall;
|
|
199
|
+
|
|
200
|
+
React.useEffect(() => {
|
|
201
|
+
if (!shouldPoll) return undefined;
|
|
202
|
+
let controller;
|
|
203
|
+
const scheduler = createPollScheduler({
|
|
204
|
+
setTimeoutFn: (callback, delay) => globalThis.setTimeout(callback, delay),
|
|
205
|
+
clearTimeoutFn: (timer) => globalThis.clearTimeout(timer),
|
|
206
|
+
});
|
|
207
|
+
const poll = async () => {
|
|
208
|
+
const pendingController = new AbortController();
|
|
209
|
+
controller = pendingController;
|
|
210
|
+
pollReadController.current = pendingController;
|
|
211
|
+
let keepPolling = true;
|
|
212
|
+
try {
|
|
213
|
+
const next = await invoke('update.status', {}, pendingController.signal);
|
|
214
|
+
if (!pendingController.signal.aborted) {
|
|
215
|
+
accept(next);
|
|
216
|
+
setError(null);
|
|
217
|
+
setUncertainInstall(false);
|
|
218
|
+
keepPolling = ACTIVE_STATES.has(next.job?.state);
|
|
219
|
+
}
|
|
220
|
+
} catch (cause) {
|
|
221
|
+
if (!pendingController.signal.aborted) setError(presentError(cause));
|
|
222
|
+
} finally {
|
|
223
|
+
if (pollReadController.current === pendingController) pollReadController.current = null;
|
|
224
|
+
}
|
|
225
|
+
if (keepPolling) scheduler.schedule(poll, 1_000);
|
|
226
|
+
};
|
|
227
|
+
scheduler.schedule(poll, 1_000);
|
|
228
|
+
return () => {
|
|
229
|
+
controller?.abort();
|
|
230
|
+
scheduler.dispose();
|
|
231
|
+
};
|
|
232
|
+
}, [accept, invoke, shouldPoll]);
|
|
233
|
+
|
|
234
|
+
const readSnapshot = async (endpoint) => {
|
|
235
|
+
const checkLatest = endpoint === 'update.check';
|
|
236
|
+
if (!mounted.current || busy.current || (checkLatest && (activeJob || restartRequired))) return;
|
|
237
|
+
busy.current = true;
|
|
238
|
+
setAction(checkLatest ? 'checking' : 'status');
|
|
239
|
+
setError(null);
|
|
240
|
+
if (checkLatest) {
|
|
241
|
+
installRequest.current = null;
|
|
242
|
+
setSnapshot((current) => current
|
|
243
|
+
? { ...current, canInstall: false, checkId: null, latestVersion: null, checkedAt: null }
|
|
244
|
+
: current);
|
|
245
|
+
}
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
readController.current = controller;
|
|
248
|
+
try {
|
|
249
|
+
const next = await invoke(endpoint, {}, controller.signal);
|
|
250
|
+
if (!controller.signal.aborted && mounted.current) {
|
|
251
|
+
if (checkLatest) accept(next);
|
|
252
|
+
else acceptAuthoritative(next);
|
|
253
|
+
}
|
|
254
|
+
} catch (cause) {
|
|
255
|
+
if (!controller.signal.aborted && mounted.current) setError(presentError(cause));
|
|
256
|
+
} finally {
|
|
257
|
+
if (!controller.signal.aborted && mounted.current) {
|
|
258
|
+
busy.current = false;
|
|
259
|
+
setAction(null);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
const check = () => readSnapshot('update.check');
|
|
264
|
+
const refreshStatus = () => readSnapshot('update.status');
|
|
265
|
+
|
|
266
|
+
const install = async () => {
|
|
267
|
+
if (busy.current || !snapshot?.canInstall || !snapshot.checkId) return;
|
|
268
|
+
busy.current = true;
|
|
269
|
+
setAction('starting');
|
|
270
|
+
setError(null);
|
|
271
|
+
if (installRequest.current?.checkId !== snapshot.checkId) {
|
|
272
|
+
installRequest.current = {
|
|
273
|
+
checkId: snapshot.checkId,
|
|
274
|
+
requestId: globalThis.crypto?.randomUUID?.()
|
|
275
|
+
?? `update-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
// Closing this page only aborts reads, never an accepted Host installation.
|
|
280
|
+
const next = await invoke('update.install', installRequest.current);
|
|
281
|
+
if (mounted.current) acceptAuthoritative(next);
|
|
282
|
+
} catch (cause) {
|
|
283
|
+
if (mounted.current) {
|
|
284
|
+
setError(presentError(cause));
|
|
285
|
+
setUncertainInstall(true);
|
|
286
|
+
}
|
|
287
|
+
} finally {
|
|
288
|
+
if (mounted.current) {
|
|
289
|
+
busy.current = false;
|
|
290
|
+
setAction(null);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const busyAction = action !== null;
|
|
296
|
+
const blocked = BLOCKED_REASONS[snapshot?.blockedReason] ?? ERROR_MESSAGES[snapshot?.blockedReason];
|
|
297
|
+
const canConfirm = snapshot?.canInstall && snapshot.checkId && !activeJob && !restartRequired;
|
|
298
|
+
const versionsDiffer = snapshot && clientVersion && snapshot.runningVersion !== clientVersion;
|
|
299
|
+
const failedJob = ['failed', 'interrupted'].includes(snapshot?.job?.state);
|
|
300
|
+
const jobMessage = snapshot?.job?.message;
|
|
301
|
+
const targetVersion = snapshot?.job?.targetVersion;
|
|
302
|
+
const buttonLabel = action === 'checking' ? '检查中…'
|
|
303
|
+
: action === 'starting' || activeJob ? '正在更新…'
|
|
304
|
+
: restartRequired ? '待手动重启'
|
|
305
|
+
: snapshot?.canInstall ? '更新至'
|
|
306
|
+
: '检查更新';
|
|
307
|
+
|
|
308
|
+
return h(React.Fragment, null,
|
|
309
|
+
h('button', {
|
|
310
|
+
type: 'button',
|
|
311
|
+
className: 'dim-updateButton dim-updateTrigger',
|
|
312
|
+
disabled: busyAction,
|
|
313
|
+
'aria-haspopup': 'dialog',
|
|
314
|
+
onClick: () => {
|
|
315
|
+
setOpen(true);
|
|
316
|
+
if (restartRequired) void refreshStatus();
|
|
317
|
+
else if (!snapshot?.canInstall && !snapshot?.job) void check();
|
|
318
|
+
},
|
|
319
|
+
}, buttonLabel, buttonLabel === '更新至' ? ` v${snapshot.latestVersion}` : null),
|
|
320
|
+
open ? h(UpdateDialog, { onClose: () => setOpen(false) },
|
|
321
|
+
h('div', { className: 'dim-updateBody' },
|
|
322
|
+
h('dl', { className: 'dim-updateVersions' },
|
|
323
|
+
h('dt', null, '运行版本'), h('dd', null, `v${snapshot?.runningVersion ?? clientVersion}`),
|
|
324
|
+
snapshot?.installedVersion && snapshot.installedVersion !== snapshot.runningVersion
|
|
325
|
+
? h(React.Fragment, null,
|
|
326
|
+
h('dt', null, '已安装版本'), h('dd', null, `v${snapshot.installedVersion}`)) : null,
|
|
327
|
+
h('dt', null, 'npm 最新版本'), h('dd', null, snapshot?.latestVersion ? `v${snapshot.latestVersion}` : '尚未检查'),
|
|
328
|
+
targetVersion ? h(React.Fragment, null,
|
|
329
|
+
h('dt', null, '目标版本'), h('dd', null, `v${targetVersion}`)) : null,
|
|
330
|
+
h('dt', null, '目标 profile'), h('dd', null, snapshot?.profileName ?? '无法确认'),
|
|
331
|
+
h('dt', null, '更新来源'), h('dd', null, 'registry.npmjs.org'),
|
|
332
|
+
versionsDiffer ? h(React.Fragment, null,
|
|
333
|
+
h('dt', null, '页面版本'), h('dd', null, `v${clientVersion}`)) : null),
|
|
334
|
+
h('div', {
|
|
335
|
+
className: `dim-updateStatus${error || failedJob ? ' dim-updateStatusError' : ''}`,
|
|
336
|
+
role: 'status',
|
|
337
|
+
'aria-live': 'polite',
|
|
338
|
+
'aria-atomic': 'true',
|
|
339
|
+
},
|
|
340
|
+
h('strong', null, summary(snapshot, action, error)),
|
|
341
|
+
activeJob || action === 'starting'
|
|
342
|
+
? h('p', null, '关闭窗口不会取消安装。请勿同时在其他窗口管理此 profile 的插件。') : null,
|
|
343
|
+
restartRequired ? h('p', null, BLOCKED_REASONS['pending-restart']) : null,
|
|
344
|
+
failedJob && jobMessage ? h('p', null,
|
|
345
|
+
BLOCKED_REASONS[jobMessage] ?? ERROR_MESSAGES[jobMessage] ?? jobMessage) : null),
|
|
346
|
+
error ? h('p', { className: 'dim-updateError', role: 'alert' }, error) : null,
|
|
347
|
+
blocked && !restartRequired ? h('p', { className: 'dim-updateHint' }, blocked) : null,
|
|
348
|
+
versionsDiffer && !restartRequired ? h('p', { className: 'dim-updateHint' }, '页面版本与运行版本不同,请手动刷新页面;若仍不一致,请手动重启 Harness 或 Desktop。') : null,
|
|
349
|
+
canConfirm ? h('p', { className: 'dim-updateHint' },
|
|
350
|
+
'请在机器人空闲时安装;安装会修改当前 profile 的依赖,完成后需手动重启。') : null),
|
|
351
|
+
h('footer', { className: 'dim-updateFooter' },
|
|
352
|
+
h('button', { type: 'button', className: 'dim-updateButton', onClick: () => setOpen(false) }, '关闭'),
|
|
353
|
+
restartRequired || !activeJob ? h('button', {
|
|
354
|
+
type: 'button', className: 'dim-updateButton', disabled: busyAction,
|
|
355
|
+
onClick: (event) => {
|
|
356
|
+
retainDialogFocus(event);
|
|
357
|
+
void (restartRequired ? refreshStatus() : check());
|
|
358
|
+
},
|
|
359
|
+
}, restartRequired ? '刷新状态' : '重新检查') : null,
|
|
360
|
+
canConfirm ? h('button', {
|
|
361
|
+
type: 'button', className: 'dim-updateButton dim-updatePrimary', disabled: busyAction,
|
|
362
|
+
onClick: (event) => {
|
|
363
|
+
retainDialogFocus(event);
|
|
364
|
+
void install();
|
|
365
|
+
},
|
|
366
|
+
}, '安装更新') : null)) : null);
|
|
367
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installDingtalkRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-dingtalk-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', '
|
|
5
|
+
export const inject = ['connection', 'credentials', 'apiProxy', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -17,17 +17,9 @@ import {
|
|
|
17
17
|
import { listAgentPresetCatalog } from '../../../../src/channels/shared/agent-preset.mjs';
|
|
18
18
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
19
19
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
20
|
+
import { harnessConnection } from '../../harness-connection.mjs';
|
|
20
21
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
21
22
|
|
|
22
|
-
function harnessOrigin(webServer, configured) {
|
|
23
|
-
if (configured !== undefined) return new URL(configured);
|
|
24
|
-
const port = webServer?.port;
|
|
25
|
-
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
26
|
-
throw new Error('dsh-dingtalk requires an initialized DSH webServer port');
|
|
27
|
-
}
|
|
28
|
-
return new URL(`http://127.0.0.1:${port}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
23
|
function pluginPaths(config) {
|
|
32
24
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
33
25
|
const root = resolve(config.dataDir ?? join(dshHome, 'integrations', 'dsh-dingtalk'));
|
|
@@ -41,7 +33,7 @@ function pluginPaths(config) {
|
|
|
41
33
|
|
|
42
34
|
export async function createProductionController(ctx, config = {}, internals = {}) {
|
|
43
35
|
if (!ctx?.credentials) throw new TypeError('dsh-dingtalk requires ctx.credentials');
|
|
44
|
-
|
|
36
|
+
const connection = harnessConnection(ctx, config);
|
|
45
37
|
|
|
46
38
|
const ConfigStore = internals.ConfigStore ?? DingtalkConfigStore;
|
|
47
39
|
const DeviceAuth = internals.DeviceAuth ?? DingtalkDeviceAuth;
|
|
@@ -92,7 +84,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
92
84
|
fileIngressExecutor: internals.fileIngressExecutor,
|
|
93
85
|
});
|
|
94
86
|
const harness = new Harness({
|
|
95
|
-
|
|
87
|
+
...connection,
|
|
96
88
|
workspace: defaultWorkspace,
|
|
97
89
|
autostart: false,
|
|
98
90
|
dshBin: config.dshBin ?? 'dsh',
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installDiscordRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-discord-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', '
|
|
5
|
+
export const inject = ['connection', 'credentials', 'apiProxy', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|