@ai-wendao/ant 0.3.13 → 0.3.14

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.
Files changed (4) hide show
  1. package/README.md +5 -3
  2. package/client.js +292 -35
  3. package/index.js +38 -3
  4. 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` 导入技能,核对来源、文件和依赖后发布。已连接团队的成员在 DSH 的 ANT 插件设置页点击“打开扩展中心”,进入“技能”页面浏览并安装。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
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 精确版本创建草稿,填写源码、许可证、依赖和安装脚本的审核记录后发布。成员在“扩展中心 → 插件”查看审核信息和本机状态。当前 DSH RC2 无法让 ANT 在安装脚本运行前确认实际下载制品与审核制品一致,因此插件一键安装暂停;需要使用时请联系企业管理员安排。安全撤回版本会在扩展中心标明;本机安装受影响版本或无法读取本机版本时,ANT 设置页也显示相应风险公告。
15
+ 管理员可从 npm 官方源的 DSH Bundle 精确版本创建草稿,填写源码、许可证、依赖和安装脚本的审核记录后发布。成员在“企业扩展 → 插件”查看审核信息和本机状态。对无新增依赖、无安装阶段脚本且归档符合首批准入条件的草稿,管理员可从 npm 官方源锁定实际 `.tgz` 并查看文件清单、下载核验制品;这类草稿经审核发布后,成员可让 DSH 从 ANT 的固定制品地址一键安装。其他版本仍是目录项,需联系管理员安排。安全撤回版本会停止新的 ANT 下载,但不会自动删除本机副本;ANT 设置页继续显示相应风险公告。范围与限制见[上线边界](../../docs/decisions/2026-10-15-plugin-center-rollout.md)。
16
16
 
17
- 企业精选是团队推荐目录,不是强制白名单;ANT 不能阻止成员从 DSH 原生插件管理页自行安装其他包。当前 DSH RC2 接口无法让 ANT 强制校验成员安装的包字节与审核时的摘要一致,也不能保证 profile 的依赖范围始终为精确版本;上线边界见 [待决策记录](../../docs/decisions/2026-10-15-plugin-center-rollout.md)。
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
@@ -9,6 +9,7 @@ window.__ModuleLoader__.load({
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,
@@ -40,7 +41,7 @@ window.__ModuleLoader__.load({
40
41
  gap: 5,
41
42
  } },
42
43
  h('h2', { style: { margin: 0, fontSize: 16 } }, '没有找到想用的技能或插件?'),
43
- h('p', { style: mutedTextStyle }, '请联系企业管理员,说明你希望完成的任务或提供扩展地址。管理员审核后会将合适的版本加入扩展中心。技能可在这里安装;插件请由管理员安排,勿自行安装未经审核的扩展。'))
44
+ h('p', { style: mutedTextStyle }, '目录里没有想用的扩展,或插件没有安装按钮?请联系企业管理员,说明你希望完成的任务或提供扩展地址。已开放安装的技能和插件可在这里直接安装;勿自行安装未经审核的扩展。'))
44
45
  }
45
46
  const normalizeServiceUrl = (value) => {
46
47
  let url
@@ -97,6 +98,35 @@ window.__ModuleLoader__.load({
97
98
  : ''
98
99
  }
99
100
 
101
+ function approvedArtifactUrl(release) {
102
+ const expectedPath = `/api/ant-bundles/${release?.version}/package.tgz`
103
+ let url
104
+ try { url = new URL(release?.artifact_url) } catch { return null }
105
+ return normalizeServiceUrl(url.origin) === url.origin
106
+ && !url.username && !url.password && !url.search && !url.hash
107
+ && url.pathname === expectedPath && release?.artifact_path === expectedPath
108
+ && /^[a-f0-9]{64}$/u.test(release?.artifact_sha256 || '')
109
+ ? url.href : null
110
+ }
111
+
112
+ function approvedPluginArtifactUrl(plugin) {
113
+ if (plugin?.status !== 'published' || typeof plugin.id !== 'string'
114
+ || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(plugin.id)) return null
115
+ const expectedPath = `/api/plugin-artifacts/${plugin.id}/package.tgz`
116
+ let url
117
+ try { url = new URL(plugin.artifact_url) } catch { return null }
118
+ return normalizeServiceUrl(url.origin) === url.origin
119
+ && !url.username && !url.password && !url.search && !url.hash
120
+ && url.pathname === expectedPath && plugin.artifact_path === expectedPath
121
+ && /^[a-f0-9]{64}$/u.test(plugin.artifact_sha256 || '')
122
+ ? url.href : null
123
+ }
124
+
125
+ function previouslyInstalledReviewedPlugin(plugin) {
126
+ try { return localStorage.getItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`) === plugin.artifact_sha256 }
127
+ catch { return false }
128
+ }
129
+
100
130
  function loadServiceUrl() {
101
131
  try { return normalizeServiceUrl(localStorage.getItem(SERVICE_URL_STORAGE_KEY) || '') || '' } catch { return '' }
102
132
  }
