@ai-wendao/ant 0.3.14 → 0.3.16

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 (3) hide show
  1. package/README.md +2 -2
  2. package/client.js +228 -85
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,13 +6,13 @@ 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 精确版本创建草稿,填写源码、许可证、依赖和安装脚本的审核记录后发布。成员在“企业扩展 → 插件”查看审核信息和本机状态。对无新增依赖、无安装阶段脚本且归档符合首批准入条件的草稿,管理员可从 npm 官方源锁定实际 `.tgz` 并查看文件清单、下载核验制品;这类草稿经审核发布后,成员可让 DSH 从 ANT 的固定制品地址一键安装。其他版本仍是目录项,需联系管理员安排。安全撤回版本会停止新的 ANT 下载,但不会自动删除本机副本;ANT 设置页继续显示相应风险公告。范围与限制见[上线边界](../../docs/decisions/2026-10-15-plugin-center-rollout.md)。
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
18
 
package/client.js CHANGED
@@ -3,7 +3,7 @@ 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'
@@ -34,14 +34,15 @@ window.__ModuleLoader__.load({
34
34
  border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 8,
35
35
  cursor: 'pointer',
36
36
  }
37
+ const categoryButtonStyle = { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', whiteSpace: 'nowrap' }
37
38
  function ExtensionRequestNotice() {
38
39
  return h('aside', { role: 'note', style: {
39
- ...panelStyle,
40
- borderColor: 'var(--dsw-alias-state-business-primary)',
41
- gap: 5,
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,
42
43
  } },
43
- h('h2', { style: { margin: 0, fontSize: 16 } }, '没有找到想用的技能或插件?'),
44
- h('p', { style: mutedTextStyle }, '目录里没有想用的扩展,或插件没有安装按钮?请联系企业管理员,说明你希望完成的任务或提供扩展地址。已开放安装的技能和插件可在这里直接安装;勿自行安装未经审核的扩展。'))
44
+ h('strong', { style: { flexShrink: 0, color: 'var(--dsw-alias-label-primary)' } }, '提示'),
45
+ h('span', null, '没有找到想用的扩展,或插件没有安装按钮?联系企业管理员,说明用途或提供地址。已开放的扩展可直接安装;未经审核的版本请勿自行安装。'))
45
46
  }
46
47
  const normalizeServiceUrl = (value) => {
47
48
  let url
@@ -149,8 +150,38 @@ window.__ModuleLoader__.load({
149
150
  function TeamServiceConfig({ remote, openInstalledPlugin }) {
150
151
  const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
151
152
  const [draft, setDraft] = useState(serviceUrl)
153
+ const [binding, setBinding] = useState({ state: 'checking', url: null })
154
+ const bindingRevision = useRef(0)
152
155
  const [message, setMessage] = useState('')
153
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])
154
185
  useEffect(() => {
155
186
  let disposed = false
156
187
  const inspect = async () => {
@@ -175,6 +206,10 @@ window.__ModuleLoader__.load({
175
206
  }, [remote])
176
207
  const save = (event) => {
177
208
  event.preventDefault()
209
+ if (binding.state !== 'ready' || binding.url) {
210
+ setMessage(binding.url ? '当前已连接团队;切换前请先在下方断开当前设备。' : '尚未核对这台 DSH 的团队绑定,不能修改地址。')
211
+ return
212
+ }
178
213
  const normalized = normalizeServiceUrl(draft)
179
214
  if (!normalized) {
180
215
  setMessage('请输入 HTTPS 团队服务地址;本机测试可使用 localhost。')
@@ -189,25 +224,39 @@ window.__ModuleLoader__.load({
189
224
  setMessage('浏览器无法保存此地址,请检查本地存储权限。')
190
225
  }
191
226
  }
192
- 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
+ }
193
237
  return h('section', { style: { display: 'grid', gap: 16, width: '100%', maxWidth: 1120 } },
194
238
  h('p', { style: mutedTextStyle }, '连接团队服务后,即可同步获准模型并使用团队协作。'),
195
239
  h('section', { style: panelStyle },
196
240
  h('div', null,
197
241
  h('h3', { style: { margin: 0, fontSize: 16, fontWeight: 650 } }, '团队服务'),
198
242
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '由团队管理员提供服务地址;管理员可在控制台管理成员、模型和协作设置。')),
199
- h('form', { onSubmit: save, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto', alignItems: 'end', gap: 10 } },
200
- 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 ? '当前连接的团队服务地址' : '服务地址'),
201
245
  h('input', {
202
246
  id: 'ant-team-service-url',
203
247
  type: 'url',
204
248
  required: true,
205
- value: draft,
249
+ value: binding.url || draft,
250
+ readOnly: binding.state !== 'ready' || Boolean(binding.url),
206
251
  placeholder: 'https://ant.example.com',
207
- onChange: (event) => setDraft(event.target.value),
252
+ onChange: (event) => { if (binding.state === 'ready' && !binding.url) setDraft(event.target.value) },
208
253
  style: inputStyle,
209
254
  }),
210
- 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,
211
260
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 } },
212
261
  message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
213
262
  link ? h('a', { href: link, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '打开团队管理 ↗') : null)),
@@ -217,17 +266,22 @@ window.__ModuleLoader__.load({
217
266
  `${plugin.title} ${plugin.version}${plugin.versionUnknown ? '(本机版本暂无法读取,请核对是否受影响)' : ''}:${plugin.withdrawal_reason}`)),
218
267
  ...pluginAlerts.map((plugin) => h('button', { key: `${plugin.id}-open`, type: 'button',
219
268
  onClick: () => openInstalledPlugin(plugin.package_name), style: secondaryButtonStyle }, `在 DSH 中查看 ${plugin.title}`))) : null,
220
- h(ModelSyncFooter, { remote, serviceUrl, onServiceUrlChange: setServiceUrl }))
269
+ h(ModelSyncFooter, { remote, serviceUrl, bindingReady: binding.state === 'ready',
270
+ onServiceUrlChange: setServiceUrl, onConnectionChange }))
221
271
  }
222
272
 
223
- function SkillCenterPage({ onBack, onPlugins }) {
273
+ function SkillCenterPage({ remote, onBack, onAll, onSkills, onPlugins, openBundle,
274
+ includePlugins, query, onQueryChange }) {
224
275
  const [skills, setSkills] = useState([])
225
276
  const [loading, setLoading] = useState(false)
277
+ const [loadError, setLoadError] = useState(false)
226
278
  const [workingId, setWorkingId] = useState('')
227
279
  const [message, setMessage] = useState('')
228
- const [query, setQuery] = useState('')
229
280
  const [filter, setFilter] = useState('all')
281
+ const [refreshToken, setRefreshToken] = useState(0)
282
+ const refreshGeneration = useRef(0)
230
283
  const refresh = async () => {
284
+ const generation = ++refreshGeneration.current
231
285
  setLoading(true)
232
286
  try {
233
287
  const response = await fetch('/api/ant-teams/skills', { cache: 'no-store' })
@@ -236,13 +290,17 @@ window.__ModuleLoader__.load({
236
290
  ? '请返回 ANT 插件设置,先验证工作邮箱并连接团队。'
237
291
  : result.message || `读取技能失败(${response.status})。`)
238
292
  if (!Array.isArray(result.skills)) throw new Error('企业技能目录格式无效。')
293
+ if (generation !== refreshGeneration.current) return
239
294
  setSkills(result.skills)
295
+ setLoadError(false)
240
296
  setMessage(result.skills.length ? '' : '团队暂未发布精选技能。')
241
297
  } catch (error) {
298
+ if (generation !== refreshGeneration.current) return
242
299
  setSkills([])
300
+ setLoadError(true)
243
301
  setMessage(error instanceof Error ? error.message : '无法读取企业精选技能。')
244
302
  }
245
- finally { setLoading(false) }
303
+ finally { if (generation === refreshGeneration.current) setLoading(false) }
246
304
  }
247
305
  useEffect(() => { void refresh() }, [])
248
306
  const changeInstall = async (skill, remove = false) => {
@@ -267,38 +325,58 @@ window.__ModuleLoader__.load({
267
325
  finally { setWorkingId('') }
268
326
  }
269
327
  const visible = skills.filter((skill) => {
270
- const matchesFilter = filter === 'all' || skill.local_status === filter
328
+ const matchesFilter = filter === 'all'
329
+ || filter === 'available' && ['not-installed', 'update-available'].includes(skill.local_status)
330
+ || filter === 'installed' && ['installed', 'update-available'].includes(skill.local_status)
331
+ || filter === 'updates' && skill.local_status === 'update-available'
271
332
  const matchesQuery = !query.trim() || `${skill.title} ${skill.description} ${skill.dsh_name}`.toLowerCase().includes(query.trim().toLowerCase())
272
333
  return matchesFilter && matchesQuery
273
334
  })
274
335
  const statusLabel = { installed: '已放入本机目录', 'update-available': '可更新', 'not-installed': '未安装', conflict: '本机冲突' }
275
- return h('section', { 'aria-label': '企业精选技能', style: {
336
+ return h('section', { 'aria-label': includePlugins ? '企业扩展' : '企业精选技能', style: {
276
337
  height: '100%', overflowY: 'auto', boxSizing: 'border-box',
277
338
  padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
278
339
  } }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
279
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
280
- h('button', { type: 'button', onClick: onBack, style: secondaryButtonStyle }, '← 返回 ANT 插件设置'),
281
- h('button', { type: 'button', onClick: onPlugins, style: secondaryButtonStyle }, '查看企业精选插件 →')),
340
+ h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
341
+ h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
342
+ includePlugins
343
+ ? h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '总览')
344
+ : h('button', { type: 'button', onClick: onAll, style: categoryButtonStyle }, '总览'),
345
+ includePlugins
346
+ ? h('button', { type: 'button', onClick: onSkills, style: categoryButtonStyle }, '技能')
347
+ : h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '技能'),
348
+ h('button', { type: 'button', onClick: onPlugins, style: categoryButtonStyle }, '插件')),
349
+ h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
282
350
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'end', justifyContent: 'space-between', gap: 16 } },
283
351
  h('div', null,
284
- h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选技能'),
285
- h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '团队管理员审核后发布;安装到这台 DSH 后,由 DSH 原生技能系统发现和使用。')),
286
- h('button', { type: 'button', disabled: loading || Boolean(workingId), onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
352
+ h('h1', { style: { margin: 0, fontSize: 28 } }, includePlugins ? '企业扩展' : '企业精选技能'),
353
+ h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, includePlugins
354
+ ? '浏览团队审核的技能与插件;选择需要的扩展安装到这台 DSH。'
355
+ : '团队管理员审核后发布;安装到这台 DSH 后,由 DSH 原生技能系统发现和使用。')),
356
+ h('button', { type: 'button', disabled: loading || Boolean(workingId), onClick: () => {
357
+ void refresh()
358
+ if (includePlugins) setRefreshToken((value) => value + 1)
359
+ }, style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
287
360
  h(ExtensionRequestNotice),
288
361
  h('div', { style: { ...panelStyle, gap: 12 } },
289
- h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索技能名称或说明', 'aria-label': '搜索企业精选技能', style: inputStyle }),
362
+ h('input', { type: 'search', value: query, onChange: (event) => onQueryChange(event.target.value),
363
+ placeholder: includePlugins ? '搜索技能或插件名称、用途' : '搜索技能名称或说明',
364
+ 'aria-label': includePlugins ? '搜索企业扩展' : '搜索企业精选技能', style: inputStyle }),
290
365
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
291
- ...[['all', '全部'], ['not-installed', '可安装'], ['installed', '已安装'], ['update-available', '可更新']].map(([value, label]) =>
366
+ ...[['all', '全部'], ['available', '可安装或更新'], ['installed', '已安装'], ['updates', '可更新']].map(([value, label]) =>
292
367
  h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
293
368
  style: { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
294
- h('span', { style: { ...mutedTextStyle, marginLeft: 'auto' } }, `显示 ${visible.length} / ${skills.length} 个技能`))),
295
- message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
369
+ includePlugins ? null : h('span', { style: { ...mutedTextStyle, marginLeft: 'auto' } }, `显示 ${visible.length} / ${skills.length} 个技能`))),
370
+ includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, loading ? '技能 · 读取中…' : `技能 · ${visible.length}`) : null,
371
+ message && (!includePlugins || loadError || skills.length) ? h('p', { role: loadError ? 'alert' : 'status', style: mutedTextStyle }, message) : null,
296
372
  visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
297
373
  ...visible.map((skill) => {
298
374
  const description = skill.description || ''
299
375
  return h('article', { key: skill.id, style: { ...panelStyle, gap: 13 } },
300
376
  h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 } },
301
- h('h2', { style: { margin: 0, fontSize: 19 } }, skill.title),
377
+ h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
378
+ h('h2', { style: { margin: 0, fontSize: 19 } }, skill.title),
379
+ includePlugins ? h('span', { style: mutedTextStyle }, '技能') : null),
302
380
  h('span', { style: mutedTextStyle }, statusLabel[skill.local_status] || '状态未知')),
303
381
  h('p', { style: mutedTextStyle }, description.length > 180 ? `${description.slice(0, 180)}…` : description),
304
382
  description.length > 180 ? h('details', null,
@@ -309,25 +387,33 @@ window.__ModuleLoader__.load({
309
387
  skill.local_status === 'conflict'
310
388
  ? h('p', { role: 'status', style: mutedTextStyle }, '本机同名技能有未受 ANT 管理的内容、文件已被修改或备份损坏;请联系管理员处理。')
311
389
  : h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 'auto' } },
312
- h('button', { type: 'button', disabled: Boolean(workingId) || skill.local_status === 'installed',
390
+ skill.local_status === 'installed' ? null : h('button', { type: 'button', disabled: Boolean(workingId),
313
391
  onClick: () => void changeInstall(skill), style: primaryButtonStyle },
314
- skill.local_status === 'installed' ? '文件已安装' : skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
392
+ skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
315
393
  skill.local_status !== 'not-installed' ? h('button', { type: 'button', disabled: Boolean(workingId),
316
394
  onClick: () => void changeInstall(skill, true), style: secondaryButtonStyle }, '卸载') : null))
317
- })) : !loading && skills.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null))
395
+ })) : !includePlugins && !loading && skills.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null,
396
+ includePlugins ? h(PluginCenterPage, { remote, onBack, onAll, onSkills, openBundle,
397
+ embedded: true, query, onQueryChange, externalFilter: filter,
398
+ hasSkillMatches: visible.length > 0, skillCatalogReady: !loading && !loadError,
399
+ refreshToken }) : null))
318
400
  }
