@ai-wendao/ant 0.5.11 → 0.5.12

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 CHANGED
@@ -2,10 +2,24 @@
2
2
 
3
3
  ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to its team's membership and approved models.
4
4
 
5
+ ## 安装包与更新通道
6
+
7
+ ANT 同一份源码可发布为 `@ai-wendao/ant` 和 `dsh-ant`,DSH 添加插件时直接输入其中一个包名。两者连接同一 ANT 服务并使用相同业务协议;同一 DSH profile 只安装并启用其中一份,不同时启用两个包,也不使用 npm alias。选择新包不代表自动迁移旧包;需要跨包切换时先另行验证凭证和本机配置的保留流程。
8
+
9
+ 每个包只查询和安装自己的已审核更新。缺省更新指引 `/ant-update` 对应原包;`dsh-ant` 使用 `/ant-update?package_name=dsh-ant`,该指引分发以 `.tgz` 结尾、不带查询参数的 `/api/ant-bundles/dsh-ant/<版本>/package.tgz` 安装地址。服务端须先升级到支持双包的版本;旧服务端返回原包信息时,新包拒绝安装,不跨包回退。历史 `ant` alias 的迁移工具仍只处理 `@ai-wendao/ant`,不用于切换包名。
10
+
11
+ ## DSH 客户端兼容性
12
+
13
+ 当前 Bundle 声明的 DSH 最低版本是 `0.2.1-rc.3`。DSH 会在安装和启动 profile 时检查此范围;低于最低版本时,DSH 插件管理器会报告并跳过 ANT Bundle。高于最低版本的 DSH 不会被版本上限拦截;ANT 只把 `0.2.1-rc.3` 标为已记录的验收基线,其他版本显示为“未验收”。ANT 的“关于”页会检查当前 DSH 与候选 ANT 制品的兼容状态;低于最低版本、版本未知或候选制品未提供范围时,不提供自动更新操作。
14
+
15
+ `0.2.1-rc.3` 是仓库当前有记录的 DSH 基线。更高的稳定版和预发布版都可以通过最低版本门禁,但在完成对应 Desktop/DSH-IM 验证前仍标为“未验收”。ANT 协作会话还会在实际调用前检查 Session 事件接口;缺少该接口时会提示检查 DSH 兼容状态,不发送协作 prompt。
16
+
5
17
  ## 团队模型默认值
6
18
 
7
19
  团队管理员在 ANT 控制台设置团队默认模型,并授权成员可用模型。成员第一次在 ANT 插件中同步模型时,团队默认会写入 DSH 的全局默认模型;成员之后可在“团队模型”页选另一项已获准模型。成员也可按 provider 选择同步到本机 DSH 的模型路由;首次升级保留已有路由,关闭某项并刷新后会从本机 Profile 移除,保存了该路由的历史会话可能暂时无法调用,重新启用并刷新可恢复。普通同步保留成员默认选择;若模型被撤销,插件会回到仍获准的团队默认。该默认写入 DSH profile,因此也影响同一 profile 新建的普通 DSH 会话。ANT 对话和任务会额外校验当前用户的获准模型;Codex 与 Claude Code 使用各自执行器的默认模型。
8
20
 
21
+ 图片输入能力由管理员按团队模型配置;成员默认继承。成员可在“团队模型”页关闭当前 DSH Profile 中单个模型的图片输入,并通过刷新应用;这只限制本机同步,不修改团队设置或授权。管理员未开放图片输入时,成员不能自行打开。
22
+
9
23
  每个 OpenAI 兼容模型可配置 Chat Completions 或 Responses API。旧模型记录在升级后按 Chat Completions 解释;ANT 服务端继续使用原配置的 `/chat/completions` URL,并从相同 API 根路径派生 `/responses` 上游路径。
10
24
 
11
25
  ## 企业品牌
@@ -36,7 +50,7 @@ ANT Host 只代取 ANT 自己管理的资源。当前 Host Connector 使用成
36
50
 
37
51
  ## 企业精选技能
38
52
 