@@ -116,7 +146,7 @@ window.__ModuleLoader__.load({
116
146
  return deviceId
117
147
  }
118
148
 
119
- function TeamServiceConfig({ remote, openSkillCenter, openPluginCenter }) {
149
+ function TeamServiceConfig({ remote, openInstalledPlugin }) {
120
150
  const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
121
151
  const [draft, setDraft] = useState(serviceUrl)
122
152
  const [message, setMessage] = useState('')
@@ -181,16 +211,12 @@ window.__ModuleLoader__.load({
181
211
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
182
212
  message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
183
213
  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
214
  pluginAlerts.length ? h('section', { role: 'alert', style: { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } },
190
215
  h('h3', { style: { margin: 0, fontSize: 16 } }, '已安装插件存在安全撤回公告'),
191
216
  ...pluginAlerts.map((plugin) => h('p', { key: plugin.id, style: mutedTextStyle },
192
217
  `${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
193
- h('button', { type: 'button', onClick: openPluginCenter, style: secondaryButtonStyle }, '查看插件并打开 DSH 管理 →')) : null,
218
+ ...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
219
+ onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
194
220
  h(ModelSyncFooter, { remote, serviceUrl, onServiceUrlChange: setServiceUrl }))
195
221
  }
196
222
 
@@ -295,6 +321,9 @@ window.__ModuleLoader__.load({
295
321
  const [plugins, setPlugins] = useState([])
296
322
  const [bundles, setBundles] = useState([])
297
323
  const [loading, setLoading] = useState(false)
324
+ const [workingId, setWorkingId] = useState('')
325
+ const [uncertainId, setUncertainId] = useState('')
326
+ const [completedId, setCompletedId] = useState('')
298
327
  const [message, setMessage] = useState('')
299
328
  const [query, setQuery] = useState('')
300
329
  const [filter, setFilter] = useState('all')
@@ -320,11 +349,96 @@ window.__ModuleLoader__.load({
320
349
  } finally { setLoading(false) }
321
350
  }
322
351
  useEffect(() => { void refresh() }, [])
352
+ const install = async (plugin) => {
353
+ const artifactUrl = approvedPluginArtifactUrl(plugin)
354
+ if (!artifactUrl || workingId || uncertainId === plugin.id || completedId === plugin.id) return
355
+ const manager = remote?.pluginManager
356
+ if (typeof manager?.installBundle !== 'function') {
357
+ setMessage('当前 DSH 未提供插件安装接口,请联系管理员。')
358
+ return
359
+ }
360
+ const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
361
+ const action = local ? local.version === plugin.version ? '重新安装' : '更新' : '安装'
362
+ if (!window.confirm(`在这台 DSH ${action}团队审核的 ${plugin.title} ${plugin.version}?插件将在本机运行,完成后需要完全重启 DSH。`)) return
363
+ setWorkingId(plugin.id)
364
+ setMessage(`正在安装 ${plugin.title},请不要退出 DSH…`)
365
+ try {
366
+ const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
367
+ const catalog = await readLocalJsonResponse(response)
368
+ const latest = Array.isArray(catalog?.plugins)
369
+ ? catalog.plugins.find((item) => item.id === plugin.id) : null
370
+ if (!response.ok || latest?.package_name !== plugin.package_name
371
+ || latest?.version !== plugin.version || latest?.artifact_sha256 !== plugin.artifact_sha256
372
+ || approvedPluginArtifactUrl(latest) !== artifactUrl) {
373
+ throw new Error('插件审核信息已变化,请刷新目录后重试。')
374
+ }
375
+ const requestId = crypto.randomUUID()
376
+ let result
377
+ try { result = await manager.installBundle(artifactUrl, { enabled: true, requestId }) }
378
+ catch { result = null }
379
+ if (!result?.ok) {
380
+ let recovered = null
381
+ if (typeof manager.waitForInstall === 'function') {
382
+ try {
383
+ const pending = await manager.waitForInstall(requestId)
384
+ if (pending.ok && pending.value) recovered = { ok: true, value: pending.value }
385
+ } catch {}
386
+ }
387
+ if (!recovered) {
388
+ setUncertainId(plugin.id)
389
+ setMessage(`暂时无法确认 ${plugin.title} 的安装结果。请先在 DSH 插件列表核对,勿重复点击安装。`)
390
+ return
391
+ }
392
+ result = recovered
393
+ }
394
+ const outcome = result.value
395
+ if (!outcome || typeof outcome !== 'object') {
396
+ setUncertainId(plugin.id)
397
+ setMessage(`DSH 未返回 ${plugin.title} 的安装结果。请先在插件列表核对,勿重复点击安装。`)
398
+ return
399
+ }
400
+ if (outcome.application === 'failed' || outcome.application === 'cancelled') {
401
+ if (outcome.error?.code === 'ambiguous-install' && local?.version === plugin.version) {
402
+ setMessage(`DSH 无法确定 ${plugin.title} 本次安装的唯一结果,未确认完成。请先在 DSH 插件列表核对;若已经正常使用同版则无需重复安装,仍需核对来源时请联系管理员。`)
403
+ return
404
+ }
405
+ const diagnostic = typeof outcome.error?.diagnostic === 'string'
406
+ ? outcome.error.diagnostic.slice(0, 180) : ''
407
+ setMessage(`${plugin.title} 安装未完成。请在 DSH 插件管理查看状态或联系管理员。${diagnostic ? `原因:${diagnostic}` : ''}`)
408
+ return
409
+ }
410
+ if (outcome.bundle !== plugin.package_name
411
+ || !['restart-required', 'applied'].includes(outcome.application)) {
412
+ setUncertainId(plugin.id)
413
+ setMessage(`DSH 未确认 ${plugin.title} 已安装。请先在插件列表核对,勿重复点击安装。`)
414
+ return
415
+ }
416
+ let observed = null
417
+ try {
418
+ const installed = await manager.listBundles()
419
+ if (installed.ok && Array.isArray(installed.value)) {
420
+ observed = installed.value.find((bundle) => bundle.name === plugin.package_name && bundle.installed) || null
421
+ }
422
+ } catch {}
423
+ if ((outcome.version && outcome.version !== plugin.version) || observed?.version !== plugin.version) {
424
+ setUncertainId(plugin.id)
425
+ setMessage(`DSH 未确认 ${plugin.title} 的审核版本已安装。请先在插件列表核对,勿重复点击安装。`)
426
+ return
427
+ }
428
+ await refresh()
429
+ try { localStorage.setItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`, plugin.artifact_sha256) } catch {}
430
+ setCompletedId(plugin.id)
431
+ setMessage(`${plugin.title} 已安装。请完全退出并重新启动 DSH 后使用。`)
432
+ } catch (error) {
433
+ setMessage(error instanceof Error ? error.message : '插件安装未完成,请稍后重试。')
434
+ } finally { setWorkingId('') }
435
+ }
323
436
  const visible = plugins.filter((plugin) => {
324
437
  const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
438
+ const artifactUrl = approvedPluginArtifactUrl(plugin)
325
439
  const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
326
440
  return matches && (filter === 'all' || filter === 'installed' && installed?.version === plugin.version
327
- || filter === 'available' && plugin.status === 'published' && !installed
441
+ || filter === 'available' && artifactUrl && (!installed || (installed.version && installed.version !== plugin.version))
328
442
  || filter === 'retired' && plugin.status !== 'published')
329
443
  })
330
444
  return h('section', { 'aria-label': '企业精选插件', style: {
@@ -340,13 +454,13 @@ window.__ModuleLoader__.load({
340
454
  h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。')),
341
455
  h('button', { type: 'button', disabled: loading, onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
342
456
  h('aside', { role: 'note', style: { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } },
343
- h('strong', null, '插件一键安装暂未开放'),
344
- h('p', { style: mutedTextStyle }, '当前 DSH 无法让 ANT 在运行代码前核对实际下载的插件及其依赖。需要使用某个插件时,请联系企业管理员安排审核与安全安装;请勿自行从其他来源安装。')),
457
+ h('strong', null, '只安装团队锁定的固定版本'),
458
+ h('p', { style: mutedTextStyle }, '显示“安装”按钮的版本已锁定实际安装包并由管理员发布;其他目录项仅供查看,如需使用请联系企业管理员。')),
345
459
  h(ExtensionRequestNotice),
346
460
  h('div', { style: panelStyle },
347
461
  h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
348
462
  h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
349
- ...[['all', '全部'], ['available', '已审核'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
463
+ ...[['all', '全部'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
350
464
  h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
351
465
  style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))),
352
466
  message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
@@ -355,10 +469,17 @@ window.__ModuleLoader__.load({
355
469
  const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
356
470
  const sameVersion = local?.version === plugin.version
357
471
  const security = plugin.status === 'security_withdrawn'
472
+ const artifactUrl = approvedPluginArtifactUrl(plugin)
473
+ const sourceKnown = typeof local?.source === 'string'
474
+ const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
475
+ const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
358
476
  const status = security ? '已废弃(安全风险)'
359
- : plugin.status === 'deprecated' ? '已废弃' : sameVersion
360
- ? local.error ? '加载异常' : local.enabled ? '已安装(配置已启用)' : '已安装未启用'
361
- : local ? `本机版本 ${local.version || '暂无法读取'}` : '已审核,等待安全安装'
477
+ : plugin.status === 'deprecated' ? '已废弃' : local?.error ? '加载异常' : sameVersion
478
+ ? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
479
+ : sourceKnown ? '本机同版,来源不同' : recordedInstall
480
+ ? '本机同版,曾由企业扩展安装;当前 DSH 无法复核来源' : '本机同版,当前 DSH 无法核对来源'
481
+ : artifactUrl ? local ? local.version ? `可更新 · 本机 ${local.version}` : '本机版本暂无法读取' : '可安装'
482
+ : local ? `本机版本 ${local.version || '暂无法读取'}` : '已审核,暂需管理员安装'
362
483
  return h('article', { key: plugin.id, style: security
363
484
  ? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
364
485
  h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
@@ -374,13 +495,24 @@ window.__ModuleLoader__.load({
374
495
  ? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)' } : mutedTextStyle },
375
496
  plugin.withdrawal_reason || '团队已停止推荐此版本。') : null,
376
497
  local?.error ? h('p', { role: 'status', style: mutedTextStyle }, local.error.message || String(local.error)) : null,
498
+ sameVersion && !sourceKnown && artifactUrl ? h('p', { style: mutedTextStyle }, recordedInstall
499
+ ? '此前曾由企业扩展安装;当前 DSH 无法再次核对来源。正常使用无需重复安装;若后来换过来源,可以重新安装。'
500
+ : '已装同版且使用正常时无需重复安装;若想改用团队审核包,可自愿安装。') : null,
377
501
  h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
378
- plugin.status === 'published' && !local
502
+ artifactUrl && (!local || (local.version && !verifiedLocal))
503
+ ? h('button', { type: 'button', disabled: Boolean(workingId) || uncertainId === plugin.id || completedId === plugin.id,
504
+ onClick: () => void install(plugin), style: primaryButtonStyle },
505
+ workingId === plugin.id ? '安装中…' : uncertainId === plugin.id ? '先核对安装结果'
506
+ : completedId === plugin.id ? '重启 DSH 后生效'
507
+ : local ? sameVersion ? '按团队审核包重新安装' : '更新到审核版本' : '安装到这台 DSH') : null,
508
+ plugin.status === 'published' && !local && !artifactUrl
379
509
  ? 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'
510
+ local || uncertainId === plugin.id ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
511
+ local && !sameVersion && plugin.status === 'published' && !artifactUrl
382
512
  ? h('p', { style: mutedTextStyle }, '本机版本与团队审核版本不同;请联系企业管理员核对。') : null))
383
- })) : !loading && plugins.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的插件。') : null))
513
+ })) : !loading && plugins.length ? h('p', { style: mutedTextStyle }, filter === 'available'
514
+ ? '当前没有可一键安装的插件。可在“全部”查看审核目录,或联系企业管理员。'
515
+ : '没有符合筛选条件的插件。') : null))
384
516
  }
385
517
 
386
518
  function ModelSyncFooter({ remote, serviceUrl, onServiceUrlChange }) {
@@ -396,6 +528,7 @@ window.__ModuleLoader__.load({
396
528
  const [bundleRelease, setBundleRelease] = useState(null)
397
529
  const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
398
530
  const [serviceCompatibility, setServiceCompatibility] = useState({ state: 'idle', message: '' })
531
+ const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
399
532
 
400
533
  const connectionErrorMessage = (result, status) => {
401
534
  const message = result?.message || `请求失败(${status})`
@@ -563,16 +696,105 @@ window.__ModuleLoader__.load({
563
696
  throw new Error(`当前 DSH 未找到已安装的 ${release.package_name} Bundle。`)
564
697
  }
565
698
  const available = newerVersion(release.version, installed.version)
699
+ if (release.artifact_status === 'revoked') {
700
+ setBundleUpdate({ state: 'revoked', currentVersion: installed.version,
701
+ message: installed.version === release.version
702
+ ? `当前 ANT ${installed.version} 的安装包已因安全问题停用。请联系企业管理员获取处理方式;ANT 不会自动删除本机副本。`
703
+ : `发布版本 ${release.version} 的安装包已因安全问题停用,请等待管理员发布修复版本。` })
704
+ return
705
+ }
706
+ const artifactUrl = approvedArtifactUrl(release)
566
707
  setBundleUpdate({
567
- state: available ? 'available' : 'current',
708
+ state: available ? artifactUrl ? 'available' : 'artifact-missing' : 'current',
568
709
  currentVersion: installed.version,
569
- message: available ? `发现新版本 ${release.version}。` : `当前已是最新发布版本(${installed.version})。`,
710
+ message: available
711
+ ? artifactUrl
712
+ ? `发现新版本 ${release.version},可直接更新。`
713
+ : `发现新版本 ${release.version},但管理员尚未提供已审核安装包。请联系管理员。`
714
+ : `当前已是最新发布版本(${installed.version})。`,
570
715
  })
571
716
  } catch (error) {
572
717
  setBundleUpdate({ state: 'failed', currentVersion: '', message: error instanceof Error ? error.message : '检查 ANT 更新失败。' })
573
718
  }
574
719
  }
575
720
 
721
+ const installBundleUpdate = async () => {
722
+ if (bundleUpdate.state !== 'available') return
723
+ const artifactUrl = approvedArtifactUrl(bundleRelease)
724
+ if (!artifactUrl) return
725
+ const manager = remote?.pluginManager
726
+ if (typeof manager?.installBundle !== 'function') {
727
+ setBundleUpdate((current) => ({ ...current, state: 'failed', message: '当前 DSH 未提供 Bundle 更新接口。' }))
728
+ return
729
+ }
730
+ setBusy(true)
731
+ setBundleUpdate((current) => ({ ...current, state: 'installing', message: '正在由 DSH 安装已审核的 ANT 更新…' }))
732
+ try {
733
+ const releaseResponse = await fetch('/api/ant-teams/bundle-release', {
734
+ cache: 'no-store', headers: {
735
+ 'x-ant-bundle-capabilities': 'direct-package-install',
736
+ 'x-ant-bundle-update-mode': 'check-only',
737
+ },
738
+ })
739
+ const latest = await readLocalJsonResponse(releaseResponse)
740
+ if (!releaseResponse.ok || latest.version !== bundleRelease.version
741
+ || latest.artifact_sha256 !== bundleRelease.artifact_sha256
742
+ || approvedArtifactUrl(latest) !== artifactUrl) {
743
+ throw new Error('ANT 更新信息已变化,请重新检查更新。')
744
+ }
745
+ const requestId = crypto.randomUUID()
746
+ let result
747
+ try {
748
+ result = await manager.installBundle(artifactUrl, { enabled: true, requestId })
749
+ } catch {
750
+ result = null
751
+ }
752
+ if (!result?.ok) {
753
+ let recovered = null
754
+ if (typeof manager.waitForInstall === 'function') {
755
+ try {
756
+ const pending = await manager.waitForInstall(requestId)
757
+ if (pending.ok && pending.value) recovered = { ok: true, value: pending.value }
758
+ } catch {}
759
+ }
760
+ if (!recovered) {
761
+ setBundleUpdate((current) => ({ ...current, state: 'unknown',
762
+ message: 'DSH 连接中断,更新结果暂时无法确认。请先在 DSH 插件列表查看 ANT 版本,勿重复点击更新。' }))
763
+ return
764
+ }
765
+ result = recovered
766
+ }
767
+ const outcome = result.value
768
+ if (outcome.application === 'failed' || outcome.application === 'cancelled') {
769
+ const reason = outcome.error?.diagnostic || outcome.packageResult?.output || '安装未完成。'
770
+ setBundleUpdate((current) => ({ ...current, state: 'failed', message: `更新失败:${reason}` }))
771
+ return
772
+ }
773
+ if (outcome.bundle !== ANT_BUNDLE_PACKAGE_NAME
774
+ || !['restart-required', 'applied'].includes(outcome.application)) {
775
+ throw new Error('DSH 未确认 ANT 安装成功,请联系管理员检查插件状态。')
776
+ }
777
+ let observed = null
778
+ try {
779
+ const installed = await manager.listBundles()
780
+ if (installed.ok && Array.isArray(installed.value)) {
781
+ observed = installed.value.find((bundle) => bundle.name === ANT_BUNDLE_PACKAGE_NAME && bundle.installed) || null
782
+ }
783
+ } catch {}
784
+ if ((outcome.version && outcome.version !== bundleRelease.version) || observed?.version !== bundleRelease.version) {
785
+ setBundleUpdate((current) => ({ ...current, state: 'unknown',
786
+ message: 'DSH 未确认新版本已安装。请先在 DSH 插件列表核对 ANT 版本,勿重复点击更新。' }))
787
+ return
788
+ }
789
+ setBundleUpdate({ state: 'done', currentVersion: bundleRelease.version,
790
+ message: `ANT ${bundleRelease.version} 已安装。请完全退出并重新启动 DSH 后生效。` })
791
+ } catch (error) {
792
+ setBundleUpdate((current) => ({ ...current, state: 'failed', message: error instanceof Error ? error.message : '更新未完成。' }))
793
+ } finally {
794
+ setBusy(false)
795
+ }
796
+ }
797
+
576
798
  const clearLocalConnection = async () => {
577
799
  if (typeof remote?.settings?.mutate !== 'function') throw new Error('DSH 未提供模型设置接口。')
578
800
  const settingsResult = await remote.settings.mutate('llm-pi-ai', [
@@ -643,6 +865,23 @@ window.__ModuleLoader__.load({
643
865
  return () => { current = false }
644
866
  }, [remote])
645
867
 
868
+ useEffect(() => {
869
+ let current = true
870
+ if (credentialConfigured !== true) {
871
+ setPinnedServiceUrl(null)
872
+ return () => { current = false }
873
+ }
874
+ const readPinnedOrigin = async () => {
875
+ try {
876
+ const response = await fetch('/api/ant-teams/connection-origin', { cache: 'no-store' })
877
+ const result = await readLocalJsonResponse(response)
878
+ if (current) setPinnedServiceUrl(response.ok ? normalizeServiceUrl(result.service_url) : null)
879
+ } catch { if (current) setPinnedServiceUrl(null) }
880
+ }
881
+ void readPinnedOrigin()
882
+ return () => { current = false }
883
+ }, [remote, credentialConfigured])
884
+
646
885
  useEffect(() => {
647
886
  void inspectServiceCompatibility(serviceUrl, credentialConfigured === true)
648
887
  }, [remote, serviceUrl, credentialConfigured])
@@ -746,6 +985,9 @@ window.__ModuleLoader__.load({
746
985
  }
747
986
 
748
987
  const showConnectForm = credentialConfigured !== true || reauthenticate || needsReconnect
988
+ const canCheckUpdate = credentialConfigured === true && !needsReconnect
989
+ const updateGuideUrl = pinnedServiceUrl
990
+ ? new URL('/ant-update', pinnedServiceUrl).href : null
749
991
  return h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 400px), 1fr))', alignItems: 'start', gap: 16 } },
750
992
  h('section', { style: panelStyle },
751
993
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
@@ -772,15 +1014,20 @@ window.__ModuleLoader__.load({
772
1014
  h('button', { type: 'button', disabled: busy, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
773
1015
  h('button', { type: 'button', disabled: busy, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
774
1016
  h('button', { type: 'button', disabled: busy, onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() }, style: secondaryButtonStyle }, '断开当前设备')) : null,
775
- credentialConfigured === true && !showConnectForm ? h('section', { style: { ...panelStyle, padding: 14, gap: 10 } },
1017
+ h('section', { style: { ...panelStyle, padding: 14, gap: 10 } },
776
1018
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },
777
1019
  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
- bundleUpdate.message ? h('p', { role: 'status', style: mutedTextStyle }, bundleUpdate.message) : null,
780
- bundleRelease?.version && bundleUpdate.state === 'available' ? h('div', { style: { display: 'grid', gap: 8 } },
1020
+ h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
1021
+ !canCheckUpdate ? h('p', { role: 'status', style: mutedTextStyle }, '连接团队后可在这里检查并安装 ANT 更新;需要先恢复插件时,可打开团队管理台的更新指引。') : null,
1022
+ updateGuideUrl ? h('a', { href: updateGuideUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, `打开 ${pinnedServiceUrl} 的更新指引 ↗`)
1023
+ : !canCheckUpdate ? h('p', { style: mutedTextStyle }, '无法核对原团队地址时,请向企业管理员索取更新指引。') : null,
1024
+ bundleUpdate.message ? h('p', { role: 'status', style: bundleUpdate.state === 'revoked'
1025
+ ? { ...mutedTextStyle, color: 'var(--dsw-alias-state-error-primary)' } : mutedTextStyle }, bundleUpdate.message) : null,
1026
+ canCheckUpdate && bundleRelease?.version && ['available', 'artifact-missing'].includes(bundleUpdate.state) ? h('div', { style: { display: 'grid', gap: 8 } },
781
1027
  h('p', { style: mutedTextStyle }, `发布版本:${bundleRelease.version} · 精确包名:${directInstallSpec(bundleRelease)}`),
782
1028
  bundleRelease.release_notes ? h('p', { style: mutedTextStyle }, bundleRelease.release_notes) : null,
783
- h('p', { style: mutedTextStyle }, 'ANT 暂停一键安装更新:当前 DSH 无法在执行安装脚本前核对审核制品。请联系团队管理员确认升级方式。')) : null) : null,
1029
+ bundleUpdate.state === 'available'
1030
+ ? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null) : null),
784
1031
  showConnectForm ? h('form', { onSubmit: requestCode, style: { display: 'grid', gap: 9, maxWidth: 560 } },
785
1032
  h('label', { htmlFor: 'ant-team-model-email', style: { fontSize: 13, fontWeight: 550 } }, '工作邮箱'),
786
1033
  h('input', {
@@ -822,6 +1069,20 @@ window.__ModuleLoader__.load({
822
1069
 
823
1070
  }
824
1071
 
1072
+ function EnterpriseExtensionsPanelIcon({ size }) {
1073
+ return h('span', { 'aria-hidden': true, style: {
1074
+ display: 'inline-grid', placeItems: 'center', width: size, height: size,
1075
+ fontSize: size, lineHeight: 1,
1076
+ } }, '✦')
1077
+ }
1078
+
1079
+ function EnterpriseExtensionsPage({ remote, onBack, openBundle }) {
1080
+ const [tab, setTab] = useState('skills')
1081
+ return tab === 'skills'
1082
+ ? h(SkillCenterPage, { onBack, onPlugins: () => setTab('plugins') })
1083
+ : h(PluginCenterPage, { remote, onBack, onSkills: () => setTab('skills'), openBundle })
1084
+ }
1085
+
825
1086
  function apply(ctx) {
826
1087
  const remote = {
827
1088
  credentials: ctx.remote.credentials,
@@ -829,17 +1090,14 @@ window.__ModuleLoader__.load({
829
1090
  pluginManager: ctx.remote.pluginManager,
830
1091
  }
831
1092
  ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: 'ant-skills' },
832
- () => h(SkillCenterPage, {
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, {
1093
+ () => h(EnterpriseExtensionsPage, {
838
1094
  remote,
839
1095
  onBack: () => ctx.pluginNavigation.openBundle(ANT_BUNDLE_PACKAGE_NAME),
840
- onSkills: () => ctx.layout.selectPanel('ant-skills'),
841
1096
  openBundle: (name) => ctx.pluginNavigation.openBundle(name),
842
1097
  })))
1098
+ ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
1099
+ name: 'sidebar.panellist', id: 'ant-skills', order: 5, label: '企业扩展',
1100
+ }, EnterpriseExtensionsPanelIcon))
843
1101
  ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
844
1102
  name: 'plugins.bundle.config',
845
1103
  key: ANT_BUNDLE_PACKAGE_NAME,
@@ -848,8 +1106,7 @@ window.__ModuleLoader__.load({
848
1106
  ? h('p', null, 'ANT 团队服务入口。')
849
1107
  : h(TeamServiceConfig, {
850
1108
  remote: injectedRemote,
851
- openSkillCenter: () => ctx.layout.selectPanel('ant-skills'),
852
- openPluginCenter: () => ctx.layout.selectPanel('ant-plugins'),
1109
+ openInstalledPlugin: (name) => ctx.pluginNavigation.openBundle(name),
853
1110
  })))
854
1111
  }
855
1112
 
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, release)
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),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.3.13",
3
+ "version": "0.3.14",
4
4
  "description": "ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to team identity and approved models.",
5
5
  "type": "module",
6
6
  "scripts": {