@ai-wendao/ant 0.3.13 → 0.3.15
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 +5 -3
- package/client.js +428 -81
- package/index.js +38 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,15 +6,17 @@ ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to its team's
|
|
|
6
6
|
|
|
7
7
|
## 企业精选技能
|
|
8
8
|
|
|
9
|
-
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md`
|
|
9
|
+
团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md` 导入技能,核对来源、文件和依赖后发布。已连接团队的成员从 DSH 左侧“企业扩展”入口进入独立页面浏览并安装技能;ANT 插件设置页只处理连接、模型和更新。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
|
|
10
10
|
|
|
11
11
|
安装不会自动运行技能脚本或安装 Python、Node 等系统依赖。ANT 只更新或卸载自己管理且本机内容未被修改的技能目录;发现同名个人技能或本机改动时会停止覆盖。
|
|
12
12
|
|
|
13
13
|
## 企业精选插件
|
|
14
14
|
|
|
15
|
-
管理员可从 npm 官方源的 DSH Bundle
|
|
15
|
+
管理员可从 npm 官方源的 DSH Bundle 精确版本创建草稿,填写源码、许可证、依赖和安装脚本的审核记录后发布。成员在“企业扩展 → 插件”查看审核信息和本机状态。对无新增依赖、无安装阶段脚本且归档符合首批准入条件的草稿,管理员可从 npm 官方源锁定实际 `.tgz` 并查看文件清单、下载核验制品;这类草稿经审核发布后,成员可让 DSH 从 ANT 的固定制品地址一键安装。其他版本仍是目录项,需联系管理员安排。安全撤回版本会停止新的 ANT 下载,但不会自动删除本机副本;ANT 设置页继续显示相应风险公告。范围与限制见[上线边界](../../docs/decisions/2026-10-15-plugin-center-rollout.md)。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
ANT 自身更新使用单独的已审核安装包。管理员将与 npm 发布一致的 `.tgz` 上传到 ANT 服务端并登记版本;新版 ANT 页面可点击“更新”,DSH 原位替换同名 Bundle 后提示重启。旧版 `@ai-wendao/ant` 用户可打开团队服务的 `/ant-update` 指引,在 DSH“添加插件”中粘贴该安装包地址完成一次性原位更新,无需先卸载或使用终端。此入口只分发 ANT 自身,不启用扩展中心其他插件的一键安装。
|
|
18
|
+
|
|
19
|
+
企业精选是团队推荐目录,不是强制白名单;ANT 不能阻止成员从 DSH 原生插件管理页自行安装其他包。第一批一键安装只分发 ANT 已核验并保存的顶层包,不把有依赖或安装脚本的版本伪装成已受控;DSH RC2 后续包管理能否一直保留这一固定制品仍需真机验收。上线边界见[待决策记录](../../docs/decisions/2026-10-15-plugin-center-rollout.md)。
|
|
18
20
|
|
|
19
21
|
## Existing `ant` alias installations
|
|
20
22
|
|
package/client.js
CHANGED
|
@@ -3,12 +3,13 @@ window.__ModuleLoader__.load({
|
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} }
|
|
5
5
|
var exports = module.exports
|
|
6
|
-
const { createElement, useEffect, useState } = require('react')
|
|
6
|
+
const { createElement, useEffect, useRef, useState } = require('react')
|
|
7
7
|
const h = createElement
|
|
8
8
|
const SERVICE_URL_STORAGE_KEY = 'ant-team-service-url'
|
|
9
9
|
const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
|
|
10
10
|
const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
|
|
11
11
|
const ANT_API_PROTOCOL_VERSIONS = [1]
|
|
12
|
+
const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
|
|
12
13
|
const fileCountFormat = new Intl.NumberFormat('zh-CN')
|
|
13
14
|
const panelStyle = {
|
|
14
15
|
display: 'grid', alignContent: 'start', gap: 14, padding: 18,
|
|
@@ -33,14 +34,15 @@ window.__ModuleLoader__.load({
|
|
|
33
34
|
border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
34
35
|
cursor: 'pointer',
|
|
35
36
|
}
|
|
37
|
+
const categoryButtonStyle = { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', whiteSpace: 'nowrap' }
|
|
36
38
|
function ExtensionRequestNotice() {
|
|
37
39
|
return h('aside', { role: 'note', style: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
display: 'flex', alignItems: 'baseline', gap: 8, padding: '9px 12px',
|
|
41
|
+
borderRadius: 8, backgroundColor: 'var(--dsw-alias-bg-layer-2)',
|
|
42
|
+
color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6,
|
|
41
43
|
} },
|
|
42
|
-
h('
|
|
43
|
-
h('
|
|
44
|
+
h('strong', { style: { flexShrink: 0, color: 'var(--dsw-alias-label-primary)' } }, '提示'),
|
|
45
|
+
h('span', null, '没有找到想用的扩展,或插件没有安装按钮?联系企业管理员,说明用途或提供地址。已开放的扩展可直接安装;未经审核的版本请勿自行安装。'))
|
|
44
46
|
}
|
|
45
47
|
const normalizeServiceUrl = (value) => {
|
|
46
48
|
let url
|
|
@@ -97,6 +99,35 @@ window.__ModuleLoader__.load({
|
|
|
97
99
|
: ''
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
function approvedArtifactUrl(release) {
|
|
103
|
+
const expectedPath = `/api/ant-bundles/${release?.version}/package.tgz`
|
|
104
|
+
let url
|
|
105
|
+
try { url = new URL(release?.artifact_url) } catch { return null }
|
|
106
|
+
return normalizeServiceUrl(url.origin) === url.origin
|
|
107
|
+
&& !url.username && !url.password && !url.search && !url.hash
|
|
108
|
+
&& url.pathname === expectedPath && release?.artifact_path === expectedPath
|
|
109
|
+
&& /^[a-f0-9]{64}$/u.test(release?.artifact_sha256 || '')
|
|
110
|
+
? url.href : null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function approvedPluginArtifactUrl(plugin) {
|
|
114
|
+
if (plugin?.status !== 'published' || typeof plugin.id !== 'string'
|
|
115
|
+
|| !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(plugin.id)) return null
|
|
116
|
+
const expectedPath = `/api/plugin-artifacts/${plugin.id}/package.tgz`
|
|
117
|
+
let url
|
|
118
|
+
try { url = new URL(plugin.artifact_url) } catch { return null }
|
|
119
|
+
return normalizeServiceUrl(url.origin) === url.origin
|
|
120
|
+
&& !url.username && !url.password && !url.search && !url.hash
|
|
121
|
+
&& url.pathname === expectedPath && plugin.artifact_path === expectedPath
|
|
122
|
+
&& /^[a-f0-9]{64}$/u.test(plugin.artifact_sha256 || '')
|
|
123
|
+
? url.href : null
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function previouslyInstalledReviewedPlugin(plugin) {
|
|
127
|
+
try { return localStorage.getItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`) === plugin.artifact_sha256 }
|
|
128
|
+
catch { return false }
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
function loadServiceUrl() {
|
|
101
132
|
try { return normalizeServiceUrl(localStorage.getItem(SERVICE_URL_STORAGE_KEY) || '') || '' } catch { return '' }
|
|
102
133
|
}
|
|
@@ -116,11 +147,41 @@ window.__ModuleLoader__.load({
|
|
|
116
147
|
return deviceId
|
|
117
148
|
}
|
|
118
149
|
|
|
119
|
-
function TeamServiceConfig({ remote,
|
|
150
|
+
function TeamServiceConfig({ remote, openInstalledPlugin }) {
|
|
120
151
|
const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
|
|
121
152
|
const [draft, setDraft] = useState(serviceUrl)
|
|
153
|
+
const [binding, setBinding] = useState({ state: 'checking', url: null })
|
|
154
|
+
const bindingRevision = useRef(0)
|
|
122
155
|
const [message, setMessage] = useState('')
|
|
123
156
|
const [pluginAlerts, setPluginAlerts] = useState([])
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
let disposed = false
|
|
159
|
+
const revision = bindingRevision.current
|
|
160
|
+
const current = () => !disposed && bindingRevision.current === revision
|
|
161
|
+
const inspectBinding = async () => {
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch('/api/ant-teams/connection-origin', { cache: 'no-store' })
|
|
164
|
+
const result = await readLocalJsonResponse(response)
|
|
165
|
+
if (!response.ok) throw new Error()
|
|
166
|
+
const pinned = result.service_url == null ? null : normalizeServiceUrl(result.service_url)
|
|
167
|
+
if (result.service_url != null && !pinned) throw new Error()
|
|
168
|
+
if (pinned) {
|
|
169
|
+
if (current()) {
|
|
170
|
+
setBinding({ state: 'ready', url: pinned })
|
|
171
|
+
setServiceUrl(pinned)
|
|
172
|
+
setDraft(pinned)
|
|
173
|
+
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, pinned) } catch {}
|
|
174
|
+
}
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
const described = await remote?.credentials?.describe?.(['ANT_MODEL_API_KEY'])
|
|
178
|
+
if (!described?.ok) throw new Error()
|
|
179
|
+
if (current()) setBinding({ state: described.value?.ANT_MODEL_API_KEY?.configured ? 'unverified' : 'ready', url: null })
|
|
180
|
+
} catch { if (current()) setBinding({ state: 'error', url: null }) }
|
|
181
|
+
}
|
|
182
|
+
void inspectBinding()
|
|
183
|
+
return () => { disposed = true }
|
|
184
|
+
}, [remote])
|
|
124
185
|
useEffect(() => {
|
|
125
186
|
let disposed = false
|
|
126
187
|
const inspect = async () => {
|
|
@@ -145,6 +206,10 @@ window.__ModuleLoader__.load({
|
|
|
145
206
|
}, [remote])
|
|
146
207
|
const save = (event) => {
|
|
147
208
|
event.preventDefault()
|
|
209
|
+
if (binding.state !== 'ready' || binding.url) {
|
|
210
|
+
setMessage(binding.url ? '当前已连接团队;切换前请先在下方断开当前设备。' : '尚未核对这台 DSH 的团队绑定,不能修改地址。')
|
|
211
|
+
return
|
|
212
|
+
}
|
|
148
213
|
const normalized = normalizeServiceUrl(draft)
|
|
149
214
|
if (!normalized) {
|
|
150
215
|
setMessage('请输入 HTTPS 团队服务地址;本机测试可使用 localhost。')
|
|
@@ -159,39 +224,50 @@ window.__ModuleLoader__.load({
|
|
|
159
224
|
setMessage('浏览器无法保存此地址,请检查本地存储权限。')
|
|
160
225
|
}
|
|
161
226
|
}
|
|
162
|
-
const link = consoleUrl(serviceUrl)
|
|
227
|
+
const link = binding.state === 'ready' ? consoleUrl(binding.url || serviceUrl) : null
|
|
228
|
+
const onConnectionChange = (url) => {
|
|
229
|
+
bindingRevision.current += 1
|
|
230
|
+
setBinding({ state: 'ready', url: url || null })
|
|
231
|
+
if (url) {
|
|
232
|
+
setServiceUrl(url)
|
|
233
|
+
setDraft(url)
|
|
234
|
+
try { localStorage.setItem(SERVICE_URL_STORAGE_KEY, url) } catch {}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
163
237
|
return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
|
|
164
238
|
h('p', { style: mutedTextStyle }, '连接团队服务后,即可同步获准模型并使用团队协作。'),
|
|
165
239
|
h('section', { style: panelStyle },
|
|
166
240
|
h('div', null,
|
|
167
241
|
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '团队服务'),
|
|
168
242
|
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
|
|
169
|
-
h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
|
|
170
|
-
h('label', { htmlFor: 'ant-team-service-url', style: { gridColumn: '1 / -1', fontSize: 13, fontWeight: 550 } }, '服务地址'),
|
|
243
|
+
h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: binding.url ? '1fr' : 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
|
|
244
|
+
h('label', { htmlFor: 'ant-team-service-url', style: { gridColumn: '1 / -1', fontSize: 13, fontWeight: 550 } }, binding.url ? '当前连接的团队服务地址' : '服务地址'),
|
|
171
245
|
h('input', {
|
|
172
246
|
id: 'ant-team-service-url',
|
|
173
247
|
type: 'url',
|
|
174
248
|
required: true,
|
|
175
|
-
value: draft,
|
|
249
|
+
value: binding.url || draft,
|
|
250
|
+
readOnly: binding.state !== 'ready' || Boolean(binding.url),
|
|
176
251
|
placeholder: 'https://ant.example.com',
|
|
177
|
-
onChange: (event) => setDraft(event.target.value),
|
|
252
|
+
onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) },
|
|
178
253
|
style: inputStyle,
|
|
179
254
|
}),
|
|
180
|
-
h('button', { type: 'submit', style: primaryButtonStyle }, '保存地址')),
|
|
255
|
+
binding.url ? null : h('button', { type: 'submit', disabled: binding.state !== 'ready', style: primaryButtonStyle }, '保存地址')),
|
|
256
|
+
binding.url ? h('p', { style: mutedTextStyle }, '若要切换团队,请先在下方断开当前设备,再填写新地址并验证邮箱。') : null,
|
|
257
|
+
binding.state === 'checking' ? h('p', { role: 'status', style: mutedTextStyle }, '正在核对这台 DSH 绑定的团队…') : null,
|
|
258
|
+
binding.state === 'unverified' ? h('p', { role: 'status', style: mutedTextStyle }, '本机仍有 ANT 凭证,但无法核对所属团队。请联系原团队管理员核查并撤销旧设备;确认后可在下方断开或仅清理本机。') : null,
|
|
259
|
+
binding.state === 'error' ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法核对这台 DSH 的团队绑定,请重新打开 ANT 或联系管理员。') : null,
|
|
181
260
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
|
|
182
261
|
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
183
262
|
link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)),
|
|
184
|
-
h('section', { style: { ...panelStyle, display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between' } },
|
|
185
|
-
h('div', null,
|
|
186
|
-
h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '企业精选扩展'),
|
|
187
|
-
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '浏览团队审核发布的技能与插件;技能可在这里安装,插件安全安装暂未开放。没有找到需要的扩展时,请联系企业管理员上架。')),
|
|
188
|
-
h('button', { type: 'button', onClick: openSkillCenter, style: primaryButtonStyle }, '打开扩展中心 →')),
|
|
189
263
|
pluginAlerts.length ? h('section', { role: 'alert', style: { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } },
|
|
190
264
|
h('h3', { style: { margin: 0, fontSize: 16 } }, '已安装插件存在安全撤回公告'),
|
|
191
265
|
...pluginAlerts.map((plugin) => h('p', { key: plugin.id, style: mutedTextStyle },
|
|
192
266
|
`${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
|
|
193
|
-
h('button', {
|
|
194
|
-
|
|
267
|
+
...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
|
|
268
|
+
onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
|
|
269
|
+
h(ModelSyncFooter, { remote, serviceUrl, bindingReady: binding.state === 'ready',
|
|
270
|
+
onServiceUrlChange: setServiceUrl, onConnectionChange }))
|
|
195
271
|
}
|
|
196
272
|
|
|
197
273
|
function SkillCenterPage({ onBack, onPlugins }) {
|
|
@@ -250,9 +326,11 @@ window.__ModuleLoader__.load({
|
|
|
250
326
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
251
327
|
padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
|
|
252
328
|
} }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
|
|
253
|
-
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
254
|
-
h('
|
|
255
|
-
|
|
329
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
|
|
330
|
+
h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
|
|
331
|
+
h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '技能'),
|
|
332
|
+
h('button', { type: 'button', onClick: onPlugins, style: categoryButtonStyle }, '插件')),
|
|
333
|
+
h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
|
|
256
334
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'end', justifyContent: 'space-between', gap: 16 } },
|
|
257
335
|
h('div', null,
|
|
258
336
|
h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选技能'),
|
|
@@ -283,9 +361,9 @@ window.__ModuleLoader__.load({
|
|
|
283
361
|
skill.local_status === 'conflict'
|
|
284
362
|
? h('p', { role: 'status', style: mutedTextStyle }, '本机同名技能有未受 ANT 管理的内容、文件已被修改或备份损坏;请联系管理员处理。')
|
|
285
363
|
: h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 'auto' } },
|
|
286
|
-
h('button', { type: 'button', disabled: Boolean(workingId)
|
|
364
|
+
skill.local_status === 'installed' ? null : h('button', { type: 'button', disabled: Boolean(workingId),
|
|
287
365
|
onClick: () => void changeInstall(skill), style: primaryButtonStyle },
|
|
288
|
-
skill.local_status === '
|
|
366
|
+
skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
|
|
289
367
|
skill.local_status !== 'not-installed' ? h('button', { type: 'button', disabled: Boolean(workingId),
|
|
290
368
|
onClick: () => void changeInstall(skill, true), style: secondaryButtonStyle }, '卸载') : null))
|
|
291
369
|
})) : !loading && skills.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null))
|
|
@@ -294,7 +372,11 @@ window.__ModuleLoader__.load({
|
|
|
294
372
|
function PluginCenterPage({ remote, onBack, onSkills, openBundle }) {
|
|
295
373
|
const [plugins, setPlugins] = useState([])
|
|
296
374
|
const [bundles, setBundles] = useState([])
|
|
297
|
-
const [loading, setLoading] = useState(
|
|
375
|
+
const [loading, setLoading] = useState(true)
|
|
376
|
+
const [loadError, setLoadError] = useState(false)
|
|
377
|
+
const [workingId, setWorkingId] = useState('')
|
|
378
|
+
const [uncertainId, setUncertainId] = useState('')
|
|
379
|
+
const [completedId, setCompletedId] = useState('')
|
|
298
380
|
const [message, setMessage] = useState('')
|
|
299
381
|
const [query, setQuery] = useState('')
|
|
300
382
|
const [filter, setFilter] = useState('all')
|
|
@@ -312,53 +394,150 @@ window.__ModuleLoader__.load({
|
|
|
312
394
|
if (!listed.ok) throw new Error(listed.error?.message || '无法读取 DSH 本机插件。')
|
|
313
395
|
setPlugins(result.plugins)
|
|
314
396
|
setBundles(listed.value)
|
|
397
|
+
setLoadError(false)
|
|
315
398
|
setMessage(result.plugins.length ? '' : '团队暂未发布精选插件。')
|
|
316
399
|
} catch (error) {
|
|
317
400
|
setPlugins([])
|
|
318
401
|
setBundles([])
|
|
402
|
+
setLoadError(true)
|
|
319
403
|
setMessage(error instanceof Error ? error.message : '无法读取企业精选插件。')
|
|
320
404
|
} finally { setLoading(false) }
|
|
321
405
|
}
|
|
322
406
|
useEffect(() => { void refresh() }, [])
|
|
407
|
+
const install = async (plugin) => {
|
|
408
|
+
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
409
|
+
if (!artifactUrl || workingId || uncertainId === plugin.id || completedId === plugin.id) return
|
|
410
|
+
const manager = remote?.pluginManager
|
|
411
|
+
if (typeof manager?.installBundle !== 'function') {
|
|
412
|
+
setMessage('当前 DSH 未提供插件安装接口,请联系管理员。')
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
416
|
+
const action = local ? local.version === plugin.version ? '重新安装' : '更新' : '安装'
|
|
417
|
+
if (!window.confirm(`在这台 DSH ${action}团队审核的 ${plugin.title} ${plugin.version}?插件将在本机运行,完成后需要完全重启 DSH。`)) return
|
|
418
|
+
setWorkingId(plugin.id)
|
|
419
|
+
setMessage(`正在安装 ${plugin.title},请不要退出 DSH…`)
|
|
420
|
+
try {
|
|
421
|
+
const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
|
|
422
|
+
const catalog = await readLocalJsonResponse(response)
|
|
423
|
+
const latest = Array.isArray(catalog?.plugins)
|
|
424
|
+
? catalog.plugins.find((item) => item.id === plugin.id) : null
|
|
425
|
+
if (!response.ok || latest?.package_name !== plugin.package_name
|
|
426
|
+
|| latest?.version !== plugin.version || latest?.artifact_sha256 !== plugin.artifact_sha256
|
|
427
|
+
|| approvedPluginArtifactUrl(latest) !== artifactUrl) {
|
|
428
|
+
throw new Error('插件审核信息已变化,请刷新目录后重试。')
|
|
429
|
+
}
|
|
430
|
+
const requestId = crypto.randomUUID()
|
|
431
|
+
let result
|
|
432
|
+
try { result = await manager.installBundle(artifactUrl, { enabled: true, requestId }) }
|
|
433
|
+
catch { result = null }
|
|
434
|
+
if (!result?.ok) {
|
|
435
|
+
let recovered = null
|
|
436
|
+
if (typeof manager.waitForInstall === 'function') {
|
|
437
|
+
try {
|
|
438
|
+
const pending = await manager.waitForInstall(requestId)
|
|
439
|
+
if (pending.ok && pending.value) recovered = { ok: true, value: pending.value }
|
|
440
|
+
} catch {}
|
|
441
|
+
}
|
|
442
|
+
if (!recovered) {
|
|
443
|
+
setUncertainId(plugin.id)
|
|
444
|
+
setMessage(`暂时无法确认 ${plugin.title} 的安装结果。请先在 DSH 插件列表核对,勿重复点击安装。`)
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
result = recovered
|
|
448
|
+
}
|
|
449
|
+
const outcome = result.value
|
|
450
|
+
if (!outcome || typeof outcome !== 'object') {
|
|
451
|
+
setUncertainId(plugin.id)
|
|
452
|
+
setMessage(`DSH 未返回 ${plugin.title} 的安装结果。请先在插件列表核对,勿重复点击安装。`)
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
if (outcome.application === 'failed' || outcome.application === 'cancelled') {
|
|
456
|
+
if (outcome.error?.code === 'ambiguous-install' && local?.version === plugin.version) {
|
|
457
|
+
setMessage(`DSH 无法确定 ${plugin.title} 本次安装的唯一结果,未确认完成。请先在 DSH 插件列表核对;若已经正常使用同版则无需重复安装,仍需核对来源时请联系管理员。`)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
const diagnostic = typeof outcome.error?.diagnostic === 'string'
|
|
461
|
+
? outcome.error.diagnostic.slice(0, 180) : ''
|
|
462
|
+
setMessage(`${plugin.title} 安装未完成。请在 DSH 插件管理查看状态或联系管理员。${diagnostic ? `原因:${diagnostic}` : ''}`)
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
if (outcome.bundle !== plugin.package_name
|
|
466
|
+
|| !['restart-required', 'applied'].includes(outcome.application)) {
|
|
467
|
+
setUncertainId(plugin.id)
|
|
468
|
+
setMessage(`DSH 未确认 ${plugin.title} 已安装。请先在插件列表核对,勿重复点击安装。`)
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
let observed = null
|
|
472
|
+
try {
|
|
473
|
+
const installed = await manager.listBundles()
|
|
474
|
+
if (installed.ok && Array.isArray(installed.value)) {
|
|
475
|
+
observed = installed.value.find((bundle) => bundle.name === plugin.package_name && bundle.installed) || null
|
|
476
|
+
}
|
|
477
|
+
} catch {}
|
|
478
|
+
if ((outcome.version && outcome.version !== plugin.version) || observed?.version !== plugin.version) {
|
|
479
|
+
setUncertainId(plugin.id)
|
|
480
|
+
setMessage(`DSH 未确认 ${plugin.title} 的审核版本已安装。请先在插件列表核对,勿重复点击安装。`)
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
await refresh()
|
|
484
|
+
try { localStorage.setItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`, plugin.artifact_sha256) } catch {}
|
|
485
|
+
setCompletedId(plugin.id)
|
|
486
|
+
setMessage(`${plugin.title} 已安装。请完全退出并重新启动 DSH 后使用。`)
|
|
487
|
+
} catch (error) {
|
|
488
|
+
setMessage(error instanceof Error ? error.message : '插件安装未完成,请稍后重试。')
|
|
489
|
+
} finally { setWorkingId('') }
|
|
490
|
+
}
|
|
323
491
|
const visible = plugins.filter((plugin) => {
|
|
324
492
|
const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
493
|
+
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
325
494
|
const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
|
|
326
|
-
return matches && (filter === 'all' || filter === 'installed' && installed
|
|
327
|
-
|| filter === 'available' &&
|
|
495
|
+
return matches && (filter === 'all' || filter === 'installed' && Boolean(installed)
|
|
496
|
+
|| filter === 'available' && artifactUrl && (!installed || (installed.version && installed.version !== plugin.version))
|
|
328
497
|
|| filter === 'retired' && plugin.status !== 'published')
|
|
329
498
|
})
|
|
330
499
|
return h('section', { 'aria-label': '企业精选插件', style: {
|
|
331
500
|
height: '100%', overflowY: 'auto', boxSizing: 'border-box',
|
|
332
501
|
padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
|
|
333
502
|
} }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
|
|
334
|
-
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
335
|
-
h('
|
|
336
|
-
|
|
503
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
|
|
504
|
+
h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
|
|
505
|
+
h('button', { type: 'button', onClick: onSkills, style: categoryButtonStyle }, '技能'),
|
|
506
|
+
h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '插件')),
|
|
507
|
+
h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
|
|
337
508
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 } },
|
|
338
509
|
h('div', null,
|
|
339
510
|
h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选插件'),
|
|
340
511
|
h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。')),
|
|
341
512
|
h('button', { type: 'button', disabled: loading, onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
|
|
342
|
-
h('
|
|
343
|
-
h('
|
|
344
|
-
h('p', { style: mutedTextStyle }, '
|
|
345
|
-
h(
|
|
346
|
-
h(
|
|
513
|
+
!loading && !plugins.length && !loadError ? h('section', { role: 'status', style: panelStyle },
|
|
514
|
+
h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
|
|
515
|
+
h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
|
|
516
|
+
!loading && loadError ? h('p', { role: 'alert', style: mutedTextStyle }, message) : null,
|
|
517
|
+
plugins.length ? h(ExtensionRequestNotice) : null,
|
|
518
|
+
plugins.length ? h('div', { style: panelStyle },
|
|
347
519
|
h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
|
|
348
520
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
349
|
-
...[['all', '全部'], ['available', '
|
|
521
|
+
...[['all', '全部'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
|
|
350
522
|
h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
|
|
351
|
-
style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))),
|
|
352
|
-
message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
523
|
+
style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))) : null,
|
|
524
|
+
plugins.length && message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
|
|
353
525
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
354
526
|
...visible.map((plugin) => {
|
|
355
527
|
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
356
528
|
const sameVersion = local?.version === plugin.version
|
|
357
529
|
const security = plugin.status === 'security_withdrawn'
|
|
530
|
+
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
531
|
+
const sourceKnown = typeof local?.source === 'string'
|
|
532
|
+
const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
|
|
533
|
+
const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
|
|
358
534
|
const status = security ? '已废弃(安全风险)'
|
|
359
|
-
: plugin.status === 'deprecated' ? '已废弃' : sameVersion
|
|
360
|
-
?
|
|
361
|
-
|
|
535
|
+
: plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' : sameVersion
|
|
536
|
+
? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
|
|
537
|
+
: sourceKnown ? '本机同版,来源不同' : recordedInstall
|
|
538
|
+
? '本机同版,曾由企业扩展安装;当前 DSH 无法复核来源' : '本机同版,当前 DSH 无法核对来源'
|
|
539
|
+
: artifactUrl ? local ? local.version ? `可更新 · 本机 ${local.version}` : '本机版本暂无法读取' : '可安装'
|
|
540
|
+
: local ? `本机版本 ${local.version || '暂无法读取'}` : '已审核,暂需管理员安装'
|
|
362
541
|
return h('article', { key: plugin.id, style: security
|
|
363
542
|
? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
|
|
364
543
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
|
|
@@ -374,16 +553,27 @@ window.__ModuleLoader__.load({
|
|
|
374
553
|
? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)' } : mutedTextStyle },
|
|
375
554
|
plugin.withdrawal_reason || '团队已停止推荐此版本。') : null,
|
|
376
555
|
local?.error ? h('p', { role: 'status', style: mutedTextStyle }, local.error.message || String(local.error)) : null,
|
|
556
|
+
sameVersion && !sourceKnown && artifactUrl ? h('p', { style: mutedTextStyle }, recordedInstall
|
|
557
|
+
? '此前曾由企业扩展安装;当前 DSH 无法再次核对来源。正常使用无需重复安装;若后来换过来源,可以重新安装。'
|
|
558
|
+
: '已装同版且使用正常时无需重复安装;若想改用团队审核包,可自愿安装。') : null,
|
|
377
559
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
378
|
-
|
|
560
|
+
artifactUrl && (!local || (local.version && !verifiedLocal))
|
|
561
|
+
? h('button', { type: 'button', disabled: Boolean(workingId) || uncertainId === plugin.id || completedId === plugin.id,
|
|
562
|
+
onClick: () => void install(plugin), style: primaryButtonStyle },
|
|
563
|
+
workingId === plugin.id ? '安装中…' : uncertainId === plugin.id ? '先核对安装结果'
|
|
564
|
+
: completedId === plugin.id ? '重启 DSH 后生效'
|
|
565
|
+
: local ? sameVersion ? '按团队审核包重新安装' : '更新到审核版本' : '安装到这台 DSH') : null,
|
|
566
|
+
plugin.status === 'published' && !local && !artifactUrl
|
|
379
567
|
? h('p', { style: mutedTextStyle }, '如需安装,请联系企业管理员安排。') : null,
|
|
380
|
-
local ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
|
|
381
|
-
local && !sameVersion && plugin.status === 'published'
|
|
568
|
+
local || uncertainId === plugin.id ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
|
|
569
|
+
local && !sameVersion && plugin.status === 'published' && !artifactUrl
|
|
382
570
|
? h('p', { style: mutedTextStyle }, '本机版本与团队审核版本不同;请联系企业管理员核对。') : null))
|
|
383
|
-
})) : !loading && plugins.length ? h('p', { style: mutedTextStyle }, '
|
|
571
|
+
})) : !loading && plugins.length ? h('p', { style: mutedTextStyle }, filter === 'available'
|
|
572
|
+
? '当前没有可一键安装的插件。可在“全部”查看审核目录,或联系企业管理员。'
|
|
573
|
+
: '没有符合筛选条件的插件。') : null))
|
|
384
574
|
}
|
|
385
575
|
|
|
386
|
-
function ModelSyncFooter({ remote, serviceUrl, onServiceUrlChange }) {
|
|
576
|
+
function ModelSyncFooter({ remote, serviceUrl, bindingReady, onServiceUrlChange, onConnectionChange }) {
|
|
387
577
|
const [email, setEmail] = useState('')
|
|
388
578
|
const [code, setCode] = useState('')
|
|
389
579
|
const [message, setMessage] = useState('')
|
|
@@ -396,6 +586,7 @@ window.__ModuleLoader__.load({
|
|
|
396
586
|
const [bundleRelease, setBundleRelease] = useState(null)
|
|
397
587
|
const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
|
|
398
588
|
const [serviceCompatibility, setServiceCompatibility] = useState({ state: 'idle', message: '' })
|
|
589
|
+
const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
|
|
399
590
|
|
|
400
591
|
const connectionErrorMessage = (result, status) => {
|
|
401
592
|
const message = result?.message || `请求失败(${status})`
|
|
@@ -407,13 +598,13 @@ window.__ModuleLoader__.load({
|
|
|
407
598
|
: `${message} 本机无法确认旧设备编号;请让原团队管理员按成员查看设备清单,勿用新编号猜测。`
|
|
408
599
|
}
|
|
409
600
|
|
|
410
|
-
const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false) => {
|
|
601
|
+
const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false, isCurrent = () => true) => {
|
|
411
602
|
const normalized = normalizeServiceUrl(rawServiceUrl)
|
|
412
603
|
if (!normalized && !useSavedConnection) {
|
|
413
|
-
setServiceCompatibility({ state: 'idle', message: '' })
|
|
604
|
+
if (isCurrent()) setServiceCompatibility({ state: 'idle', message: '' })
|
|
414
605
|
return null
|
|
415
606
|
}
|
|
416
|
-
setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
|
|
607
|
+
if (isCurrent()) setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
|
|
417
608
|
try {
|
|
418
609
|
const manager = remote?.pluginManager
|
|
419
610
|
if (typeof manager?.listBundles !== 'function') throw new Error('当前 DSH 未提供 Bundle 版本读取接口。')
|
|
@@ -444,14 +635,14 @@ window.__ModuleLoader__.load({
|
|
|
444
635
|
: `已识别 ANT Bundle v${result.bundle_version} 和服务端 v${result.server_version};服务端未提供协议能力,兼容性未知,仍允许继续。`
|
|
445
636
|
const state = compatibility === true ? 'compatible' : compatibility === false ? 'incompatible' : 'unknown'
|
|
446
637
|
const status = { state, message, ...result }
|
|
447
|
-
setServiceCompatibility(status)
|
|
638
|
+
if (isCurrent()) setServiceCompatibility(status)
|
|
448
639
|
return status
|
|
449
640
|
} catch (error) {
|
|
450
641
|
const status = {
|
|
451
642
|
state: 'unknown',
|
|
452
643
|
message: `无法确认 ANT 版本兼容性,仍允许继续:${error instanceof Error ? error.message : '连接检查失败。'}`,
|
|
453
644
|
}
|
|
454
|
-
setServiceCompatibility(status)
|
|
645
|
+
if (isCurrent()) setServiceCompatibility(status)
|
|
455
646
|
return status
|
|
456
647
|
}
|
|
457
648
|
}
|
|
@@ -496,6 +687,10 @@ window.__ModuleLoader__.load({
|
|
|
496
687
|
}
|
|
497
688
|
|
|
498
689
|
const refreshModels = async () => {
|
|
690
|
+
if (!bindingReady) {
|
|
691
|
+
setMessage('请先等待团队绑定检查完成,再刷新团队模型。')
|
|
692
|
+
return
|
|
693
|
+
}
|
|
499
694
|
setBusy(true)
|
|
500
695
|
setMessage('正在从 ANT 刷新获准模型…')
|
|
501
696
|
try {
|
|
@@ -563,16 +758,105 @@ window.__ModuleLoader__.load({
|
|
|
563
758
|
throw new Error(`当前 DSH 未找到已安装的 ${release.package_name} Bundle。`)
|
|
564
759
|
}
|
|
565
760
|
const available = newerVersion(release.version, installed.version)
|
|
761
|
+
if (release.artifact_status === 'revoked') {
|
|
762
|
+
setBundleUpdate({ state: 'revoked', currentVersion: installed.version,
|
|
763
|
+
message: installed.version === release.version
|
|
764
|
+
? `当前 ANT ${installed.version} 的安装包已因安全问题停用。请联系企业管理员获取处理方式;ANT 不会自动删除本机副本。`
|
|
765
|
+
: `发布版本 ${release.version} 的安装包已因安全问题停用,请等待管理员发布修复版本。` })
|
|
766
|
+
return
|
|
767
|
+
}
|
|
768
|
+
const artifactUrl = approvedArtifactUrl(release)
|
|
566
769
|
setBundleUpdate({
|
|
567
|
-
state: available ? 'available' : 'current',
|
|
770
|
+
state: available ? artifactUrl ? 'available' : 'artifact-missing' : 'current',
|
|
568
771
|
currentVersion: installed.version,
|
|
569
|
-
message: available
|
|
772
|
+
message: available
|
|
773
|
+
? artifactUrl
|
|
774
|
+
? `发现新版本 ${release.version},可直接更新。`
|
|
775
|
+
: `发现新版本 ${release.version},但管理员尚未提供已审核安装包。请联系管理员。`
|
|
776
|
+
: `当前已是最新发布版本(${installed.version})。`,
|
|
570
777
|
})
|
|
571
778
|
} catch (error) {
|
|
572
779
|
setBundleUpdate({ state: 'failed', currentVersion: '', message: error instanceof Error ? error.message : '检查 ANT 更新失败。' })
|
|
573
780
|
}
|
|
574
781
|
}
|
|
575
782
|
|
|
783
|
+
const installBundleUpdate = async () => {
|
|
784
|
+
if (bundleUpdate.state !== 'available') return
|
|
785
|
+
const artifactUrl = approvedArtifactUrl(bundleRelease)
|
|
786
|
+
if (!artifactUrl) return
|
|
787
|
+
const manager = remote?.pluginManager
|
|
788
|
+
if (typeof manager?.installBundle !== 'function') {
|
|
789
|
+
setBundleUpdate((current) => ({ ...current, state: 'failed', message: '当前 DSH 未提供 Bundle 更新接口。' }))
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
setBusy(true)
|
|
793
|
+
setBundleUpdate((current) => ({ ...current, state: 'installing', message: '正在由 DSH 安装已审核的 ANT 更新…' }))
|
|
794
|
+
try {
|
|
795
|
+
const releaseResponse = await fetch('/api/ant-teams/bundle-release', {
|
|
796
|
+
cache: 'no-store', headers: {
|
|
797
|
+
'x-ant-bundle-capabilities': 'direct-package-install',
|
|
798
|
+
'x-ant-bundle-update-mode': 'check-only',
|
|
799
|
+
},
|
|
800
|
+
})
|
|
801
|
+
const latest = await readLocalJsonResponse(releaseResponse)
|
|
802
|
+
if (!releaseResponse.ok || latest.version !== bundleRelease.version
|
|
803
|
+
|| latest.artifact_sha256 !== bundleRelease.artifact_sha256
|
|
804
|
+
|| approvedArtifactUrl(latest) !== artifactUrl) {
|
|
805
|
+
throw new Error('ANT 更新信息已变化,请重新检查更新。')
|
|
806
|
+
}
|
|
807
|
+
const requestId = crypto.randomUUID()
|
|
808
|
+
let result
|
|
809
|
+
try {
|
|
810
|
+
result = await manager.installBundle(artifactUrl, { enabled: true, requestId })
|
|
811
|
+
} catch {
|
|
812
|
+
result = null
|
|
813
|
+
}
|
|
814
|
+
if (!result?.ok) {
|
|
815
|
+
let recovered = null
|
|
816
|
+
if (typeof manager.waitForInstall === 'function') {
|
|
817
|
+
try {
|
|
818
|
+
const pending = await manager.waitForInstall(requestId)
|
|
819
|
+
if (pending.ok && pending.value) recovered = { ok: true, value: pending.value }
|
|
820
|
+
} catch {}
|
|
821
|
+
}
|
|
822
|
+
if (!recovered) {
|
|
823
|
+
setBundleUpdate((current) => ({ ...current, state: 'unknown',
|
|
824
|
+
message: 'DSH 连接中断,更新结果暂时无法确认。请先在 DSH 插件列表查看 ANT 版本,勿重复点击更新。' }))
|
|
825
|
+
return
|
|
826
|
+
}
|
|
827
|
+
result = recovered
|
|
828
|
+
}
|
|
829
|
+
const outcome = result.value
|
|
830
|
+
if (outcome.application === 'failed' || outcome.application === 'cancelled') {
|
|
831
|
+
const reason = outcome.error?.diagnostic || outcome.packageResult?.output || '安装未完成。'
|
|
832
|
+
setBundleUpdate((current) => ({ ...current, state: 'failed', message: `更新失败:${reason}` }))
|
|
833
|
+
return
|
|
834
|
+
}
|
|
835
|
+
if (outcome.bundle !== ANT_BUNDLE_PACKAGE_NAME
|
|
836
|
+
|| !['restart-required', 'applied'].includes(outcome.application)) {
|
|
837
|
+
throw new Error('DSH 未确认 ANT 安装成功,请联系管理员检查插件状态。')
|
|
838
|
+
}
|
|
839
|
+
let observed = null
|
|
840
|
+
try {
|
|
841
|
+
const installed = await manager.listBundles()
|
|
842
|
+
if (installed.ok && Array.isArray(installed.value)) {
|
|
843
|
+
observed = installed.value.find((bundle) => bundle.name === ANT_BUNDLE_PACKAGE_NAME && bundle.installed) || null
|
|
844
|
+
}
|
|
845
|
+
} catch {}
|
|
846
|
+
if ((outcome.version && outcome.version !== bundleRelease.version) || observed?.version !== bundleRelease.version) {
|
|
847
|
+
setBundleUpdate((current) => ({ ...current, state: 'unknown',
|
|
848
|
+
message: 'DSH 未确认新版本已安装。请先在 DSH 插件列表核对 ANT 版本,勿重复点击更新。' }))
|
|
849
|
+
return
|
|
850
|
+
}
|
|
851
|
+
setBundleUpdate({ state: 'done', currentVersion: bundleRelease.version,
|
|
852
|
+
message: `ANT ${bundleRelease.version} 已安装。请完全退出并重新启动 DSH 后生效。` })
|
|
853
|
+
} catch (error) {
|
|
854
|
+
setBundleUpdate((current) => ({ ...current, state: 'failed', message: error instanceof Error ? error.message : '更新未完成。' }))
|
|
855
|
+
} finally {
|
|
856
|
+
setBusy(false)
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
576
860
|
const clearLocalConnection = async () => {
|
|
577
861
|
if (typeof remote?.settings?.mutate !== 'function') throw new Error('DSH 未提供模型设置接口。')
|
|
578
862
|
const settingsResult = await remote.settings.mutate('llm-pi-ai', [
|
|
@@ -584,6 +868,7 @@ window.__ModuleLoader__.load({
|
|
|
584
868
|
const credentialResult = await remote.credentials.unset('ANT_MODEL_API_KEY')
|
|
585
869
|
if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
|
|
586
870
|
setCredentialConfigured(false)
|
|
871
|
+
onConnectionChange?.(null)
|
|
587
872
|
setNeedsReconnect(false)
|
|
588
873
|
setReauthenticate(false)
|
|
589
874
|
setAllowLocalCleanup(false)
|
|
@@ -635,20 +920,47 @@ window.__ModuleLoader__.load({
|
|
|
635
920
|
if (!current) return
|
|
636
921
|
const configured = result.ok ? Boolean(result.value?.ANT_MODEL_API_KEY?.configured) : null
|
|
637
922
|
setCredentialConfigured(configured)
|
|
638
|
-
if (configured) {
|
|
923
|
+
if (configured && bindingReady) {
|
|
639
924
|
void refreshModels()
|
|
640
925
|
void checkBundleUpdate()
|
|
641
926
|
}
|
|
642
927
|
}).catch(() => { if (current) setCredentialConfigured(null) })
|
|
643
928
|
return () => { current = false }
|
|
644
|
-
}, [remote])
|
|
929
|
+
}, [remote, bindingReady])
|
|
645
930
|
|
|
646
931
|
useEffect(() => {
|
|
647
|
-
|
|
648
|
-
|
|
932
|
+
let current = true
|
|
933
|
+
if (credentialConfigured !== true) {
|
|
934
|
+
setPinnedServiceUrl(null)
|
|
935
|
+
return () => { current = false }
|
|
936
|
+
}
|
|
937
|
+
const readPinnedOrigin = async () => {
|
|
938
|
+
try {
|
|
939
|
+
const response = await fetch('/api/ant-teams/connection-origin', { cache: 'no-store' })
|
|
940
|
+
const result = await readLocalJsonResponse(response)
|
|
941
|
+
if (current) setPinnedServiceUrl(response.ok ? normalizeServiceUrl(result.service_url) : null)
|
|
942
|
+
} catch { if (current) setPinnedServiceUrl(null) }
|
|
943
|
+
}
|
|
944
|
+
void readPinnedOrigin()
|
|
945
|
+
return () => { current = false }
|
|
946
|
+
}, [remote, credentialConfigured])
|
|
947
|
+
|
|
948
|
+
useEffect(() => {
|
|
949
|
+
if (!bindingReady) {
|
|
950
|
+
setServiceCompatibility({ state: 'idle', message: '' })
|
|
951
|
+
return
|
|
952
|
+
}
|
|
953
|
+
let current = true
|
|
954
|
+
void inspectServiceCompatibility(serviceUrl, credentialConfigured === true, () => current)
|
|
955
|
+
return () => { current = false }
|
|
956
|
+
}, [remote, serviceUrl, credentialConfigured, bindingReady])
|
|
649
957
|
|
|
650
958
|
const requestCode = async (event) => {
|
|
651
959
|
event.preventDefault()
|
|
960
|
+
if (!bindingReady) {
|
|
961
|
+
setMessage('请先等待团队绑定检查完成,再验证工作邮箱。')
|
|
962
|
+
return
|
|
963
|
+
}
|
|
652
964
|
const normalized = normalizeServiceUrl(serviceUrl)
|
|
653
965
|
const normalizedEmail = email.trim()
|
|
654
966
|
if (!normalized) {
|
|
@@ -690,6 +1002,10 @@ window.__ModuleLoader__.load({
|
|
|
690
1002
|
|
|
691
1003
|
const connect = async (event) => {
|
|
692
1004
|
event.preventDefault()
|
|
1005
|
+
if (!bindingReady) {
|
|
1006
|
+
setMessage('请先等待团队绑定检查完成,再连接团队。')
|
|
1007
|
+
return
|
|
1008
|
+
}
|
|
693
1009
|
const normalized = normalizeServiceUrl(serviceUrl)
|
|
694
1010
|
const normalizedEmail = email.trim()
|
|
695
1011
|
const normalizedCode = code.trim()
|
|
@@ -706,6 +1022,7 @@ window.__ModuleLoader__.load({
|
|
|
706
1022
|
}
|
|
707
1023
|
setBusy(true)
|
|
708
1024
|
setMessage('正在验证邮箱并同步团队模型…')
|
|
1025
|
+
let credentialSaved = false
|
|
709
1026
|
try {
|
|
710
1027
|
await requireCompatibleService(normalized)
|
|
711
1028
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
@@ -729,23 +1046,36 @@ window.__ModuleLoader__.load({
|
|
|
729
1046
|
}
|
|
730
1047
|
const credentialWrite = await remote.credentials.set('ANT_MODEL_API_KEY', result.token)
|
|
731
1048
|
if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
|
|
1049
|
+
credentialSaved = true
|
|
732
1050
|
await applyAuthorizedModels(result.models, normalized)
|
|
733
1051
|
setCode('')
|
|
734
1052
|
onServiceUrlChange(normalized)
|
|
735
1053
|
setCodeRequested(false)
|
|
736
1054
|
setCredentialConfigured(true)
|
|
1055
|
+
onConnectionChange?.(normalized)
|
|
737
1056
|
setAllowLocalCleanup(false)
|
|
738
1057
|
setNeedsReconnect(false)
|
|
739
1058
|
setReauthenticate(false)
|
|
740
1059
|
setMessage(`已连接 ${result.email || normalizedEmail},同步 ${result.models.length} 个获准模型。它们现已出现在 DSH 模型选择器中。`)
|
|
741
1060
|
} catch (error) {
|
|
742
|
-
|
|
1061
|
+
if (credentialSaved) {
|
|
1062
|
+
setCredentialConfigured(true)
|
|
1063
|
+
setCode('')
|
|
1064
|
+
setCodeRequested(false)
|
|
1065
|
+
onConnectionChange?.(normalized)
|
|
1066
|
+
setMessage(`ANT 凭证已保存,但团队模型未能同步:${error instanceof Error ? error.message : '请稍后重试。'} 请点击“刷新团队模型”重试。`)
|
|
1067
|
+
} else {
|
|
1068
|
+
setMessage(error instanceof Error ? error.message : '连接失败,请稍后重试。')
|
|
1069
|
+
}
|
|
743
1070
|
} finally {
|
|
744
1071
|
setBusy(false)
|
|
745
1072
|
}
|
|
746
1073
|
}
|
|
747
1074
|
|
|
748
1075
|
const showConnectForm = credentialConfigured !== true || reauthenticate || needsReconnect
|
|
1076
|
+
const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
|
|
1077
|
+
const updateGuideUrl = pinnedServiceUrl
|
|
1078
|
+
? new URL('/ant-update', pinnedServiceUrl).href : null
|
|
749
1079
|
return h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 400px), 1fr))', alignItems: 'start', gap: 16 } },
|
|
750
1080
|
h('section', { style: panelStyle },
|
|
751
1081
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
|
|
@@ -754,11 +1084,13 @@ window.__ModuleLoader__.load({
|
|
|
754
1084
|
h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '使用上方团队服务地址验证工作邮箱并同步授权模型。连接状态变化时可随时刷新。')),
|
|
755
1085
|
h('span', { style: {
|
|
756
1086
|
flexShrink: 0, padding: '4px 9px', borderRadius: 999,
|
|
757
|
-
color: credentialConfigured === true ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
758
|
-
backgroundColor: credentialConfigured === true ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
|
|
1087
|
+
color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
1088
|
+
backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
|
|
759
1089
|
fontSize: 12,
|
|
760
|
-
} }, credentialConfigured === true ? '已连接' : credentialConfigured === false ? '未连接' : '检查中')),
|
|
761
|
-
h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true
|
|
1090
|
+
} }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
|
|
1091
|
+
h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true && !bindingReady
|
|
1092
|
+
? '本机有 ANT 凭证,但尚未确认它绑定的团队;请先完成上方的绑定检查。'
|
|
1093
|
+
: credentialConfigured === true
|
|
762
1094
|
? 'DSH 已安全保存此设备的 ANT 团队凭证。'
|
|
763
1095
|
: credentialConfigured === false ? '验证工作邮箱后即可连接团队模型。' : '正在检查 ANT 连接状态…'),
|
|
764
1096
|
serviceCompatibility.state !== 'idle' ? h('p', {
|
|
@@ -769,25 +1101,30 @@ window.__ModuleLoader__.load({
|
|
|
769
1101
|
},
|
|
770
1102
|
}, serviceCompatibility.message) : null,
|
|
771
1103
|
credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
772
|
-
h('button', { type: 'button', disabled: busy, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
773
|
-
h('button', { type: 'button', disabled: busy, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
1104
|
+
h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
|
|
1105
|
+
h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
|
|
774
1106
|
h('button', { type: 'button', disabled: busy, onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() }, style: secondaryButtonStyle }, '断开当前设备')) : null,
|
|
775
|
-
|
|
1107
|
+
h('section', { style: { display: 'grid', gap: 8, paddingTop: 12, borderTop: '0.5px solid var(--dsw-alias-border-l2)' } },
|
|
776
1108
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },
|
|
777
1109
|
h('h4', { style: { margin: 0, fontSize: 14, fontWeight: 650 } }, 'ANT 插件更新'),
|
|
778
|
-
h('button', { type: 'button', disabled: busy || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
|
|
779
|
-
|
|
780
|
-
|
|
1110
|
+
h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
|
|
1111
|
+
!canCheckUpdate ? h('p', { role: 'status', style: mutedTextStyle }, '连接团队后可在这里检查并安装 ANT 更新;需要先恢复插件时,可打开团队管理台的更新指引。') : null,
|
|
1112
|
+
updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, `打开 ${pinnedServiceUrl} 的更新指引 ↗`)
|
|
1113
|
+
: !canCheckUpdate ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
|
|
1114
|
+
bundleUpdate.message ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
|
|
1115
|
+
? { ...mutedTextStyle, color: 'var(--dsw-alias-state-error-primary)' } : mutedTextStyle }, bundleUpdate.message) : null,
|
|
1116
|
+
canCheckUpdate && bundleRelease?.version && ['available', 'artifact-missing'].includes(bundleUpdate.state) ? h('div', { style: { display: 'grid', gap: 8 } },
|
|
781
1117
|
h('p', { style: mutedTextStyle }, `发布版本:${bundleRelease.version} · 精确包名:${directInstallSpec(bundleRelease)}`),
|
|
782
1118
|
bundleRelease.release_notes ? h('p', { style: mutedTextStyle }, bundleRelease.release_notes) : null,
|
|
783
|
-
|
|
1119
|
+
bundleUpdate.state === 'available'
|
|
1120
|
+
? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null) : null),
|
|
784
1121
|
showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
|
|
785
1122
|
h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
|
|
786
1123
|
h('input', {
|
|
787
1124
|
id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
|
|
788
1125
|
onChange: (event) => setEmail(event.target.value), style: inputStyle,
|
|
789
1126
|
}),
|
|
790
|
-
h('button', { type: 'submit', disabled: busy, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
|
|
1127
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
|
|
791
1128
|
credentialConfigured === true && showConnectForm ? h('button', {
|
|
792
1129
|
type: 'button', disabled: busy,
|
|
793
1130
|
onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
|
|
@@ -800,7 +1137,7 @@ window.__ModuleLoader__.load({
|
|
|
800
1137
|
required: true, maxLength: 8, value: code, placeholder: '8 位验证码',
|
|
801
1138
|
onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle,
|
|
802
1139
|
}),
|
|
803
|
-
h('button', { type: 'submit', disabled: busy || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
1140
|
+
h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
|
|
804
1141
|
allowLocalCleanup ? h('section', { style: { ...panelStyle, padding: 14, gap: 9 } },
|
|
805
1142
|
h('p', { style: mutedTextStyle }, savedDeviceId()
|
|
806
1143
|
? `已保存的设备编号:${savedDeviceId()}。请交给原团队管理员核对。`
|
|
@@ -822,6 +1159,20 @@ window.__ModuleLoader__.load({
|
|
|
822
1159
|
|
|
823
1160
|
}
|
|
824
1161
|
|
|
1162
|
+
function EnterpriseExtensionsPanelIcon({ size }) {
|
|
1163
|
+
return h('span', { 'aria-hidden': true, style: {
|
|
1164
|
+
display: 'inline-grid', placeItems: 'center', width: size, height: size,
|
|
1165
|
+
fontSize: size, lineHeight: 1,
|
|
1166
|
+
} }, '✦')
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function EnterpriseExtensionsPage({ remote, onBack, openBundle }) {
|
|
1170
|
+
const [tab, setTab] = useState('skills')
|
|
1171
|
+
return tab === 'skills'
|
|
1172
|
+
? h(SkillCenterPage, { onBack, onPlugins: () => setTab('plugins') })
|
|
1173
|
+
: h(PluginCenterPage, { remote, onBack, onSkills: () => setTab('skills'), openBundle })
|
|
1174
|
+
}
|
|
1175
|
+
|
|
825
1176
|
function apply(ctx) {
|
|
826
1177
|
const remote = {
|
|
827
1178
|
credentials: ctx.remote.credentials,
|
|
@@ -829,17 +1180,14 @@ window.__ModuleLoader__.load({
|
|
|
829
1180
|
pluginManager: ctx.remote.pluginManager,
|
|
830
1181
|
}
|
|
831
1182
|
ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: 'ant-skills' },
|
|
832
|
-
() => h(
|
|
833
|
-
onBack: () => ctx.pluginNavigation.openBundle(ANT_BUNDLE_PACKAGE_NAME),
|
|
834
|
-
onPlugins: () => ctx.layout.selectPanel('ant-plugins'),
|
|
835
|
-
})))
|
|
836
|
-
ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: 'ant-plugins' },
|
|
837
|
-
() => h(PluginCenterPage, {
|
|
1183
|
+
() => h(EnterpriseExtensionsPage, {
|
|
838
1184
|
remote,
|
|
839
1185
|
onBack: () => ctx.pluginNavigation.openBundle(ANT_BUNDLE_PACKAGE_NAME),
|
|
840
|
-
onSkills: () => ctx.layout.selectPanel('ant-skills'),
|
|
841
1186
|
openBundle: (name) => ctx.pluginNavigation.openBundle(name),
|
|
842
1187
|
})))
|
|
1188
|
+
ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
|
|
1189
|
+
name: 'sidebar.panellist', id: 'ant-skills', order: 5, label: '企业扩展',
|
|
1190
|
+
}, EnterpriseExtensionsPanelIcon))
|
|
843
1191
|
ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
|
|
844
1192
|
name: 'plugins.bundle.config',
|
|
845
1193
|
key: ANT_BUNDLE_PACKAGE_NAME,
|
|
@@ -848,8 +1196,7 @@ window.__ModuleLoader__.load({
|
|
|
848
1196
|
? h('p', null, 'ANT 团队服务入口。')
|
|
849
1197
|
: h(TeamServiceConfig, {
|
|
850
1198
|
remote: injectedRemote,
|
|
851
|
-
|
|
852
|
-
openPluginCenter: () => ctx.layout.selectPanel('ant-plugins'),
|
|
1199
|
+
openInstalledPlugin: (name) => ctx.pluginNavigation.openBundle(name),
|
|
853
1200
|
})))
|
|
854
1201
|
}
|
|
855
1202
|
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export const inject = []
|
|
|
8
8
|
const MODEL_SYNC_PATH = '/api/ant-teams/models'
|
|
9
9
|
const SERVICE_INFO_PATH = '/api/ant-teams/service-info'
|
|
10
10
|
const BUNDLE_RELEASE_PATH = '/api/ant-teams/bundle-release'
|
|
11
|
+
const CONNECTION_ORIGIN_PATH = '/api/ant-teams/connection-origin'
|
|
11
12
|
const DISCONNECT_PATH = '/api/ant-teams/disconnect'
|
|
12
13
|
const REQUEST_CODE_PATH = '/api/ant-teams/request-code'
|
|
13
14
|
const CONNECT_PATH = '/api/ant-teams/connect'
|
|
@@ -292,19 +293,37 @@ async function fetchBundleRelease(request, credentials, settings) {
|
|
|
292
293
|
const directInstallSpec = release?.package_name && release?.version
|
|
293
294
|
? `${release.package_name}@${release.version}`
|
|
294
295
|
: ''
|
|
296
|
+
const artifactPath = release?.version ? `/api/ant-bundles/${release.version}/package.tgz` : ''
|
|
297
|
+
const hasArtifact = release?.artifact_path === artifactPath
|
|
298
|
+
&& /^[a-f0-9]{64}$/u.test(release?.artifact_sha256 || '')
|
|
299
|
+
const noArtifact = !release?.artifact_path && !release?.artifact_sha256
|
|
295
300
|
if (release?.bundle_name !== 'ant' || !validPackageName || !validVersion
|
|
296
301
|
|| typeof release?.install_spec !== 'string' || typeof release?.release_notes !== 'string'
|
|
297
302
|
|| (release.version && release.install_spec !== installSpec)
|
|
298
303
|
|| (!release.version && release.install_spec !== '')
|
|
299
|
-
|| (release.direct_install_spec !== undefined && release.direct_install_spec !== directInstallSpec)
|
|
304
|
+
|| (release.direct_install_spec !== undefined && release.direct_install_spec !== directInstallSpec)
|
|
305
|
+
|| (!hasArtifact && !noArtifact)) {
|
|
300
306
|
return jsonResponse(502, { message: 'ANT 更新信息字段无效。' })
|
|
301
307
|
}
|
|
302
|
-
return jsonResponse(200,
|
|
308
|
+
return jsonResponse(200, hasArtifact ? {
|
|
309
|
+
...release,
|
|
310
|
+
artifact_url: new URL(artifactPath, connection.serviceUrl).href,
|
|
311
|
+
} : release)
|
|
303
312
|
} catch {
|
|
304
313
|
return jsonResponse(502, { message: '无法连接 ANT 更新服务。' })
|
|
305
314
|
}
|
|
306
315
|
}
|
|
307
316
|
|
|
317
|
+
async function fetchConnectionOrigin(request, credentials, settings) {
|
|
318
|
+
if (request.method !== 'GET') return jsonResponse(405, { message: '只支持 GET 请求' })
|
|
319
|
+
try {
|
|
320
|
+
const connection = await readMemberConnection(credentials, settings)
|
|
321
|
+
return jsonResponse(200, { service_url: connection?.serviceUrl || null })
|
|
322
|
+
} catch {
|
|
323
|
+
return jsonResponse(500, { message: '无法核对已绑定的 ANT 团队地址。' })
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
308
327
|
async function disconnectMember(request, credentials, settings) {
|
|
309
328
|
if (request.method !== 'POST') return jsonResponse(405, { message: '只支持 POST 请求' })
|
|
310
329
|
let connection
|
|
@@ -397,7 +416,17 @@ async function fetchMemberPlugins(request, credentials, settings) {
|
|
|
397
416
|
{ message: '无法读取企业精选插件目录。' })
|
|
398
417
|
const plugins = JSON.parse(text)
|
|
399
418
|
if (!Array.isArray(plugins)) throw new Error('插件目录格式无效。')
|
|
400
|
-
return jsonResponse(200, { plugins
|
|
419
|
+
return jsonResponse(200, { plugins: plugins.map((plugin) => {
|
|
420
|
+
const validId = typeof plugin?.id === 'string'
|
|
421
|
+
&& /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(plugin.id)
|
|
422
|
+
const validDigest = /^[0-9a-f]{64}$/u.test(plugin?.artifact_sha256 || '')
|
|
423
|
+
if (plugin?.status !== 'published' || !validId || !validDigest) {
|
|
424
|
+
return { ...plugin, artifact_sha256: null }
|
|
425
|
+
}
|
|
426
|
+
const artifactPath = `/api/plugin-artifacts/${plugin.id}/package.tgz`
|
|
427
|
+
return { ...plugin, artifact_path: artifactPath,
|
|
428
|
+
artifact_url: new URL(artifactPath, connection.serviceUrl).href }
|
|
429
|
+
}) })
|
|
401
430
|
} catch {
|
|
402
431
|
return jsonResponse(502, { message: '无法读取企业精选插件目录。' })
|
|
403
432
|
}
|
|
@@ -505,6 +534,12 @@ export function apply(ctx) {
|
|
|
505
534
|
requestBody: 'buffered',
|
|
506
535
|
fetch: (request) => fetchBundleRelease(request, connectionCtx.credentials, connectionCtx.settings),
|
|
507
536
|
}), 'ant-teams: authenticated Bundle release relay')
|
|
537
|
+
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
538
|
+
path: CONNECTION_ORIGIN_PATH,
|
|
539
|
+
methods: ['GET'],
|
|
540
|
+
requestBody: 'buffered',
|
|
541
|
+
fetch: (request) => fetchConnectionOrigin(request, connectionCtx.credentials, connectionCtx.settings),
|
|
542
|
+
}), 'ant-teams: pinned connection origin for update guide')
|
|
508
543
|
connectionCtx.effect(() => connectionCtx.connection.fetch.register({
|
|
509
544
|
path: SKILLS_PATH, methods: ['GET'], requestBody: 'buffered',
|
|
510
545
|
fetch: (request) => fetchMemberSkills(request, connectionCtx.credentials, connectionCtx.settings),
|