39
- 团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md` 导入技能,核对来源、文件和依赖后发布,并为企业技能和插件指定主要用途类别。成员从 DSH 左侧“企业扩展”进入目录,可组合筛选办公文档、编程开发、设计与多媒体、数据分析、协作沟通、写作与营销、自动化与效率、运维与安全或其他类别,同时按来源范围、技能/插件类型和安装状态筛选或搜索。类别只用于发现内容;“本企业”“全市场精选”和“DSH 官方”是独立来源范围。ANT 插件设置页只处理连接、模型和更新。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
53
+ 团队管理员可在 ANT 管理台从 GitHub 目录、ZIP、TAR、TAR.GZ/TGZ 或单份 `SKILL.md` 导入技能,核对来源、文件和依赖后发布,并为企业技能和插件指定主要用途类别。成员可从 DSH 主侧栏“ANT”或 ANT Bundle 配置页直接进入团队设置;DSH“设置”中的 ANT 项提供打开 ANT 主页面的快捷入口。主侧栏“企业扩展”继续进入统一目录,可组合筛选办公文档、编程开发、设计与多媒体、数据分析、协作沟通、写作与营销、自动化与效率、运维与安全或其他类别,同时按来源范围、技能/插件类型和安装状态筛选或搜索。类别只用于发现内容;“本企业”“全市场精选”和“DSH 官方”是独立来源范围。ANT Host 将固定版本的完整文件集放进 DSH 的用户技能目录,并重新核对本机文件;启用本地技能的 DSH 会话随后由原生技能提供者发现和使用。ANT 不用 Host 全局技能列表判断会话是否已发现技能。
40
54
 
41
55
  安装不会自动运行技能脚本或安装 Python、Node 等系统依赖。ANT 只更新或卸载自己管理且本机内容未被修改的技能目录;发现同名个人技能或本机改动时会停止覆盖。
42
56
 
package/client.js CHANGED
@@ -16,9 +16,12 @@ window.__ModuleLoader__.load({
16
16
  const BRANDING_SYNC_PATH = '/api/ant-teams/branding'
17
17
  const PROVIDER_ROUTES_STORAGE_KEY = 'ant-team-provider-routes'
18
18
  const PROVIDER_SYNC_EXCLUSIONS_KEY = 'ant-team-provider-sync-exclusions'
19
+ const IMAGE_INPUT_OPT_OUTS_KEY = 'ant-team-image-input-opt-outs'
19
20
  const DEVICE_ID_STORAGE_KEY = 'ant-team-device-id'
20
21
  const DEFAULT_MODEL_INITIALIZED_KEY = 'ant-team-default-model-initialized'
21
22
  const ANT_BUNDLE_PACKAGE_NAME = '@ai-wendao/ant'
23
+ const ANT_SETTINGS_PANEL_ID = 'ant-settings'
24
+ const ANT_SETTINGS_SECTION_ID = 'ant-team'
22
25
  const ANT_API_PROTOCOL_VERSIONS = [1]
23
26
  const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
24
27
  const ANT_SETTINGS_TAB_STORAGE_KEY = 'ant-settings-tab'
@@ -259,7 +262,7 @@ window.__ModuleLoader__.load({
259
262
  }
260
263
 
261
264
  function approvedArtifactUrl(release) {
262
- const expectedPath = `/api/ant-bundles/${release?.version}/package.tgz`
265
+ const expectedPath = `/api/ant-bundles/${ANT_BUNDLE_PACKAGE_NAME === 'dsh-ant' ? 'dsh-ant/' : ''}${release?.version}/package.tgz`
263
266
  let url
264
267
  try { url = new URL(release?.artifact_url) } catch { return null }
265
268
  return normalizeServiceUrl(url.origin) === url.origin
@@ -312,6 +315,26 @@ window.__ModuleLoader__.load({
312
315
  return deviceId
313
316
  }
314
317
 
318
+ function imageInputOptOutPreferenceKey(rawServiceUrl) {
319
+ const normalized = normalizeServiceUrl(rawServiceUrl)
320
+ return normalized ? `${IMAGE_INPUT_OPT_OUTS_KEY}:${normalized}:${savedDeviceId() || ''}` : null
321
+ }
322
+
323
+ function readImageInputOptOuts(rawServiceUrl) {
324
+ const key = imageInputOptOutPreferenceKey(rawServiceUrl)
325
+ if (!key) return []
326
+ try {
327
+ const value = JSON.parse(localStorage.getItem(key) || '[]')
328
+ return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : []
329
+ } catch { return [] }
330
+ }
331
+
332
+ function saveImageInputOptOuts(rawServiceUrl, modelKeys) {
333
+ const key = imageInputOptOutPreferenceKey(rawServiceUrl)
334
+ if (!key) return
335
+ try { localStorage.setItem(key, JSON.stringify([...new Set(modelKeys)])) } catch {}
336
+ }
337
+
315
338
  function TeamServiceConfig({ remote, openInstalledPlugin }) {
316
339
  const [activeTab, setActiveTab] = useState(loadAntSettingsTab)
317
340
  const [serviceUrl, setServiceUrl] = useState(loadServiceUrl)
@@ -339,6 +362,7 @@ window.__ModuleLoader__.load({
339
362
  }
340
363
  const updateNeedsAttention = ['available', 'artifact-missing', 'revoked', 'migration-required',
341
364
  'unknown', 'failed', 'installing', 'done'].includes(updateSummary?.state)
365
+ || ['incompatible', 'unknown', 'untested'].includes(updateSummary?.dshCompatibility?.state)
342
366
  useEffect(() => {
343
367
  let disposed = false
344
368
  const revision = bindingRevision.current
@@ -465,24 +489,32 @@ window.__ModuleLoader__.load({
465
489
  updateNeedsAttention ? h('section', { role: 'status', 'aria-live': 'polite', style: {
466
490
  ...panelStyle, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap',
467
491
  gap: 12, padding: 12, borderColor: updateSummary.state === 'revoked'
492
+ || updateSummary.dshCompatibility?.state === 'incompatible'
468
493
  ? 'var(--dsw-alias-state-error-primary)' : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-border-l2))',
469
- backgroundColor: updateSummary.state === 'revoked'
494
+ backgroundColor: updateSummary.state === 'revoked' || updateSummary.dshCompatibility?.state === 'incompatible'
470
495
  ? 'var(--dsw-alias-state-error-secondary, var(--dsw-alias-bg-module-platform))'
471
496
  : 'var(--dsw-alias-state-warning-secondary, var(--dsw-alias-bg-module-platform))',
472
497
  } },
473
498
  h('div', { style: { display: 'grid', gap: 3, flex: '1 1 300px' } },
474
- h('strong', null, updateSummary.state === 'available'
475
- ? `有新版 ANT:${updateSummary.currentVersion || '当前版本未知'} → ${updateSummary.version}`
476
- : updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
477
- : updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
478
- h('span', { style: mutedTextStyle }, updateSummary.state === 'available'
479
- ? '查看“关于”中的更新说明与兼容要求;安装后需重启 DSH。'
480
- : updateSummary.state === 'done' ? '请完全退出并重新启动 DSH 后生效。'
481
- : updateSummary.state === 'revoked' ? '打开“关于”查看安全处置方式。'
482
- : updateSummary.state === 'artifact-missing' ? '新版安装包尚未审核,请联系管理员。'
483
- : '打开“关于”查看处理方式。')),
499
+ h('strong', null, updateSummary.dshCompatibility?.state === 'incompatible'
500
+ ? `当前 DSH ${updateSummary.dshCompatibility.runtime_version || ''} 与 ANT 不兼容`
501
+ : updateSummary.dshCompatibility?.state === 'unknown' ? '无法确认当前 DSH 与 ANT 的兼容性'
502
+ : updateSummary.dshCompatibility?.state === 'untested'
503
+ ? `当前 DSH ${updateSummary.dshCompatibility.runtime_version} 尚未完成 ANT 兼容验收`
504
+ : updateSummary.state === 'available'
505
+ ? `有新版 ANT:${updateSummary.currentVersion || '当前版本未知'} → ${updateSummary.version}`
506
+ : updateSummary.state === 'done' ? `ANT ${updateSummary.version} 已安装,需重启 DSH`
507
+ : updateSummary.state === 'revoked' ? '当前 ANT 安装包已撤回' : 'ANT 更新需要处理'),
508
+ h('span', { style: mutedTextStyle }, ['incompatible', 'unknown', 'untested'].includes(updateSummary.dshCompatibility?.state)
509
+ ? updateSummary.dshCompatibility.message
510
+ : updateSummary.state === 'available'
511
+ ? '查看“关于”中的更新说明与兼容要求;安装后需重启 DSH。'
512
+ : updateSummary.state === 'done' ? '请完全退出并重新启动 DSH 后生效。'
513
+ : updateSummary.state === 'revoked' ? '打开“关于”查看安全处置方式。'
514
+ : updateSummary.state === 'artifact-missing' ? '新版安装包尚未审核,请联系管理员。'
515
+ : '打开“关于”查看处理方式。')),
484
516
  h('button', { type: 'button', onClick: () => selectSettingsTab('updates'), style: primaryButtonStyle },
485
- updateSummary.state === 'available' ? '查看并更新' : '查看处理方式')) : null,
517
+ updateSummary.state === 'available' ? '查看更新与兼容性' : '查看兼容详情')) : null,
486
518
  h('nav', { role: 'tablist', 'aria-label': 'ANT 插件设置', style: {
487
519
  display: 'flex', gap: 4, overflowX: 'auto', borderBottom: '1px solid var(--dsw-alias-border-l2)',
488
520
  } },
@@ -494,7 +526,9 @@ window.__ModuleLoader__.load({
494
526
  borderRadius: '8px 8px 0 0', color: activeTab === tab ? 'var(--dsw-alias-state-business-primary)' : secondaryButtonStyle.color,
495
527
  border: '0', borderBottom: activeTab === tab ? '2px solid var(--dsw-alias-state-business-primary)' : '2px solid transparent',
496
528
  backgroundColor: 'transparent',
497
- } }, tab === 'updates' && updateNeedsAttention ? `${label} · ${updateSummary.state === 'available' ? '有新版' : '需处理'}` : label)))),
529
+ } }, tab === 'updates' && updateNeedsAttention
530
+ ? `${label} · ${['incompatible', 'unknown', 'untested'].includes(updateSummary.dshCompatibility?.state)
531
+ ? '兼容性需处理' : updateSummary.state === 'available' ? '有新版' : '需处理'}` : label)))),
498
532
  h('div', { id: 'ant-settings-content', role: 'tabpanel', 'aria-labelledby': `ant-settings-tab-${activeTab}`,
499
533
  style: { display: 'grid', gap: activeTab === 'connection' ? 12 : 0 } },
500
534
  teamServicePanel,
@@ -2362,10 +2396,18 @@ window.__ModuleLoader__.load({
2362
2396
  const [allowLocalCleanup, setAllowLocalCleanup] = useState(false)
2363
2397
  const [bundleRelease, setBundleRelease] = useState(null)
2364
2398
  const [bundleUpdate, setBundleUpdate] = useState({ state: 'idle', currentVersion: '', message: '' })
2399
+ const [dshCompatibility, setDshCompatibility] = useState({ state: 'checking', runtime_version: null,
2400
+ required_range: null, validated_baseline: null, message: '正在读取 DSH 客户端版本…' })
2401
+ const dshCompatibilityCheckRevision = useRef(0)
2365
2402
  const [authorizedModels, setAuthorizedModels] = useState([])
2366
2403
  const [providerSyncGroups, setProviderSyncGroups] = useState([])
2367
2404
  const [appliedProviderSyncExclusions, setAppliedProviderSyncExclusions] = useState([])
2368
2405
  const [providerSyncDraft, setProviderSyncDraft] = useState([])
2406
+ const [imageInputOptOuts, setImageInputOptOuts] = useState(() => ({
2407
+ serviceUrl: normalizeServiceUrl(serviceUrl),
2408
+ modelKeys: readImageInputOptOuts(serviceUrl),
2409
+ }))
2410
+ const [appliedImageInputOptOuts, setAppliedImageInputOptOuts] = useState(() => readImageInputOptOuts(serviceUrl))
2369
2411
  const [serverDefaultModel, setServerDefaultModel] = useState(null)
2370
2412
  const [dshDefaultSelection, setDshDefaultSelection] = useState(null)
2371
2413
  const [defaultModelDraft, setDefaultModelDraft] = useState('')
@@ -2376,8 +2418,54 @@ window.__ModuleLoader__.load({
2376
2418
  const [pinnedServiceUrl, setPinnedServiceUrl] = useState(null)
2377
2419
 
2378
2420
  useEffect(() => {
2379
- onUpdateStateChange?.({ ...bundleUpdate, version: bundleRelease?.version || '' })
2380
- }, [bundleUpdate, bundleRelease, onUpdateStateChange])
2421
+ onUpdateStateChange?.({ ...bundleUpdate, version: bundleRelease?.version || '', dshCompatibility })
2422
+ }, [bundleUpdate, bundleRelease, dshCompatibility, onUpdateStateChange])
2423
+
2424
+ useEffect(() => {
2425
+ const saved = readImageInputOptOuts(serviceUrl)
2426
+ setImageInputOptOuts({ serviceUrl: normalizeServiceUrl(serviceUrl), modelKeys: saved })
2427
+ setAppliedImageInputOptOuts(saved)
2428
+ }, [serviceUrl])
2429
+
2430
+ const inspectDshCompatibility = async (requiredRange = null) => {
2431
+ const revision = ++dshCompatibilityCheckRevision.current
2432
+ try {
2433
+ const response = await fetch('/api/ant-teams/dsh-compatibility', {
2434
+ cache: 'no-store',
2435
+ ...(requiredRange ? { method: 'POST', headers: { 'content-type': 'application/json' },
2436
+ body: JSON.stringify({ required_range: requiredRange }) } : {}),
2437
+ })
2438
+ const result = await readLocalJsonResponse(response)
2439
+ if (!response.ok || !['incompatible', 'range-met', 'untested', 'unknown'].includes(result.state)
2440
+ || typeof result.required_range !== 'string' || typeof result.message !== 'string'
2441
+ || (result.runtime_version !== null && typeof result.runtime_version !== 'string')) {
2442
+ throw new Error('DSH 客户端兼容状态响应无效。')
2443
+ }
2444
+ if (revision === dshCompatibilityCheckRevision.current) setDshCompatibility(result)
2445
+ return result
2446
+ } catch {
2447
+ const result = {
2448
+ state: 'unknown', runtime_version: null,
2449
+ required_range: typeof requiredRange === 'string' ? requiredRange : null, validated_baseline: null,
2450
+ message: '无法读取 DSH 客户端版本;当前不能确认 ANT 与 DSH 兼容。',
2451
+ }
2452
+ if (revision === dshCompatibilityCheckRevision.current) setDshCompatibility(result)
2453
+ return result
2454
+ }
2455
+ }
2456
+
2457
+ const markDshCompatibilityUnknown = (message) => {
2458
+ dshCompatibilityCheckRevision.current += 1
2459
+ const result = { state: 'unknown', runtime_version: null, required_range: null,
2460
+ validated_baseline: null, message }
2461
+ setDshCompatibility(result)
2462
+ return result
2463
+ }
2464
+
2465
+ useEffect(() => {
2466
+ void inspectDshCompatibility()
2467
+ return () => { dshCompatibilityCheckRevision.current += 1 }
2468
+ }, [])
2381
2469
 
2382
2470
  const connectionErrorMessage = (result, status) => {
2383
2471
  const message = result?.message || `请求失败(${status})`
@@ -2452,6 +2540,19 @@ window.__ModuleLoader__.load({
2452
2540
  const modelSelectionKey = (model) => JSON.stringify({ provider_id: model.provider_id || '', protocol: model.protocol, model_id: model.model_id })
2453
2541
  const defaultPreferenceKey = (rawServiceUrl) => `${DEFAULT_MODEL_INITIALIZED_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
2454
2542
  const providerSyncPreferenceKey = (rawServiceUrl) => `${PROVIDER_SYNC_EXCLUSIONS_KEY}:${normalizeServiceUrl(rawServiceUrl)}:${savedDeviceId() || ''}`
2543
+ const toggleModelImageInput = (model, enabled) => {
2544
+ if (model.supports_images !== true) return
2545
+ const normalized = normalizeServiceUrl(serviceUrl)
2546
+ setImageInputOptOuts((current) => {
2547
+ const currentValues = current.serviceUrl === normalized
2548
+ ? current.modelKeys : readImageInputOptOuts(normalized)
2549
+ const next = new Set(currentValues)
2550
+ const key = modelSelectionKey(model)
2551
+ if (enabled) next.delete(key)
2552
+ else next.add(key)
2553
+ return { serviceUrl: normalized, modelKeys: [...next] }
2554
+ })
2555
+ }
2455
2556
  const readDefaultPreference = (rawServiceUrl) => {
2456
2557
  try { return JSON.parse(localStorage.getItem(defaultPreferenceKey(rawServiceUrl)) || 'null') } catch { return null }
2457
2558
  }
@@ -2554,6 +2655,9 @@ window.__ModuleLoader__.load({
2554
2655
  const normalized = normalizeServiceUrl(rawServiceUrl)
2555
2656
  if (!normalized || !Array.isArray(models)) throw new Error('ANT 返回的团队模型目录格式无效。')
2556
2657
  if (typeof remote?.settings?.mutate !== 'function') throw new Error('当前 DSH 版本未提供模型设置接口。')
2658
+ const imageOptOuts = imageInputOptOuts.serviceUrl === normalized
2659
+ ? imageInputOptOuts.modelKeys : readImageInputOptOuts(normalized)
2660
+ const imageOptOutSet = new Set(imageOptOuts)
2557
2661
  const grouped = new Map()
2558
2662
  for (const model of models) {
2559
2663
  const protocol = model.protocol
@@ -2596,6 +2700,12 @@ window.__ModuleLoader__.load({
2596
2700
  const routeConfigurations = new Map()
2597
2701
  const compatibilityRoutes = []
2598
2702
  const providerGroups = []
2703
+ const dshModelConfig = (model) => ({
2704
+ id: model.model_id,
2705
+ name: model.display_name,
2706
+ ...(model.supports_images === true && !imageOptOutSet.has(modelSelectionKey(model))
2707
+ ? { input: ['text', 'image'] } : {}),
2708
+ })
2599
2709
  for (const provider of providers) {
2600
2710
  const route = routeForProvider(provider)
2601
2711
  const api = provider.protocol === 'openai'
@@ -2611,7 +2721,7 @@ window.__ModuleLoader__.load({
2611
2721
  apiKeyEnv: 'ANT_MODEL_API_KEY',
2612
2722
  api,
2613
2723
  baseURL,
2614
- models: provider.models.map((model) => ({ id: model.model_id, name: model.display_name })),
2724
+ models: provider.models.map(dshModelConfig),
2615
2725
  })
2616
2726
  providerGroups.push({
2617
2727
  key: route,
@@ -2632,7 +2742,7 @@ window.__ModuleLoader__.load({
2632
2742
  apiKeyEnv: 'ANT_MODEL_API_KEY',
2633
2743
  api: 'openai-completions',
2634
2744
  baseURL: new URL('/model-gateway/openai/v1', normalized).toString(),
2635
- models: defaultOpenaiModels.map((model) => ({ id: model.model_id, name: model.display_name })),
2745
+ models: defaultOpenaiModels.map(dshModelConfig),
2636
2746
  }
2637
2747
  for (const alias of aliases) routeConfigurations.set(alias, legacyChat)
2638
2748
  }
@@ -2729,6 +2839,9 @@ window.__ModuleLoader__.load({
2729
2839
  }
2730
2840
  const settingsWrite = await remote.settings.mutate('llm-pi-ai', ops, undefined)
2731
2841
  if (!settingsWrite.ok) throw new Error(settingsWrite.error?.message || 'DSH 无法更新团队模型设置。')
2842
+ saveImageInputOptOuts(normalized, imageOptOuts)
2843
+ setImageInputOptOuts({ serviceUrl: normalized, modelKeys: imageOptOuts })
2844
+ setAppliedImageInputOptOuts(imageOptOuts)
2732
2845
  try { localStorage.setItem(PROVIDER_ROUTES_STORAGE_KEY, JSON.stringify(routes)) } catch {}
2733
2846
  try { localStorage.setItem(providerSyncPreferenceKey(normalized), JSON.stringify(excludedGroups)) } catch {}
2734
2847
  setProviderSyncGroups(allGroups)
@@ -2843,7 +2956,10 @@ window.__ModuleLoader__.load({
2843
2956
  const validPackageName = release.package_name === ANT_BUNDLE_PACKAGE_NAME
2844
2957
  const validVersion = typeof release.version === 'string'
2845
2958
  && (release.version === '' || /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u.test(release.version))
2846
- if (release.bundle_name !== 'ant' || !validPackageName || !validVersion
2959
+ const validDshRange = release.dsh_engine_range === undefined
2960
+ || (typeof release.dsh_engine_range === 'string' && release.dsh_engine_range.length <= 128
2961
+ && (!release.dsh_engine_range || /^[0-9A-Za-z .+*|,<>=~^\-]+$/u.test(release.dsh_engine_range)))
2962
+ if (release.bundle_name !== 'ant' || !validPackageName || !validVersion || !validDshRange
2847
2963
  || typeof release.install_spec !== 'string' || typeof release.release_notes !== 'string'
2848
2964
  || (release.version && release.install_spec !== legacyInstallSpec)
2849
2965
  || (!release.version && release.install_spec !== '')
@@ -2879,13 +2995,34 @@ window.__ModuleLoader__.load({
2879
2995
  : `发布版本 ${release.version} 的安装包已因安全问题停用,请等待管理员发布修复版本。` })
2880
2996
  return
2881
2997
  }
2998
+ let compatibility = null
2999
+ if (available && release.dsh_engine_range) {
3000
+ compatibility = await inspectDshCompatibility(release.dsh_engine_range)
3001
+ if (compatibility.state === 'incompatible') {
3002
+ setBundleUpdate({ state: 'incompatible', currentVersion: installed.version,
3003
+ message: `已暂停更新:${compatibility.message}` })
3004
+ return
3005
+ }
3006
+ if (!['range-met', 'untested'].includes(compatibility.state)) {
3007
+ setBundleUpdate({ state: 'unknown', currentVersion: installed.version,
3008
+ message: `无法确认候选版本与当前 DSH 的兼容性,已暂停更新。${compatibility.message}` })
3009
+ return
3010
+ }
3011
+ } else if (available) {
3012
+ const unknown = markDshCompatibilityUnknown(
3013
+ 'ANT 服务端没有提供这份候选制品的 DSH 兼容范围,已暂停更新。请管理员发布带有 DSH 兼容声明的制品。')
3014
+ setBundleUpdate({ state: 'unknown', currentVersion: installed.version, message: unknown.message })
3015
+ return
3016
+ }
2882
3017
  const artifactUrl = approvedArtifactUrl(release)
2883
3018
  setBundleUpdate({
2884
3019
  state: available ? artifactUrl ? 'available' : 'artifact-missing' : 'current',
2885
3020
  currentVersion: installed.version,
2886
3021
  message: available
2887
3022
  ? artifactUrl
2888
- ? `发现新版本 ${release.version},可直接更新。`
3023
+ ? compatibility?.state === 'untested'
3024
+ ? `发现新版本 ${release.version}。当前 DSH 版本尚未验收,请核对上方兼容提示后再更新。`
3025
+ : `发现新版本 ${release.version},当前 DSH 在兼容范围内。`
2889
3026
  : `发现新版本 ${release.version},但管理员尚未提供已审核安装包。请联系管理员。`
2890
3027
  : `当前已是最新发布版本(${installed.version})。`,
2891
3028
  })
@@ -2896,6 +3033,12 @@ window.__ModuleLoader__.load({
2896
3033
 
2897
3034
  const installBundleUpdate = async () => {
2898
3035
  if (bundleUpdate.state !== 'available') return
3036
+ if (!bundleRelease?.dsh_engine_range) {
3037
+ markDshCompatibilityUnknown('当前 ANT 更新缺少 DSH 兼容范围,已暂停安装。请联系管理员补全发布制品信息。')
3038
+ return
3039
+ }
3040
+ const compatibility = await inspectDshCompatibility(bundleRelease.dsh_engine_range)
3041
+ if (!['range-met', 'untested'].includes(compatibility.state)) return
2899
3042
  const artifactUrl = approvedArtifactUrl(bundleRelease)
2900
3043
  if (!artifactUrl) return
2901
3044
  const manager = remote?.pluginManager
@@ -2943,6 +3086,12 @@ window.__ModuleLoader__.load({
2943
3086
  const outcome = result.value
2944
3087
  if (outcome.application === 'failed' || outcome.application === 'cancelled') {
2945
3088
  const reason = outcome.error?.diagnostic || outcome.packageResult?.output || '安装未完成。'
3089
+ if (outcome.error?.code === 'incompatible-version') {
3090
+ setDshCompatibility((current) => ({ ...current, state: 'incompatible',
3091
+ runtime_version: outcome.error?.runtimeVersion || current.runtime_version,
3092
+ message: `DSH 安装器判定当前客户端${typeof outcome.error?.runtimeVersion === 'string'
3093
+ && outcome.error.runtimeVersion ? ` ${outcome.error.runtimeVersion} ` : ''}与此 ANT 版本不兼容:${reason}` }))
3094
+ }
2946
3095
  setBundleUpdate((current) => ({ ...current, state: 'failed', message: `更新失败:${reason}` }))
2947
3096
  return
2948
3097
  }
@@ -3249,6 +3398,12 @@ window.__ModuleLoader__.load({
3249
3398
  const appliedExcludedRouteSet = new Set(providerSyncGroups.filter((group) => appliedExcludedGroupSet.has(group.key))
3250
3399
  .flatMap((group) => group.routes))
3251
3400
  const syncedModels = authorizedModels.filter((model) => !appliedExcludedRouteSet.has(modelProvider(model)))
3401
+ const currentImageInputOptOuts = imageInputOptOuts.serviceUrl === normalizeServiceUrl(serviceUrl)
3402
+ ? imageInputOptOuts.modelKeys : readImageInputOptOuts(serviceUrl)
3403
+ const imageInputOptOutSet = new Set(currentImageInputOptOuts)
3404
+ const appliedImageInputOptOutSet = new Set(appliedImageInputOptOuts)
3405
+ const hasPendingImageInputChanges = authorizedModels.some((model) => model.supports_images === true
3406
+ && imageInputOptOutSet.has(modelSelectionKey(model)) !== appliedImageInputOptOutSet.has(modelSelectionKey(model)))
3252
3407
  const hasPendingSyncChanges = [...new Set([...providerSyncDraft, ...appliedProviderSyncExclusions])]
3253
3408
  .some((key) => excludedGroupSet.has(key) !== appliedExcludedGroupSet.has(key))
3254
3409
  const activePrimaryGroupCount = providerSyncGroups.filter((group) => !group.legacy && !excludedGroupSet.has(group.key)).length
@@ -3270,16 +3425,25 @@ window.__ModuleLoader__.load({
3270
3425
  ? compactOpenaiProviderName(model.provider_name)
3271
3426
  : model.provider_name
3272
3427
  const isSynced = !excludedRouteSet.has(modelProvider(model))
3428
+ const imageInputEnabled = model.supports_images === true && !imageInputOptOutSet.has(modelSelectionKey(model))
3273
3429
  return h('div', { key: `${model.provider_id || ''}:${model.protocol}:${model.model_id}`, role: 'listitem',
3274
- style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(130px, auto)',
3430
+ style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(120px, auto) minmax(190px, auto)',
3275
3431
  gap: 12, alignItems: 'center', padding: '10px 2px', borderBottom: '1px solid var(--dsw-alias-border-l2)' } },
3276
3432
  h('strong', { style: { minWidth: 0, overflowWrap: 'anywhere' } }, `${providerName ? `${providerName} · ` : ''}${model.display_name || model.model_id}`),
3277
3433
  h('span', { style: { display: 'inline-flex', justifySelf: 'start', padding: '3px 8px', borderRadius: 999,
3278
3434
  backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-secondary)', fontSize: 12 } },
3279
3435
  model.protocol === 'anthropic' ? 'Anthropic Messages' : `OpenAI · ${model.openai_api_mode === 'responses' ? 'Responses' : 'Chat Completions'}`),
3280
- h('span', { style: { ...mutedTextStyle, textAlign: 'right' } }, hasPendingSyncChanges
3281
- ? isSynced ? '刷新后同步到 DSH' : '刷新后从 DSH 移除'
3282
- : isSynced ? '已同步到 DSH' : '未同步到 DSH'))
3436
+ h('div', { style: { display: 'grid', justifyItems: 'end', gap: 5 } },
3437
+ h('span', { style: mutedTextStyle }, hasPendingSyncChanges
3438
+ ? isSynced ? '刷新后同步到 DSH' : '刷新后从 DSH 移除'
3439
+ : isSynced ? '已同步到 DSH' : '未同步到 DSH'),
3440
+ model.supports_images === true
3441
+ ? h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: busy ? 'default' : 'pointer' } },
3442
+ h('input', { type: 'checkbox', 'aria-label': `允许 ${model.display_name || model.model_id} 在本机 DSH 接收图片`,
3443
+ checked: imageInputEnabled, disabled: busy,
3444
+ onChange: (event) => toggleModelImageInput(model, event.target.checked) }),
3445
+ h('span', { style: mutedTextStyle }, imageInputEnabled ? '本机允许图片输入' : '本机已关闭图片输入'))
3446
+ : h('span', { style: mutedTextStyle }, '团队未开放图片输入')))
3283
3447
  })
3284
3448
  const legacyCompatibilityGroup = providerSyncGroups.find((group) => group.legacy)
3285
3449
  const legacyCompatibilityModels = legacyCompatibilityGroup
@@ -3360,6 +3524,7 @@ window.__ModuleLoader__.load({
3360
3524
  h('div', null,
3361
3525
  h('h3', { style: sectionTitleStyle }, '团队模型'),
3362
3526
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '每个获准模型单独列出;刷新后同步到 DSH 模型选择器。'),
3527
+ h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '图片输入默认继承管理员设置;可为本机关闭单个模型的图片输入,不影响团队其他成员。刷新团队模型后生效。'),
3363
3528
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '协议与 API 模式按团队模型配置。DSH 的本机设置只影响当前 Profile,不会修改服务器;刷新团队模型会重新同步各模型路由。')),
3364
3529
  h('span', { style: { flexShrink: 0, padding: '4px 9px', borderRadius: 999,
3365
3530
  color: credentialConfigured === true && bindingReady ? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-secondary)',
@@ -3413,6 +3578,8 @@ window.__ModuleLoader__.load({
3413
3578
  onChange: (event) => toggleProviderSyncGroup(group, event.target.checked) }),
3414
3579
  h('span', null, `${label} · ${group.modelCount} 个模型`))
3415
3580
  })) : null,
3581
+ hasPendingImageInputChanges ? h('p', { role: 'status', style: mutedTextStyle },
3582
+ '本机图片输入偏好尚未同步;点击“刷新团队模型”后生效。') : null,
3416
3583
  h('div', { role: 'list', 'aria-label': '获准模型', style: { display: 'grid', gap: 0, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
3417
3584
  modelRows.length ? modelRows : h('div', { style: { display: 'grid', gap: 8, padding: '12px 0' } },
3418
3585
  h('p', { style: mutedTextStyle }, credentialConfigured === true && modelMessage.startsWith('正在') ? modelMessage
@@ -3433,10 +3600,14 @@ window.__ModuleLoader__.load({
3433
3600
 
3434
3601
  const updateStatusLabels = {
3435
3602
  available: '有新版', 'artifact-missing': '等待发布制品', revoked: '安装包已撤回',
3436
- 'migration-required': '需要迁移', unknown: '状态待确认', failed: '检查失败',
3603
+ 'migration-required': '需要迁移', incompatible: 'DSH 不兼容', unknown: '状态待确认', failed: '检查失败',
3437
3604
  installing: '正在安装', done: '需重启 DSH', current: '已是最新', checking: '正在检查', none: '尚未发布',
3438
3605
  }
3439
- const updateStatusNeedsDetails = ['artifact-missing', 'revoked', 'migration-required', 'unknown', 'failed', 'installing', 'done']
3606
+ const dshCompatibilityLabels = {
3607
+ checking: '正在检查 DSH', incompatible: 'DSH 不兼容', 'range-met': '在声明范围内',
3608
+ untested: '版本尚未验收', unknown: 'DSH 版本未知',
3609
+ }
3610
+ const updateStatusNeedsDetails = ['artifact-missing', 'revoked', 'migration-required', 'incompatible', 'unknown', 'failed', 'installing', 'done']
3440
3611
  const updateVersionSummary = bundleUpdate.state === 'available'
3441
3612
  ? `可更新至 ${bundleRelease?.version || '最新版本'}`
3442
3613
  : updateStatusNeedsDetails.includes(bundleUpdate.state) ? '' : bundleUpdate.currentVersion && bundleRelease?.version
@@ -3447,6 +3618,16 @@ window.__ModuleLoader__.load({
3447
3618
  : bundleRelease?.version ? `最新版本 ${bundleRelease.version}` : ''
3448
3619
  const showReleaseNotes = canCheckUpdate && bundleRelease?.version && bundleRelease.release_notes
3449
3620
  && ['current', 'available', 'artifact-missing'].includes(bundleUpdate.state)
3621
+ const dshCompatibilityCard = h('section', { role: 'status', style: {
3622
+ ...feedbackNoticeStyle,
3623
+ color: dshCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)'
3624
+ : dshCompatibility.state === 'range-met' ? 'var(--dsw-alias-state-success-primary)'
3625
+ : 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))',
3626
+ } },
3627
+ h('strong', null, `DSH 客户端兼容性:${dshCompatibilityLabels[dshCompatibility.state] || '状态未知'}`),
3628
+ h('p', { style: mutedTextStyle }, dshCompatibility.message),
3629
+ dshCompatibility.required_range ? h('p', { style: mutedTextStyle },
3630
+ `ANT 要求 ${dshCompatibility.required_range};已记录基线 ${dshCompatibility.validated_baseline || '未知'}。`) : null)
3450
3631
  const updatePanel = h('section', { style: { ...settingsPanelStyle, display: activeTab === 'updates' ? 'grid' : 'none' } },
3451
3632
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 } },
3452
3633
  h('div', null,
@@ -3454,9 +3635,11 @@ window.__ModuleLoader__.load({
3454
3635
  h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '查看版本、更新说明和兼容状态。')),
3455
3636
  h('span', { style: { ...mutedTextStyle, flexShrink: 0,
3456
3637
  color: bundleUpdate.state === 'revoked' || serviceCompatibility.state === 'incompatible'
3638
+ || dshCompatibility.state === 'incompatible'
3457
3639
  ? 'var(--dsw-alias-state-error-primary)' : mutedTextStyle.color } },
3458
3640
  updateStatusLabels[bundleUpdate.state] || '等待检查')),
3459
3641
  updateVersionSummary ? h('p', { style: { ...mutedTextStyle, color: 'var(--dsw-alias-label-primary)', fontWeight: 550 } }, updateVersionSummary) : null,
3642
+ dshCompatibilityCard,
3460
3643
  showReleaseNotes ? h('section', { style: { ...infoNoticeStyle, padding: 12 } },
3461
3644
  h('strong', { style: { fontSize: 14, lineHeight: 1.4 } }, '本版更新'),
3462
3645
  h('p', { style: mutedTextStyle }, bundleRelease.release_notes)) : null,
@@ -3473,7 +3656,9 @@ window.__ModuleLoader__.load({
3473
3656
  !canCheckUpdate ? h('button', { type: 'button', onClick: () => onSelectTab('connection'), style: secondaryButtonStyle },
3474
3657
  '前往团队连接') : null,
3475
3658
  canCheckUpdate && bundleUpdate.state === 'available'
3476
- ? h('button', { type: 'button', disabled: busy, onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null,
3659
+ ? h('button', { type: 'button', disabled: busy || !bundleRelease?.dsh_engine_range
3660
+ || !['range-met', 'untested'].includes(dshCompatibility.state),
3661
+ onClick: () => void installBundleUpdate(), style: primaryButtonStyle }, busy ? '更新中…' : '更新 ANT') : null,
3477
3662
  h('button', { type: 'button', disabled: busy || !canCheckUpdate || bundleUpdate.state === 'checking',
3478
3663
  onClick: () => void checkBundleUpdate(), style: secondaryButtonStyle }, bundleUpdate.state === 'checking' ? '正在检查…' : '检查更新'))
3479
3664
 
@@ -3489,6 +3674,13 @@ window.__ModuleLoader__.load({
3489
3674
  } }, '✦')
3490
3675
  }
3491
3676
 
3677
+ function AntSettingsPanelIcon({ size }) {
3678
+ return h('span', { 'aria-hidden': true, style: {
3679
+ display: 'inline-grid', placeItems: 'center', width: size, height: size,
3680
+ color: 'var(--dsw-alias-brand-primary)', fontSize: size * 0.82, fontWeight: 700, lineHeight: 1,
3681
+ } }, 'A')
3682
+ }
3683
+
3492
3684
  function EnterpriseExtensionsPage({ remote, onBack, openBundle }) {
3493
3685
  const [tab, setTab] = useState('all')
3494
3686
  const [query, setQuery] = useState('')
@@ -3698,15 +3890,41 @@ window.__ModuleLoader__.load({
3698
3890
  settings: ctx.remote.settings,
3699
3891
  pluginManager: ctx.remote.pluginManager,
3700
3892
  }
3893
+ const openAntSettings = () => ctx.layout.selectPanel(ANT_SETTINGS_PANEL_ID)
3894
+ const renderAntSettings = () => h(TeamServiceConfig, {
3895
+ remote,
3896
+ openInstalledPlugin: (name) => ctx.pluginNavigation.openBundle(name),
3897
+ })
3898
+ const renderAntPanel = () => h('section', { 'aria-label': 'ANT 团队设置', style: {
3899
+ height: '100%', overflowY: 'auto', boxSizing: 'border-box',
3900
+ padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
3901
+ } }, h('div', { style: { maxWidth: 1120, margin: '0 auto' } }, renderAntSettings()))
3902
+ const renderAntSettingsShortcut = ({ close } = {}) => h('section', { style: {
3903
+ display: 'grid', gap: 10, width: '100%', maxWidth: 620,
3904
+ } },
3905
+ h('h2', { style: sectionTitleStyle }, 'ANT 团队设置'),
3906
+ h('p', { style: mutedTextStyle }, '团队连接、模型、协作与本机任务在左侧 ANT 页面中统一管理。'),
3907
+ h('button', { type: 'button', onClick: () => {
3908
+ close?.()
3909
+ openAntSettings()
3910
+ }, style: primaryButtonStyle }, '打开 ANT 设置'))
3911
+ ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: ANT_SETTINGS_PANEL_ID }, renderAntPanel))
3912
+ ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
3913
+ name: 'sidebar.panellist', id: ANT_SETTINGS_PANEL_ID, order: 4, label: 'ANT',
3914
+ }, AntSettingsPanelIcon))
3701
3915
  ctx.slots.inject('main', () => ctx.slots.register({ name: 'main', key: 'ant-skills' },
3702
3916
  () => h(EnterpriseExtensionsPage, {
3703
3917
  remote,
3704
- onBack: () => ctx.pluginNavigation.openBundle(ANT_BUNDLE_PACKAGE_NAME),
3918
+ onBack: openAntSettings,
3705
3919
  openBundle: (name) => ctx.pluginNavigation.openBundle(name),
3706
3920
  })))
3707
3921
  ctx.slots.inject('sidebar.panellist', () => ctx.slots.register({
3708
3922
  name: 'sidebar.panellist', id: 'ant-skills', order: 5, label: '企业扩展',
3709
3923
  }, EnterpriseExtensionsPanelIcon))
3924
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
3925
+ name: 'settings.section', id: ANT_SETTINGS_SECTION_ID, order: 40,
3926
+ label: `ANT (${ANT_BUNDLE_PACKAGE_NAME})`,
3927
+ }, renderAntSettingsShortcut))
3710
3928
  ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
3711
3929
  name: 'plugins.bundle.config',
3712
3930
  key: ANT_BUNDLE_PACKAGE_NAME,
package/conversation.js CHANGED
@@ -258,25 +258,76 @@ function registerConversationTool(agent, sessionId, name, description, parameter
258
258
  })
259
259
  }
260
260
 
261
- async function prompt(ctx, sessionId, content, signal) {
261
+ export async function promptAndCollectSessionTurn(ctx, sessionId, signal, timeoutMs,
262
+ timeoutMessage, submitPrompt, abortMessage = null) {
262
263
  signal.throwIfAborted()
263
- const before = ctx.sessions.get(sessionId)?.snapshotEvents?.().length || 0
264
- await ctx.sessionController.prompt({ sessionId, requestId: randomUUID(), mode: 'queue',
265
- content: [{ type: 'text', text: content }] }, signal)
266
- const deadline = Date.now() + MODEL_TIMEOUT_MS
267
- while (Date.now() < deadline) {
268
- signal.throwIfAborted()
269
- const events = ctx.sessions.get(sessionId)?.snapshotEvents?.().slice(before) || []
270
- const ended = events.findLast((event) => event.type === 'turn/end')
271
- if (ended) {
272
- if (ended.data?.reason?.kind !== 'completed') throw new Error('DSH 未完成这次对话。')
273
- const result = assistantText(events)
274
- if (!result) throw new Error('DSH 没有返回内容。')
275
- return result
264
+ const agent = ctx.agents?.get?.(sessionId)
265
+ const session = agent?.session
266
+ if (session?.id !== sessionId || typeof agent?.ctx?.on !== 'function') {
267
+ throw new Error('当前 DSH 未提供 ANT 协作会话所需的本机会话事件接口。请查看 ANT“关于”中的 DSH 兼容状态并更新客户端。')
268
+ }
269
+
270
+ const requestId = randomUUID()
271
+ const events = []
272
+ let inputAccepted = false
273
+ let settled = false
274
+ let resolveTurn
275
+ let rejectTurn
276
+ const turn = new Promise((resolve, reject) => {
277
+ resolveTurn = resolve
278
+ rejectTurn = reject
279
+ })
280
+ void turn.catch(() => {})
281
+
282
+ const resolve = (value) => {
283
+ if (settled) return
284
+ settled = true
285
+ resolveTurn(value)
286
+ }
287
+ const reject = (error) => {
288
+ if (settled) return
289
+ settled = true
290
+ rejectTurn(error)
291
+ }
292
+ const onSessionEvent = (observedSession, event) => {
293
+ if (observedSession !== session) return
294
+ if (!inputAccepted) {
295
+ if (event.type === 'user/message' && event.data?.source?.rpcId === requestId) {
296
+ inputAccepted = true
297
+ events.push(event)
298
+ }
299
+ return
276
300
  }
277
- await new Promise((resolve) => setTimeout(resolve, 500))
301
+ events.push(event)
302
+ if (event.type === 'turn/end') resolve(events)
303
+ }
304
+
305
+ const dispose = agent.ctx.on('session/event', onSessionEvent)
306
+ const onAbort = () => reject(abortMessage ? new Error(abortMessage)
307
+ : signal.reason ?? new Error('DSH 对话已取消。'))
308
+ let timeout
309
+ signal.addEventListener('abort', onAbort, { once: true })
310
+ try {
311
+ signal.throwIfAborted()
312
+ await submitPrompt(requestId)
313
+ if (!settled) timeout = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)
314
+ return await turn
315
+ } finally {
316
+ clearTimeout(timeout)
317
+ signal.removeEventListener('abort', onAbort)
318
+ if (typeof dispose === 'function') dispose()
278
319
  }
279
- throw new Error('DSH 对话等待超时。')
320
+ }
321
+
322
+ async function prompt(ctx, sessionId, content, signal) {
323
+ const events = await promptAndCollectSessionTurn(ctx, sessionId, signal, MODEL_TIMEOUT_MS,
324
+ 'DSH 对话等待超时。', (requestId) => ctx.sessionController.prompt({ sessionId, requestId, mode: 'queue',
325
+ content: [{ type: 'text', text: content }] }, signal))
326
+ const ended = events.findLast((event) => event.type === 'turn/end')
327
+ if (ended?.data?.reason?.kind !== 'completed') throw new Error('DSH 未完成这次对话。')
328
+ const result = assistantText(events)
329
+ if (!result) throw new Error('DSH 没有返回内容。')
330
+ return result
280
331
  }
281
332
 
282
333
  export function teamModelProvider(model) {
package/delegation.js CHANGED
@@ -5,7 +5,8 @@ import { dirname, join, resolve } from 'node:path'
5
5
  import { spawn } from 'node:child_process'
6
6
  import { CONVERSATION_TOOL_NAMES, TASK_DRAFT_TTL_MS, cancelSession, conversationKey,
7
7
  buildTaskContext, cardReviewers, createConversationEngine, giteaPullRequestContext, isHelpRequest,
8
- modelFor, requestsPrivateReply, teamModelProvider, truncateTaskOutput } from './conversation.js'
8
+ modelFor, requestsPrivateReply, teamModelProvider, truncateTaskOutput,
9
+ promptAndCollectSessionTurn } from './conversation.js'
9
10
 
10
11
  const MAX_DIFF_BYTES = 240_000
11
12
  const MAX_GITEA_JSON_BYTES = 64_000
@@ -949,35 +950,24 @@ export async function runDshPrReview(ctx, grant, material, model, sessionIds, si
949
950
  agent.ctx.tools.restrict({ allow: [] })
950
951
  agent.ctx.tools.guard(() => 'ANT 委托任务不能调用 DSH 工具。')
951
952
  await ctx.sessionController.selectModel({ sessionId, provider: teamModelProvider(model), model: model.model_id })
952
- await ctx.sessionController.prompt({ sessionId, requestId: randomUUID(), mode: 'queue',
953
- content: [{ type: 'text', text: buildTaskContext(task || legacyPrReviewTask(grant), {
953
+ const events = await promptAndCollectSessionTurn(ctx, sessionId, signal, MODEL_TIMEOUT_MS,
954
+ 'DSH 任务等待超时。', (requestId) => ctx.sessionController.prompt({ sessionId, requestId, mode: 'queue',
955
+ content: [{ type: 'text', text: buildTaskContext(task || legacyPrReviewTask(grant), {
954
956
  parameters: task?.parameters || {},
955
957
  resources: [giteaPullRequestContext(grant, material, false)],
956
958
  runtime: { filesystem: '不可用', git: '不可用', network: 'disabled', tools: [] },
957
- }) }] }, signal)
958
- const deadline = Date.now() + MODEL_TIMEOUT_MS
959
- while (Date.now() < deadline) {
960
- if (signal.aborted) throw new Error('任务已取消。')
961
- const session = ctx.sessions.get(sessionId)
962
- const events = session?.snapshotEvents?.() || []
963
- const ended = events.findLast((event) => event.type === 'turn/end')
964
- if (ended) {
965
- if (ended.data?.reason?.kind !== 'completed') {
966
- throw new Error('DSH 未完成这项任务。')
967
- }
968
- const response = events.filter((event) => event.type === 'assistant/message')
969
- .flatMap((event) => event.data?.message?.content || [])
970
- .filter((block) => block.type === 'text')
971
- .map((block) => block.text)
972
- .join('\n').trim()
973
- if (!response) throw new Error('DSH 没有返回可展示的任务结果。')
974
- completed = true
975
- const heading = task?.card?.title || `PR #${grant.pr_number} 资源任务`
976
- return truncateTaskOutput(`${heading}(${material.sha.slice(0, 8)})\n${response}`, MAX_REPLY_LENGTH)
977
- }
978
- await new Promise((resolve) => setTimeout(resolve, 500))
979
- }
980
- throw new Error('DSH 任务等待超时。')
959
+ }) }] }, signal), '任务已取消。')
960
+ const ended = events.findLast((event) => event.type === 'turn/end')
961
+ if (ended?.data?.reason?.kind !== 'completed') throw new Error('DSH 未完成这项任务。')
962
+ const response = events.filter((event) => event.type === 'assistant/message')
963
+ .flatMap((event) => event.data?.message?.content || [])
964
+ .filter((block) => block.type === 'text')
965
+ .map((block) => block.text)
966
+ .join('\n').trim()
967
+ if (!response) throw new Error('DSH 没有返回可展示的任务结果。')
968
+ completed = true
969
+ const heading = task?.card?.title || `PR #${grant.pr_number} 资源任务`
970
+ return truncateTaskOutput(`${heading}(${material.sha.slice(0, 8)})\n${response}`, MAX_REPLY_LENGTH)
981
971
  } finally {
982
972
  if (!completed) await cancelSession(ctx, sessionId)
983
973
  sessionIds.delete(sessionId)
package/index.js CHANGED
@@ -11,6 +11,7 @@ export const inject = []
11
11
  const MODEL_SYNC_PATH = '/api/ant-teams/models'
12
12
  const BRANDING_PATH = '/api/ant-teams/branding'
13
13
  const SERVICE_INFO_PATH = '/api/ant-teams/service-info'
14
+ const DSH_COMPATIBILITY_PATH = '/api/ant-teams/dsh-compatibility'
14
15
  const BUNDLE_RELEASE_PATH = '/api/ant-teams/bundle-release'
15
16
  const CONNECTION_ORIGIN_PATH = '/api/ant-teams/connection-origin'
16
17
  const DISCONNECT_PATH = '/api/ant-teams/disconnect'
@@ -34,7 +35,10 @@ const DELEGATION_EXECUTORS_PATH = '/api/ant-teams/delegation/executors'
34
35
  const DELEGATION_LOCAL_SETTINGS_PATH = '/api/ant-teams/delegation/local-settings'
35
36
  const DELEGATION_CANCEL_PATH = '/api/ant-teams/delegation/cancel'
36
37
  const ANT_NPM_PACKAGE_NAME = '@ai-wendao/ant'
37
- const BUNDLE_VERSION = createRequire(import.meta.url)('./package.json').version
38
+ const BUNDLE_MANIFEST = createRequire(import.meta.url)('./package.json')
39
+ const BUNDLE_VERSION = BUNDLE_MANIFEST.version
40
+ const DSH_ENGINE_RANGE = BUNDLE_MANIFEST.engines?.dsh
41
+ const DSH_VALIDATED_BASELINE = '0.2.1-rc.3'
38
42
  const MAX_MODEL_RESPONSE_BYTES = 1024 * 1024
39
43
  const MODEL_TOKEN_REF = 'ANT_MODEL_API_KEY'
40
44
  const SERVICE_RECORD_KEY = 'ant/team-service'
@@ -48,6 +52,71 @@ function jsonResponse(status, value) {
48
52
  })
49
53
  }
50
54
 
55
+ export async function readDshCompatibility(requiredRange = DSH_ENGINE_RANGE,
56
+ loadAppBoot = () => import('@deepseek-ai/dsh-app-boot')) {
57
+ try {
58
+ const appBoot = await loadAppBoot()
59
+ if (typeof appBoot.getDshRuntimeVersion !== 'function'
60
+ || typeof appBoot.evaluatePluginCompatibility !== 'function'
61
+ || typeof requiredRange !== 'string' || !requiredRange) throw new Error('DSH 兼容检查接口不可用。')
62
+ const runtimeVersion = appBoot.getDshRuntimeVersion()
63
+ if (typeof runtimeVersion !== 'string' || !runtimeVersion || runtimeVersion.length > 64) {
64
+ throw new Error('DSH 客户端版本无效。')
65
+ }
66
+ const candidateManifest = {
67
+ ...BUNDLE_MANIFEST,
68
+ engines: { ...BUNDLE_MANIFEST.engines, dsh: requiredRange },
69
+ }
70
+ const issue = appBoot.evaluatePluginCompatibility(candidateManifest, {}, runtimeVersion)
71
+ if (issue) {
72
+ return {
73
+ state: 'incompatible', runtime_version: runtimeVersion,
74
+ required_range: requiredRange, validated_baseline: DSH_VALIDATED_BASELINE,
75
+ message: `当前 DSH ${runtimeVersion} 不满足 ANT 要求 ${requiredRange}。请先更新 DSH,再安装或启用 ANT。`,
76
+ }
77
+ }
78
+ if (runtimeVersion === DSH_VALIDATED_BASELINE) {
79
+ return {
80
+ state: 'range-met', runtime_version: runtimeVersion,
81
+ required_range: requiredRange, validated_baseline: DSH_VALIDATED_BASELINE,
82
+ message: `当前 DSH ${runtimeVersion} 满足 ANT 声明范围;本次 Session 事件改动尚未做真机验收。`,
83
+ }
84
+ }
85
+ return {
86
+ state: 'untested', runtime_version: runtimeVersion,
87
+ required_range: requiredRange, validated_baseline: DSH_VALIDATED_BASELINE,
88
+ message: `当前 DSH ${runtimeVersion} 满足 ANT 声明范围 ${requiredRange},但不是项目已记录的验收基线 ${DSH_VALIDATED_BASELINE}。`,
89
+ }
90
+ } catch {
91
+ return {
92
+ state: 'unknown', runtime_version: null,
93
+ required_range: typeof requiredRange === 'string' ? requiredRange : null,
94
+ validated_baseline: DSH_VALIDATED_BASELINE,
95
+ message: '无法读取 DSH 客户端版本;当前不能确认 ANT 与 DSH 兼容。',
96
+ }
97
+ }
98
+ }
99
+
100
+ async function fetchDshCompatibility(request) {
101
+ let requiredRange = DSH_ENGINE_RANGE
102
+ if (request.method === 'POST') {
103
+ let body
104
+ try {
105
+ const text = await request.text()
106
+ if (text.length > 4096) return jsonResponse(413, { message: 'DSH 兼容检查请求过大。' })
107
+ body = JSON.parse(text)
108
+ } catch {
109
+ return jsonResponse(400, { message: 'DSH 兼容检查请求格式无效。' })
110
+ }
111
+ requiredRange = body?.required_range
112
+ if (typeof requiredRange !== 'string' || requiredRange.length > 128
113
+ || !/^[0-9A-Za-z .+*|,<>=~^\-]+$/u.test(requiredRange)) {
114
+ return jsonResponse(400, { message: 'ANT 制品的 DSH 兼容范围无效。' })
115
+ }
116
+ }
117
+ return jsonResponse(200, await readDshCompatibility(requiredRange))
118
+ }
119
+
51
120
  function modelProviderConfigHeader(request) {
52
121
  const version = request.headers.get('x-ant-model-provider-config')
53
122
  return version === '1' || version === '2' ? { 'x-ant-model-provider-config': version } : {}
@@ -130,7 +199,10 @@ async function readMemberConnection(credentials, settings) {
130
199
 
131
200
  async function updateModelSession(serviceUrl, token, action) {
132
201
  const headers = { authorization: `Bearer ${token}`, accept: 'application/json' }
133
- if (action === 'heartbeat') headers['x-ant-bundle-version'] = BUNDLE_VERSION
202
+ if (action === 'heartbeat') {
203
+ headers['x-ant-bundle-version'] = BUNDLE_VERSION
204
+ headers['x-ant-bundle-package'] = ANT_NPM_PACKAGE_NAME
205
+ }
134
206
  return fetch(new URL(`/auth/dsh-session/${action}`, serviceUrl), {
135
207
  method: 'POST',
136
208
  headers,
@@ -649,7 +721,9 @@ async function fetchBundleRelease(request, credentials, settings) {
649
721
  if (request.headers.get('x-ant-bundle-update-mode') === 'check-only') {
650
722
  headers['x-ant-bundle-update-mode'] = 'check-only'
651
723
  }
652
- const upstream = await fetch(new URL('/api/me/bundle-release', connection.serviceUrl), {
724
+ const releaseUrl = new URL('/api/me/bundle-release', connection.serviceUrl)
725
+ if (ANT_NPM_PACKAGE_NAME === 'dsh-ant') releaseUrl.searchParams.set('package_name', ANT_NPM_PACKAGE_NAME)
726
+ const upstream = await fetch(releaseUrl, {
653
727
  headers,
654
728
  redirect: 'manual',
655
729
  signal: AbortSignal.timeout(10_000),
@@ -666,17 +740,20 @@ async function fetchBundleRelease(request, credentials, settings) {
666
740
  const validPackageName = release?.package_name === ANT_NPM_PACKAGE_NAME
667
741
  const validVersion = typeof release?.version === 'string'
668
742
  && (release.version === '' || /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/u.test(release.version))
743
+ const validDshRange = release?.dsh_engine_range === undefined
744
+ || (typeof release.dsh_engine_range === 'string' && release.dsh_engine_range.length <= 128
745
+ && (!release.dsh_engine_range || /^[0-9A-Za-z .+*|,<>=~^\-]+$/u.test(release.dsh_engine_range)))
669
746
  const installSpec = release?.package_name === 'ant'
670
747
  ? `ant@${release?.version}`
671
748
  : `ant@npm:${release?.package_name}@${release?.version}`
672
749
  const directInstallSpec = release?.package_name && release?.version
673
750
  ? `${release.package_name}@${release.version}`
674
751
  : ''
675
- const artifactPath = release?.version ? `/api/ant-bundles/${release.version}/package.tgz` : ''
752
+ const artifactPath = release?.version ? `/api/ant-bundles/${ANT_NPM_PACKAGE_NAME === 'dsh-ant' ? 'dsh-ant/' : ''}${release.version}/package.tgz` : ''
676
753
  const hasArtifact = release?.artifact_path === artifactPath
677
754
  && /^[a-f0-9]{64}$/u.test(release?.artifact_sha256 || '')
678
755
  const noArtifact = !release?.artifact_path && !release?.artifact_sha256
679
- if (release?.bundle_name !== 'ant' || !validPackageName || !validVersion
756
+ if (release?.bundle_name !== 'ant' || !validPackageName || !validVersion || !validDshRange
680
757
  || typeof release?.install_spec !== 'string' || typeof release?.release_notes !== 'string'
681
758
  || (release.version && release.install_spec !== installSpec)
682
759
  || (!release.version && release.install_spec !== '')
@@ -1001,6 +1078,12 @@ export function apply(ctx) {
1001
1078
  requestBody: 'buffered',
1002
1079
  fetch: (request) => fetchServiceInfo(request, connectionCtx.credentials, connectionCtx.settings),
1003
1080
  }), 'ant-teams: ANT client-server version handshake')
1081
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
1082
+ path: DSH_COMPATIBILITY_PATH,
1083
+ methods: ['GET', 'POST'],
1084
+ requestBody: 'buffered',
1085
+ fetch: fetchDshCompatibility,
1086
+ }), 'ant-teams: DSH runtime compatibility check')
1004
1087
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
1005
1088
  path: MODEL_SYNC_PATH,
1006
1089
  methods: ['POST'],
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.5.11",
3
+ "version": "0.5.12",
4
4
  "description": "ANT (AI-native Teams) connects a DeepSeek Harness Desktop profile to team identity and approved models.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "dsh": ">=0.2.1-rc.3"
8
+ },
9
+ "peerDependencies": {
10
+ "@deepseek-ai/dsh-app-boot": ">=0.2.1-rc.3"
11
+ },
6
12
  "scripts": {
7
13
  "test": "node --test client.test.js delegation.test.js index.test.js skill-install.test.js tools/migrate-legacy.test.mjs"
8
14
  },
@@ -26,7 +32,8 @@
26
32
  "inject": [
27
33
  "@deepseek-ai/dsh-api-remotes",
28
34
  "@deepseek-ai/dsh-client-ui-layout",
29
- "@deepseek-ai/dsh-client-ui-plugin-manager"
35
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
36
+ "@deepseek-ai/dsh-client-ui-settings"
30
37
  ]
31
38
  }
32
39
  }