319
401
 
320
- function PluginCenterPage({ remote, onBack, onSkills, openBundle }) {
402
+ function PluginCenterPage({ remote, onBack, onAll, onSkills, openBundle,
403
+ embedded = false, query, onQueryChange, externalFilter = 'all',
404
+ hasSkillMatches = false, skillCatalogReady = false, refreshToken }) {
321
405
  const [plugins, setPlugins] = useState([])
322
406
  const [bundles, setBundles] = useState([])
323
- const [loading, setLoading] = useState(false)
407
+ const [loading, setLoading] = useState(true)
408
+ const [loadError, setLoadError] = useState(false)
324
409
  const [workingId, setWorkingId] = useState('')
325
410
  const [uncertainId, setUncertainId] = useState('')
326
411
  const [completedId, setCompletedId] = useState('')
327
412
  const [message, setMessage] = useState('')
328
- const [query, setQuery] = useState('')
329
413
  const [filter, setFilter] = useState('all')
414
+ const refreshGeneration = useRef(0)
330
415
  const refresh = async () => {
416
+ const generation = ++refreshGeneration.current
331
417
  setLoading(true)
332
418
  try {
333
419
  const manager = remote?.pluginManager
@@ -339,16 +425,20 @@ window.__ModuleLoader__.load({
339
425
  if (!response.ok) throw new Error(result.message || `读取插件目录失败(${response.status})。`)
340
426
  if (!Array.isArray(result.plugins)) throw new Error('企业插件目录格式无效。')
341
427
  if (!listed.ok) throw new Error(listed.error?.message || '无法读取 DSH 本机插件。')
428
+ if (generation !== refreshGeneration.current) return
342
429
  setPlugins(result.plugins)
343
430
  setBundles(listed.value)
431
+ setLoadError(false)
344
432
  setMessage(result.plugins.length ? '' : '团队暂未发布精选插件。')
345
433
  } catch (error) {
434
+ if (generation !== refreshGeneration.current) return
346
435
  setPlugins([])
347
436
  setBundles([])
437
+ setLoadError(true)
348
438
  setMessage(error instanceof Error ? error.message : '无法读取企业精选插件。')
349
- } finally { setLoading(false) }
439
+ } finally { if (generation === refreshGeneration.current) setLoading(false) }
350
440
  }
351
- useEffect(() => { void refresh() }, [])
441
+ useEffect(() => { void refresh() }, [refreshToken])
352
442
  const install = async (plugin) => {
353
443
  const artifactUrl = approvedPluginArtifactUrl(plugin)
354
444
  if (!artifactUrl || workingId || uncertainId === plugin.id || completedId === plugin.id) return
@@ -433,37 +523,51 @@ window.__ModuleLoader__.load({
433
523
  setMessage(error instanceof Error ? error.message : '插件安装未完成,请稍后重试。')
434
524
  } finally { setWorkingId('') }
435
525
  }
526
+ const activeFilter = embedded ? externalFilter : filter
436
527
  const visible = plugins.filter((plugin) => {
437
528
  const installed = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
438
529
  const artifactUrl = approvedPluginArtifactUrl(plugin)
439
530
  const matches = !query.trim() || `${plugin.title} ${plugin.description} ${plugin.package_name}`.toLowerCase().includes(query.trim().toLowerCase())
440
- return matches && (filter === 'all' || filter === 'installed' && installed?.version === plugin.version
441
- || filter === 'available' && artifactUrl && (!installed || (installed.version && installed.version !== plugin.version))
442
- || filter === 'retired' && plugin.status !== 'published')
531
+ return matches && (activeFilter === 'all' || activeFilter === 'installed' && Boolean(installed)
532
+ || activeFilter === 'available' && artifactUrl && (!installed || (installed.version && installed.version !== plugin.version))
533
+ || activeFilter === 'updates' && artifactUrl && installed && installed.version && installed.version !== plugin.version
534
+ || activeFilter === 'retired' && plugin.status !== 'published')
443
535
  })
444
- return h('section', { 'aria-label': '企业精选插件', style: {
445
- height: '100%', overflowY: 'auto', boxSizing: 'border-box',
446
- padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
447
- } }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
448
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
449
- h('button', { type: 'button', onClick: onBack, style: secondaryButtonStyle }, '← 返回 ANT 插件设置'),
450
- h('button', { type: 'button', onClick: onSkills, style: secondaryButtonStyle }, '查看企业精选技能 →')),
451
- h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 } },
536
+ const noMatches = embedded
537
+ ? skillCatalogReady ? '没有找到符合条件的企业扩展。' : '插件中没有符合条件的结果。'
538
+ : activeFilter === 'available'
539
+ ? '当前没有可一键安装的插件。可在“全部”查看审核目录,或联系企业管理员。'
540
+ : '没有符合筛选条件的插件。'
541
+ return h('section', { 'aria-label': '企业精选插件', style: embedded
542
+ ? { display: 'grid', gap: 16 }
543
+ : { height: '100%', overflowY: 'auto', boxSizing: 'border-box',
544
+ padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px' },
545
+ }, h('div', { style: embedded ? { display: 'grid', gap: 16 } : { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
546
+ embedded ? h('h2', { style: { margin: 0, fontSize: 20 } }, loading ? '插件 · 读取中…' : `插件 · ${visible.length}`) : h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
547
+ h('nav', { 'aria-label': '企业扩展分类', style: { display: 'flex', gap: 6 } },
548
+ h('button', { type: 'button', onClick: onAll, style: categoryButtonStyle }, '总览'),
549
+ h('button', { type: 'button', onClick: onSkills, style: categoryButtonStyle }, '技能'),
550
+ h('span', { 'aria-current': 'page', style: { ...categoryButtonStyle, display: 'inline-flex', alignItems: 'center', cursor: 'default', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '插件')),
551
+ h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
552
+ embedded ? null : h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 16 } },
452
553
  h('div', null,
453
554
  h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选插件'),
454
555
  h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。')),
455
556
  h('button', { type: 'button', disabled: loading, onClick: () => void refresh(), style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
456
- h('aside', { role: 'note', style: { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } },
457
- h('strong', null, '只安装团队锁定的固定版本'),
458
- h('p', { style: mutedTextStyle }, '显示“安装”按钮的版本已锁定实际安装包并由管理员发布;其他目录项仅供查看,如需使用请联系企业管理员。')),
459
- h(ExtensionRequestNotice),
460
- h('div', { style: panelStyle },
461
- h('input', { type: 'search', value: query, onChange: (event) => setQuery(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
557
+ !loading && !plugins.length && !loadError ? embedded
558
+ ? h('p', { role: 'status', style: mutedTextStyle }, '团队暂未发布精选插件。')
559
+ : h('section', { role: 'status', style: panelStyle },
560
+ h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
561
+ h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
562
+ !loading && loadError ? h('p', { role: 'alert', style: mutedTextStyle }, message) : null,
563
+ plugins.length && !embedded ? h(ExtensionRequestNotice) : null,
564
+ plugins.length && !embedded ? h('div', { style: panelStyle },
565
+ h('input', { type: 'search', value: query, onChange: (event) => onQueryChange(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
462
566
  h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
463
567
  ...[['all', '全部'], ['available', '可安装或升级'], ['installed', '已安装'], ['retired', '已废弃']].map(([value, label]) =>
464
568
  h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
465
- style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))),
466
- message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
569
+ style: { ...secondaryButtonStyle, minHeight: 34, backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)))) : null,
570
+ plugins.length && message ? h('p', { role: 'status', style: mutedTextStyle }, message) : null,
467
571
  visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
468
572
  ...visible.map((plugin) => {
469
573
  const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
@@ -483,7 +587,9 @@ window.__ModuleLoader__.load({
483
587
  return h('article', { key: plugin.id, style: security
484
588
  ? { ...panelStyle, borderColor: 'var(--dsw-alias-label-danger, #d44)' } : panelStyle },
485
589
  h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 8 } },
486
- h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
590
+ h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
591
+ h('h2', { style: { margin: 0, fontSize: 19 } }, plugin.title),
592
+ embedded ? h('span', { style: mutedTextStyle }, '插件') : null),
487
593
  h('span', { style: security
488
594
  ? { ...mutedTextStyle, color: 'var(--dsw-alias-label-danger, #d44)', fontWeight: 650 } : mutedTextStyle }, status)),
489
595
  h('p', { style: mutedTextStyle }, plugin.description),
@@ -510,12 +616,11 @@ window.__ModuleLoader__.load({
510
616
  local || uncertainId === plugin.id ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
511
617
  local && !sameVersion && plugin.status === 'published' && !artifactUrl
512
618
  ? h('p', { style: mutedTextStyle }, '本机版本与团队审核版本不同;请联系企业管理员核对。') : null))
513
- })) : !loading && plugins.length ? h('p', { style: mutedTextStyle }, filter === 'available'
514
- ? '当前没有可一键安装的插件。可在“全部”查看审核目录,或联系企业管理员。'
515
- : '没有符合筛选条件的插件。') : null))
619
+ })) : !loading && plugins.length && (!embedded || !hasSkillMatches)
620
+ ? h('p', { style: mutedTextStyle }, noMatches) : null))
516
621
  }
517
622
 
518
- function ModelSyncFooter({ remote, serviceUrl, onServiceUrlChange }) {
623
+ function ModelSyncFooter({ remote, serviceUrl, bindingReady, onServiceUrlChange, onConnectionChange }) {
519
624
  const [email, setEmail] = useState('')
520
625
  const [code, setCode] = useState('')
521
626
  const [message, setMessage] = useState('')
@@ -540,13 +645,13 @@ window.__ModuleLoader__.load({
540
645
  : `${message} 本机无法确认旧设备编号;请让原团队管理员按成员查看设备清单,勿用新编号猜测。`
541
646
  }
542
647
 
543
- const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false) => {
648
+ const inspectServiceCompatibility = async (rawServiceUrl, useSavedConnection = false, isCurrent = () => true) => {
544
649
  const normalized = normalizeServiceUrl(rawServiceUrl)
545
650
  if (!normalized && !useSavedConnection) {
546
- setServiceCompatibility({ state: 'idle', message: '' })
651
+ if (isCurrent()) setServiceCompatibility({ state: 'idle', message: '' })
547
652
  return null
548
653
  }
549
- setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
654
+ if (isCurrent()) setServiceCompatibility({ state: 'checking', message: '正在识别 ANT 客户端与服务端版本…' })
550
655
  try {
551
656
  const manager = remote?.pluginManager
552
657
  if (typeof manager?.listBundles !== 'function') throw new Error('当前 DSH 未提供 Bundle 版本读取接口。')
@@ -577,14 +682,14 @@ window.__ModuleLoader__.load({
577
682
  : `已识别 ANT Bundle v${result.bundle_version} 和服务端 v${result.server_version};服务端未提供协议能力,兼容性未知,仍允许继续。`
578
683
  const state = compatibility === true ? 'compatible' : compatibility === false ? 'incompatible' : 'unknown'
579
684
  const status = { state, message, ...result }
580
- setServiceCompatibility(status)
685
+ if (isCurrent()) setServiceCompatibility(status)
581
686
  return status
582
687
  } catch (error) {
583
688
  const status = {
584
689
  state: 'unknown',
585
690
  message: `无法确认 ANT 版本兼容性,仍允许继续:${error instanceof Error ? error.message : '连接检查失败。'}`,
586
691
  }
587
- setServiceCompatibility(status)
692
+ if (isCurrent()) setServiceCompatibility(status)
588
693
  return status
589
694
  }
590
695
  }
@@ -629,6 +734,10 @@ window.__ModuleLoader__.load({
629
734
  }
630
735
 
631
736
  const refreshModels = async () => {
737
+ if (!bindingReady) {
738
+ setMessage('请先等待团队绑定检查完成,再刷新团队模型。')
739
+ return
740
+ }
632
741
  setBusy(true)
633
742
  setMessage('正在从 ANT 刷新获准模型…')
634
743
  try {
@@ -806,6 +915,7 @@ window.__ModuleLoader__.load({
806
915
  const credentialResult = await remote.credentials.unset('ANT_MODEL_API_KEY')
807
916
  if (!credentialResult.ok) throw new Error(credentialResult.error?.message || 'ANT 成员凭证未能清除。')
808
917
  setCredentialConfigured(false)
918
+ onConnectionChange?.(null)
809
919
  setNeedsReconnect(false)
810
920
  setReauthenticate(false)
811
921
  setAllowLocalCleanup(false)
@@ -857,13 +967,13 @@ window.__ModuleLoader__.load({
857
967
  if (!current) return
858
968
  const configured = result.ok ? Boolean(result.value?.ANT_MODEL_API_KEY?.configured) : null
859
969
  setCredentialConfigured(configured)
860
- if (configured) {
970
+ if (configured && bindingReady) {
861
971
  void refreshModels()
862
972
  void checkBundleUpdate()
863
973
  }
864
974
  }).catch(() => { if (current) setCredentialConfigured(null) })
865
975
  return () => { current = false }
866
- }, [remote])
976
+ }, [remote, bindingReady])
867
977
 
868
978
  useEffect(() => {
869
979
  let current = true
@@ -883,11 +993,21 @@ window.__ModuleLoader__.load({
883
993
  }, [remote, credentialConfigured])
884
994
 
885
995
  useEffect(() => {
886
- void inspectServiceCompatibility(serviceUrl, credentialConfigured === true)
887
- }, [remote, serviceUrl, credentialConfigured])
996
+ if (!bindingReady) {
997
+ setServiceCompatibility({ state: 'idle', message: '' })
998
+ return
999
+ }
1000
+ let current = true
1001
+ void inspectServiceCompatibility(serviceUrl, credentialConfigured === true, () => current)
1002
+ return () => { current = false }
1003
+ }, [remote, serviceUrl, credentialConfigured, bindingReady])
888
1004
 
889
1005
  const requestCode = async (event) => {
890
1006
  event.preventDefault()
1007
+ if (!bindingReady) {
1008
+ setMessage('请先等待团队绑定检查完成,再验证工作邮箱。')
1009
+ return
1010
+ }
891
1011
  const normalized = normalizeServiceUrl(serviceUrl)
892
1012
  const normalizedEmail = email.trim()
893
1013
  if (!normalized) {
@@ -929,6 +1049,10 @@ window.__ModuleLoader__.load({
929
1049
 
930
1050
  const connect = async (event) => {
931
1051
  event.preventDefault()
1052
+ if (!bindingReady) {
1053
+ setMessage('请先等待团队绑定检查完成,再连接团队。')
1054
+ return
1055
+ }
932
1056
  const normalized = normalizeServiceUrl(serviceUrl)
933
1057
  const normalizedEmail = email.trim()
934
1058
  const normalizedCode = code.trim()
@@ -945,6 +1069,7 @@ window.__ModuleLoader__.load({
945
1069
  }
946
1070
  setBusy(true)
947
1071
  setMessage('正在验证邮箱并同步团队模型…')
1072
+ let credentialSaved = false
948
1073
  try {
949
1074
  await requireCompatibleService(normalized)
950
1075
  localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
@@ -968,24 +1093,34 @@ window.__ModuleLoader__.load({
968
1093
  }
969
1094
  const credentialWrite = await remote.credentials.set('ANT_MODEL_API_KEY', result.token)
970
1095
  if (!credentialWrite.ok) throw new Error(credentialWrite.error?.message || 'DSH 无法安全保存 ANT 凭证。')
1096
+ credentialSaved = true
971
1097
  await applyAuthorizedModels(result.models, normalized)
972
1098
  setCode('')
973
1099
  onServiceUrlChange(normalized)
974
1100
  setCodeRequested(false)
975
1101
  setCredentialConfigured(true)
1102
+ onConnectionChange?.(normalized)
976
1103
  setAllowLocalCleanup(false)
977
1104
  setNeedsReconnect(false)
978
1105
  setReauthenticate(false)
979
1106
  setMessage(`已连接 ${result.email || normalizedEmail},同步 ${result.models.length} 个获准模型。它们现已出现在 DSH 模型选择器中。`)
980
1107
  } catch (error) {
981
- setMessage(error instanceof Error ? error.message : '连接失败,请稍后重试。')
1108
+ if (credentialSaved) {
1109
+ setCredentialConfigured(true)
1110
+ setCode('')
1111
+ setCodeRequested(false)
1112
+ onConnectionChange?.(normalized)
1113
+ setMessage(`ANT 凭证已保存,但团队模型未能同步:${error instanceof Error ? error.message : '请稍后重试。'} 请点击“刷新团队模型”重试。`)
1114
+ } else {
1115
+ setMessage(error instanceof Error ? error.message : '连接失败,请稍后重试。')
1116
+ }
982
1117
  } finally {
983
1118
  setBusy(false)
984
1119
  }
985
1120
  }
986
1121
 
987
1122
  const showConnectForm = credentialConfigured !== true || reauthenticate || needsReconnect
988
- const canCheckUpdate = credentialConfigured === true && !needsReconnect
1123
+ const canCheckUpdate = bindingReady && credentialConfigured === true && !needsReconnect
989
1124
  const updateGuideUrl = pinnedServiceUrl
990
1125
  ? new URL('/ant-update', pinnedServiceUrl).href : null
991
1126
  return h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 400px), 1fr))', alignItems: 'start', gap: 16 } },
@@ -996,11 +1131,13 @@ window.__ModuleLoader__.load({
996
1131
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '使用上方团队服务地址验证工作邮箱并同步授权模型。连接状态变化时可随时刷新。')),
997
1132
  h('span', { style: {
998
1133
  flexShrink: 0, padding: '4px 9px', borderRadius: 999,
999
- color: credentialConfigured === true ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
1000
- backgroundColor: credentialConfigured === true ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
1134
+ color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
1135
+ backgroundColor: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-tertiary)' : 'var(--dsw-alias-bg-layer-2)',
1001
1136
  fontSize: 12,
1002
- } }, credentialConfigured === true ? '已连接' : credentialConfigured === false ? '未连接' : '检查中')),
1003
- h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true
1137
+ } }, credentialConfigured === true ? bindingReady ? '已连接' : '待核对' : credentialConfigured === false ? '未连接' : '检查中')),
1138
+ h('p', { role: 'status', style: mutedTextStyle }, credentialConfigured === true && !bindingReady
1139
+ ? '本机有 ANT 凭证,但尚未确认它绑定的团队;请先完成上方的绑定检查。'
1140
+ : credentialConfigured === true
1004
1141
  ? 'DSH 已安全保存此设备的 ANT 团队凭证。'
1005
1142
  : credentialConfigured === false ? '验证工作邮箱后即可连接团队模型。' : '正在检查 ANT 连接状态…'),
1006
1143
  serviceCompatibility.state !== 'idle' ? h('p', {
@@ -1011,10 +1148,10 @@ window.__ModuleLoader__.load({
1011
1148
  },
1012
1149
  }, serviceCompatibility.message) : null,
1013
1150
  credentialConfigured === true && !showConnectForm ? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
1014
- h('button', { type: 'button', disabled: busy, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
1015
- h('button', { type: 'button', disabled: busy, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
1151
+ h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => void refreshModels(), style: primaryButtonStyle }, busy ? '刷新中…' : '刷新团队模型'),
1152
+ h('button', { type: 'button', disabled: busy || !bindingReady, onClick: () => { setReauthenticate(true); setMessage('') }, style: secondaryButtonStyle }, '重新验证邮箱'),
1016
1153
  h('button', { type: 'button', disabled: busy, onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() }, style: secondaryButtonStyle }, '断开当前设备')) : null,
1017
- h('section', { style: { ...panelStyle, padding: 14, gap: 10 } },
1154
+ h('section', { style: { display: 'grid', gap: 8, paddingTop: 12, borderTop: '0.5px solid var(--dsw-alias-border-l2)' } },
1018
1155
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12 } },
1019
1156
  h('h4', { style: { margin: 0, fontSize: 14, fontWeight: 650 } }, 'ANT 插件更新'),
1020
1157
  h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking', onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '检查中…' : '检查更新')),
@@ -1034,7 +1171,7 @@ window.__ModuleLoader__.load({
1034
1171
  id: 'ant-team-model-email', type: 'email', autoComplete: 'email', required: true, value: email,
1035
1172
  onChange: (event) => setEmail(event.target.value), style: inputStyle,
1036
1173
  }),
1037
- h('button', { type: 'submit', disabled: busy, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
1174
+ h('button', { type: 'submit', disabled: busy || !bindingReady, style: primaryButtonStyle }, busy ? '发送中…' : '发送验证码')) : null,
1038
1175
  credentialConfigured === true && showConnectForm ? h('button', {
1039
1176
  type: 'button', disabled: busy,
1040
1177
  onClick: () => { if (window.confirm('断开当前设备与 ANT 的连接?本机 ANT 凭证和模型设置将被清除,团队服务地址会保留。')) void disconnect() },
@@ -1047,7 +1184,7 @@ window.__ModuleLoader__.load({
1047
1184
  required: true, maxLength: 8, value: code, placeholder: '8 位验证码',
1048
1185
  onChange: (event) => setCode(event.target.value.replace(/\D/gu, '').slice(0, 8)), style: inputStyle,
1049
1186
  }),
1050
- h('button', { type: 'submit', disabled: busy || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
1187
+ h('button', { type: 'submit', disabled: busy || !bindingReady || code.length !== 8, style: primaryButtonStyle }, busy ? '连接中…' : '验证并连接')) : null,
1051
1188
  allowLocalCleanup ? h('section', { style: { ...panelStyle, padding: 14, gap: 9 } },
1052
1189
  h('p', { style: mutedTextStyle }, savedDeviceId()
1053
1190
  ? `已保存的设备编号:${savedDeviceId()}。请交给原团队管理员核对。`
@@ -1077,10 +1214,16 @@ window.__ModuleLoader__.load({
1077
1214
  }
1078
1215
 
1079
1216
  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 })
1217
+ const [tab, setTab] = useState('all')
1218
+ const [query, setQuery] = useState('')
1219
+ const navigation = {
1220
+ onAll: () => setTab('all'), onSkills: () => setTab('skills'),
1221
+ onPlugins: () => setTab('plugins'),
1222
+ }
1223
+ return tab === 'plugins'
1224
+ ? h(PluginCenterPage, { remote, onBack, openBundle, query, onQueryChange: setQuery, ...navigation })
1225
+ : h(SkillCenterPage, { remote, onBack, openBundle, query, onQueryChange: setQuery,
1226
+ includePlugins: tab === 'all', ...navigation })
1084
1227
  }
1085
1228
 
1086
1229
  function apply(ctx) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
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": {