@ai-wendao/ant 0.3.16 → 0.3.17

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/client.js CHANGED
@@ -11,6 +11,91 @@ window.__ModuleLoader__.load({
11
11
  const ANT_API_PROTOCOL_VERSIONS = [1]
12
12
  const PLUGIN_INSTALL_RECORD_PREFIX = 'ant-reviewed-plugin-install:'
13
13
  const fileCountFormat = new Intl.NumberFormat('zh-CN')
14
+ const marketSkillCategories = {
15
+ 'ant-ppt-master': '演示', 'ant-frontend-slides': '演示', 'ant-slidev': '演示',
16
+ 'ant-word-docx': '文档', 'ant-excel-xlsx': '表格', 'ant-pdf': 'PDF',
17
+ 'ant-internal-comms': '写作', 'ant-formatted-report-writer': '写作',
18
+ 'ant-agent-browser': '开发', 'ant-vercel-react-best-practices': '开发',
19
+ 'ant-vercel-composition-patterns': '开发', 'ant-supabase-postgres-best-practices': '开发',
20
+ 'ant-diagnosing-bugs': '开发', 'ant-improve-codebase-architecture': '开发',
21
+ 'ant-code-review': '开发', 'ant-systematic-debugging': '开发',
22
+ 'ant-test-driven-development': '开发', 'ant-verification-before-completion': '开发',
23
+ 'ant-writing-plans': '开发', 'ant-to-spec': '开发', 'ant-skill-creator': '开发',
24
+ 'ant-frontend-design': '设计', 'ant-web-design-guidelines': '设计',
25
+ 'ant-ui-ux-pro-max': '设计', 'ant-emil-design-eng': '设计',
26
+ 'ant-review-animations': '设计', 'ant-shadcn': '设计',
27
+ 'ant-theme-factory': '设计', 'ant-brand-guidelines': '设计',
28
+ 'ant-copywriting': '写作', 'ant-seo-audit': '营销', 'ant-brainstorming': '策划',
29
+ }
30
+ const officialOfficeGuide = 'https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/packages/skill/skill-office/README.zh.md'
31
+ const officialOfficeEnglish = (skill) => `https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v${skill.since}/packages/skill/skill-office/assets/${skill.name}/SKILL.md`
32
+ const officialOfficeSkills = [
33
+ { name: 'office', title: 'Office 办公入口', category: '办公', since: '0.2.1-rc.2', description: '根据任务选择 Word、PPT、Excel 或文件处理能力。', prompt: '帮我完成这份办公文件' },
34
+ { name: 'office-docx', title: 'Word 文档', category: '文档', since: '0.2.0-rc.2', description: '创建、修改和检查 DOCX 文档。', prompt: '帮我写一份 Word 报告' },
35
+ { name: 'office-pptx', title: 'PPT 演示', category: '演示', since: '0.2.0-rc.2', description: '创建、修改和检查 PPTX 演示文稿。', prompt: '帮我做一份项目汇报 PPT' },
36
+ { name: 'office-xlsx', title: 'Excel 表格', category: '表格', since: '0.2.0-rc.2', description: '创建、修改和检查 XLSX 工作簿。', prompt: '帮我整理这份 Excel 表格' },
37
+ { name: 'office-render', title: '文件预览', category: '办公', since: '0.2.1-rc.2', description: '把 Office 文件渲染成图片,检查实际外观。', prompt: '请预览这份文档并检查排版' },
38
+ { name: 'office-recalculate', title: '公式重算', category: '表格', since: '0.2.1-rc.2', description: '重新计算表格公式并核对结果。', prompt: '请重新计算这份表格的公式' },
39
+ { name: 'office-export-pdf', title: '导出 PDF', category: 'PDF', since: '0.2.1-rc.2', description: '将 Office 文件导出为 PDF。', prompt: '把这份 Word 文档导出成 PDF' },
40
+ ]
41
+ const curatedSkillIntroductions = {
42
+ "ant-ppt-master": {"ref": "c350bc31e6d43b7bcf5234183d650ba8f9f4c9d7", "description": "制作和编辑 PowerPoint 演示文稿。"},
43
+ "ant-frontend-slides": {"ref": "9906a34d640d2111f724544cbc50f7f130569ae1", "description": "制作浏览器中的动态 HTML 演示文稿。"},
44
+ "ant-slidev": {"ref": "73053ab2b9653ed475960301b56e1ce4439d9c75", "description": "用 Markdown 制作技术演示文稿。"},
45
+ "ant-word-docx": {"ref": "1.0.2", "description": "创建、阅读和修改 Word 文档,尽量保留原有样式。"},
46
+ "ant-excel-xlsx": {"ref": "1.0.2", "description": "分析和修改 Excel 表格、公式与格式。"},
47
+ "ant-pdf": {"ref": "6e9b3e3b8852f12bf965f888213470574b72803e", "description": "读取、创建、合并和处理 PDF 与表单。"},
48
+ "ant-internal-comms": {"ref": "dbd4588f9e1033efb41dad4bef2f7947c8993d44", "description": "撰写内部通知、项目更新、FAQ 和周报。"},
49
+ "ant-formatted-report-writer": {"ref": "97a13607d669157a4a16d5ba79b77f208319f7b9", "description": "把分析材料整理成结构清楚的业务报告。"},
50
+ "ant-agent-browser": {"ref": "dac13bb87468fe9d74a040d862720f6cc17a9565", "description": "操作网页、截图和页面检查。"},
51
+ "ant-vercel-react-best-practices": {"ref": "063bee94c3f4df8453406c830b0a7df0f2860278", "description": "优化 React 与 Next.js 性能。"},
52
+ "ant-vercel-composition-patterns": {"ref": "063bee94c3f4df8453406c830b0a7df0f2860278", "description": "设计可复用 React 组件。"},
53
+ "ant-supabase-postgres-best-practices": {"ref": "c9be0e931b7930f7d02126d04774d904c381e7d7", "description": "改进 SQL、索引、迁移和数据库安全。"},
54
+ "ant-diagnosing-bugs": {"ref": "5beb35b040e3de97e54fa9e386d7e42d15c295c2", "description": "建立复现信号并定位问题根因。"},
55
+ "ant-improve-codebase-architecture": {"ref": "5beb35b040e3de97e54fa9e386d7e42d15c295c2", "description": "识别模块耦合和重构机会。"},
56
+ "ant-code-review": {"ref": "5beb35b040e3de97e54fa9e386d7e42d15c295c2", "description": "按规范和原始需求审查代码差异。"},
57
+ "ant-systematic-debugging": {"ref": "bb92a77741419a4ab5f06e711a283343f1ada0c3", "description": "先找根因,再修改和复核。"},
58
+ "ant-test-driven-development": {"ref": "bb92a77741419a4ab5f06e711a283343f1ada0c3", "description": "先建立失败样例,再实现和重构。"},
59
+ "ant-verification-before-completion": {"ref": "bb92a77741419a4ab5f06e711a283343f1ada0c3", "description": "交付前用实际证据核对结论。"},
60
+ "ant-writing-plans": {"ref": "bb92a77741419a4ab5f06e711a283343f1ada0c3", "description": "将已明确需求拆成可执行计划。"},
61
+ "ant-to-spec": {"ref": "5beb35b040e3de97e54fa9e386d7e42d15c295c2", "description": "把已有讨论整理为产品和实现规格。"},
62
+ "ant-skill-creator": {"ref": "dbd4588f9e1033efb41dad4bef2f7947c8993d44", "description": "设计、打包和评估新技能。"},
63
+ "ant-frontend-design": {"ref": "dbd4588f9e1033efb41dad4bef2f7947c8993d44", "description": "设计更有辨识度的网页界面。"},
64
+ "ant-web-design-guidelines": {"ref": "063bee94c3f4df8453406c830b0a7df0f2860278", "description": "检查 UI、可访问性和交互细节。"},
65
+ "ant-ui-ux-pro-max": {"ref": "56ea9cf174ed86786187daa9de0e6247cb0747e4", "description": "检索配色、排版、动效和界面规则。"},
66
+ "ant-emil-design-eng": {"ref": "2d228630ba1446365f614a108cb8252f1feda202", "description": "改进界面动效、组件和交互细节。"},
67
+ "ant-review-animations": {"ref": "2d228630ba1446365f614a108cb8252f1feda202", "description": "按照原作者的标准检查动效实现。"},
68
+ "ant-shadcn": {"ref": "ba1503c64472b82256d9de95e2e62c5901a38e8b", "description": "搜索、组合和调整 shadcn/ui 组件。"},
69
+ "ant-theme-factory": {"ref": "dbd4588f9e1033efb41dad4bef2f7947c8993d44", "description": "为文档、页面和演示选择主题。"},
70
+ "ant-brand-guidelines": {"ref": "dbd4588f9e1033efb41dad4bef2f7947c8993d44", "description": "原作者自己的品牌色和字体规范。"},
71
+ "ant-copywriting": {"ref": "365b412152b9542499ca1439426f7fc3cc92bb65", "description": "撰写和改进页面、广告与产品文案。"},
72
+ "ant-seo-audit": {"ref": "365b412152b9542499ca1439426f7fc3cc92bb65", "description": "诊断网站搜索可见性与页面问题。"},
73
+ "ant-brainstorming": {"ref": "bb92a77741419a4ab5f06e711a283343f1ada0c3", "description": "澄清需求、比较方案和形成设计。"},
74
+ }
75
+ const hasChineseIntroduction = (value) => {
76
+ const text = typeof value === 'string' ? value : ''
77
+ const han = (text.match(/[\u3400-\u9fff]/gu) || []).length
78
+ return han >= 2 && han * 5 >= (text.match(/[A-Za-z]/gu) || []).length
79
+ }
80
+ const curatedSkillIntroduction = (skill) => {
81
+ const item = curatedSkillIntroductions[skill.dsh_name]
82
+ return item && item.ref === skill.source_ref ? item.description : null
83
+ }
84
+ const skillIntroduction = (skill) => hasChineseIntroduction(skill.description)
85
+ ? skill.description : curatedSkillIntroduction(skill)
86
+ || '这项技能尚未提供中文介绍。请查看英文原文,或联系企业管理员补充。'
87
+ const skillEnglishSource = (skill) => {
88
+ let url
89
+ try { url = new URL(skill.source_url) } catch { return null }
90
+ if (url.protocol !== 'https:' || url.username || url.password) return null
91
+ const parts = url.pathname.split('/').filter(Boolean)
92
+ const fixedRef = /^[a-f0-9]{40}$/iu.test(skill.source_ref || '') ? skill.source_ref : null
93
+ if (url.hostname === 'github.com' && parts.length >= 4 && parts[2] === 'tree' && fixedRef) {
94
+ return `https://github.com/${parts[0]}/${parts[1]}/blob/${fixedRef}/${parts.slice(4).join('/')}${parts.length > 4 ? '/' : ''}SKILL.md`
95
+ }
96
+ return null
97
+ }
98
+ const skillCategory = (skill) => marketSkillCategories[skill.dsh_name] || '其他'
14
99
  const panelStyle = {
15
100
  display: 'grid', alignContent: 'start', gap: 14, padding: 18,
16
101
  border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 14,
@@ -277,7 +362,11 @@ window.__ModuleLoader__.load({
277
362
  const [loadError, setLoadError] = useState(false)
278
363
  const [workingId, setWorkingId] = useState('')
279
364
  const [message, setMessage] = useState('')
365
+ const [originalView, setOriginalView] = useState(null)
366
+ const originalGeneration = useRef(0)
280
367
  const [filter, setFilter] = useState('all')
368
+ const [scopeFilter, setScopeFilter] = useState('all')
369
+ const [categoryFilter, setCategoryFilter] = useState('all')
281
370
  const [refreshToken, setRefreshToken] = useState(0)
282
371
  const refreshGeneration = useRef(0)
283
372
  const refresh = async () => {
@@ -293,7 +382,7 @@ window.__ModuleLoader__.load({
293
382
  if (generation !== refreshGeneration.current) return
294
383
  setSkills(result.skills)
295
384
  setLoadError(false)
296
- setMessage(result.skills.length ? '' : '团队暂未发布精选技能。')
385
+ setMessage(result.warning || (result.skills.length ? '' : '暂无团队或市场精选技能;下方仍可查看 DSH 官方技能。'))
297
386
  } catch (error) {
298
387
  if (generation !== refreshGeneration.current) return
299
388
  setSkills([])
@@ -306,7 +395,9 @@ window.__ModuleLoader__.load({
306
395
  const changeInstall = async (skill, remove = false) => {
307
396
  const action = remove ? '卸载' : skill.local_status === 'update-available' ? '更新' : '安装'
308
397
  const prompt = remove
309
- ? `从这台 DSH 卸载 ${skill.title}?已修改的本地文件不会被删除。`
398
+ ? skill.conflict_reason === 'other-source'
399
+ ? '这台 DSH 已安装另一个来源的同名技能。确认卸载本机旧来源?已修改的本地文件不会被删除。'
400
+ : `从这台 DSH 卸载 ${skill.title}?已修改的本地文件不会被删除。`
310
401
  : `将 ${skill.title} ${action}到这台 DSH?\n\n本机依赖:${skill.requirements}\n\n技能可能让 DSH 读取文件或执行脚本,仍受 DSH 的工具授权控制。ANT 不会自动安装系统依赖。`
311
402
  if (!window.confirm(prompt)) return
312
403
  setWorkingId(skill.id)
@@ -314,26 +405,75 @@ window.__ModuleLoader__.load({
314
405
  try {
315
406
  const response = await fetch(`/api/ant-teams/skills/${remove ? 'uninstall' : 'install'}`, {
316
407
  method: 'POST', headers: { 'Content-Type': 'application/json' },
317
- body: JSON.stringify(remove ? { dsh_name: skill.dsh_name } : { skill_id: skill.id }),
408
+ body: JSON.stringify(remove ? { dsh_name: skill.dsh_name } : { skill_id: skill.id, scope: skill.scope || 'team' }),
318
409
  })
319
410
  const result = await readLocalJsonResponse(response)
320
411
  if (!response.ok) throw new Error(result.message || `${action}失败(${response.status})。`)
321
412
  await refresh()
322
- setMessage(remove ? `${skill.title} 已从 ANT 管理的本机目录卸载。`
413
+ setMessage(remove ? skill.conflict_reason === 'other-source'
414
+ ? '本机旧来源已卸载,可以安装所选技能。'
415
+ : `${skill.title} 已从 ANT 管理的本机目录卸载。`
323
416
  : `${skill.title} 的文件已${action}到 DSH 用户技能目录。DSH 会在后续会话或模型步骤中读取;若不可用,请检查当前预设是否启用了本地技能。`)
324
417
  } catch (error) { setMessage(error instanceof Error ? error.message : `${action}失败。`) }
325
418
  finally { setWorkingId('') }
326
419
  }
420
+ const openOriginal = async (skill) => {
421
+ const generation = ++originalGeneration.current
422
+ setOriginalView({ title: skill.title, loading: true })
423
+ try {
424
+ const params = new URLSearchParams({ skill_id: skill.id, scope: skill.scope || 'team' })
425
+ const response = await fetch(`/api/ant-teams/skills/original?${params}`, { cache: 'no-store' })
426
+ if (response.status === 404 && !hasChineseIntroduction(skill.description)) {
427
+ if (generation === originalGeneration.current) setOriginalView({
428
+ title: skill.title, markdown: skill.description, partial: true,
429
+ })
430
+ return
431
+ }
432
+ if (!response.ok) {
433
+ const error = await readLocalJsonResponse(response)
434
+ throw new Error(error.message || '技能原文暂时无法打开。')
435
+ }
436
+ const markdown = await response.text()
437
+ if (generation === originalGeneration.current) setOriginalView({ title: skill.title, markdown })
438
+ } catch (error) {
439
+ if (generation === originalGeneration.current) setOriginalView({
440
+ title: skill.title, error: error instanceof Error ? error.message : '技能原文暂时无法打开。',
441
+ })
442
+ }
443
+ }
327
444
  const visible = skills.filter((skill) => {
328
445
  const matchesFilter = filter === 'all'
329
446
  || filter === 'available' && ['not-installed', 'update-available'].includes(skill.local_status)
330
447
  || filter === 'installed' && ['installed', 'update-available'].includes(skill.local_status)
331
448
  || filter === 'updates' && skill.local_status === 'update-available'
332
- const matchesQuery = !query.trim() || `${skill.title} ${skill.description} ${skill.dsh_name}`.toLowerCase().includes(query.trim().toLowerCase())
449
+ const matchesQuery = !query.trim() || `${skill.title} ${skillIntroduction(skill)} ${skill.description} ${skill.dsh_name}`.toLowerCase().includes(query.trim().toLowerCase())
333
450
  return matchesFilter && matchesQuery
451
+ && (scopeFilter === 'all' || (skill.scope || 'team') === scopeFilter)
452
+ && (categoryFilter === 'all' || skillCategory(skill) === categoryFilter)
453
+ })
454
+ const visibleOfficial = officialOfficeSkills.filter((skill) => {
455
+ const matchesQuery = !query.trim() || `${skill.title} ${skill.name} ${skill.description} ${skill.prompt}`.toLowerCase().includes(query.trim().toLowerCase())
456
+ return filter === 'all' && matchesQuery
457
+ && (scopeFilter === 'all' || scopeFilter === 'official')
458
+ && (categoryFilter === 'all' || skill.category === categoryFilter)
334
459
  })
460
+ const categories = [...new Set([...skills.map(skillCategory), ...officialOfficeSkills.map((skill) => skill.category)])]
461
+ .sort((a, b) => a.localeCompare(b, 'zh-CN'))
335
462
  const statusLabel = { installed: '已放入本机目录', 'update-available': '可更新', 'not-installed': '未安装', conflict: '本机冲突' }
336
- return h('section', { 'aria-label': includePlugins ? '企业扩展' : '企业精选技能', style: {
463
+ if (originalView) return h('section', { 'aria-label': '技能原文', style: {
464
+ height: '100%', overflowY: 'auto', boxSizing: 'border-box',
465
+ padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
466
+ } }, h('div', { style: { maxWidth: 1100, margin: '0 auto', display: 'grid', gap: 16 } },
467
+ h('button', { type: 'button', onClick: () => { originalGeneration.current += 1; setOriginalView(null) },
468
+ style: { ...secondaryButtonStyle, width: 'fit-content' } }, includePlugins ? '← 返回企业扩展' : '← 返回技能中心'),
469
+ h('h1', { style: { margin: 0, fontSize: 26 } }, `${originalView.title} · 原始技能说明`),
470
+ h('p', { style: mutedTextStyle }, originalView.partial
471
+ ? '当前服务端尚未提供完整原文,此处仅显示导入时的英文简介。'
472
+ : '这里显示发布时保存的 SKILL.md。它供 DSH 使用,与技能中心的中文介绍分别维护;ANT 可能给原技能名加前缀。'),
473
+ originalView.loading ? h('p', { role: 'status', style: mutedTextStyle }, '正在读取原文…')
474
+ : originalView.error ? h('p', { role: 'alert', style: mutedTextStyle }, originalView.error)
475
+ : h('pre', { style: { ...panelStyle, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', fontFamily: 'monospace', fontSize: 13, lineHeight: 1.6 } }, originalView.markdown)))
476
+ return h('section', { 'aria-label': includePlugins ? '企业扩展' : '技能中心', style: {
337
477
  height: '100%', overflowY: 'auto', boxSizing: 'border-box',
338
478
  padding: 'calc(var(--dsh-frame-top-clearance, 0px) + 20px) clamp(16px, 3vw, 36px) 48px',
339
479
  } }, h('div', { style: { maxWidth: 1440, margin: '0 auto', display: 'grid', gap: 22 } },
@@ -349,10 +489,10 @@ window.__ModuleLoader__.load({
349
489
  h('button', { type: 'button', onClick: onBack, style: { ...categoryButtonStyle, marginLeft: 'auto' } }, 'ANT 设置')),
350
490
  h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'end', justifyContent: 'space-between', gap: 16 } },
351
491
  h('div', null,
352
- h('h1', { style: { margin: 0, fontSize: 28 } }, includePlugins ? '企业扩展' : '企业精选技能'),
492
+ h('h1', { style: { margin: 0, fontSize: 28 } }, includePlugins ? '企业扩展' : '技能中心'),
353
493
  h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, includePlugins
354
- ? '浏览团队审核的技能与插件;选择需要的扩展安装到这台 DSH。'
355
- : '团队管理员审核后发布;安装到这台 DSH 后,由 DSH 原生技能系统发现和使用。')),
494
+ ? '浏览全市场与本企业审核的技能、插件;选择需要的扩展安装到这台 DSH。'
495
+ : '浏览全市场精选和本企业技能;安装到这台 DSH 后,由 DSH 原生技能系统发现和使用。')),
356
496
  h('button', { type: 'button', disabled: loading || Boolean(workingId), onClick: () => {
357
497
  void refresh()
358
498
  if (includePlugins) setRefreshToken((value) => value + 1)
@@ -366,36 +506,90 @@ window.__ModuleLoader__.load({
366
506
  ...[['all', '全部'], ['available', '可安装或更新'], ['installed', '已安装'], ['updates', '可更新']].map(([value, label]) =>
367
507
  h('button', { key: value, type: 'button', onClick: () => setFilter(value), 'aria-pressed': filter === value,
368
508
  style: { ...secondaryButtonStyle, minHeight: 34, padding: '6px 12px', backgroundColor: filter === value ? 'var(--dsw-alias-bg-layer-2)' : secondaryButtonStyle.backgroundColor } }, label)),
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,
509
+ includePlugins ? null : h('span', { style: { ...mutedTextStyle, marginLeft: 'auto' } }, `显示 ${visible.length + visibleOfficial.length} / ${skills.length + officialOfficeSkills.length} 个技能`)),
510
+ includePlugins ? null : h('div', { style: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 } },
511
+ h('span', { style: mutedTextStyle }, '来源'),
512
+ ...[['all', '全部'], ['official', 'DSH 官方'], ['market', '全市场精选'], ['team', '本企业']].map(([value, label]) =>
513
+ h('button', { key: value, type: 'button', onClick: () => setScopeFilter(value), 'aria-pressed': scopeFilter === value,
514
+ style: { ...categoryButtonStyle, backgroundColor: scopeFilter === value ? 'var(--dsw-alias-bg-layer-2)' : categoryButtonStyle.backgroundColor } }, label)),
515
+ h('label', { style: { ...mutedTextStyle, marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 6 } }, '用途',
516
+ h('select', { value: categoryFilter, onChange: (event) => setCategoryFilter(event.target.value), style: { ...inputStyle, width: 'auto', minHeight: 34 } },
517
+ h('option', { value: 'all' }, '全部用途'),
518
+ ...categories.map((category) => h('option', { key: category, value: category }, category))))),
519
+ ),
520
+ includePlugins ? h('h2', { style: { margin: 0, fontSize: 20 } }, loading ? '技能 · 读取中…' : `技能 · ${visible.length + visibleOfficial.length}`) : null,
371
521
  message && (!includePlugins || loadError || skills.length) ? h('p', { role: loadError ? 'alert' : 'status', style: mutedTextStyle }, message) : null,
522
+ visibleOfficial.length ? h('section', { 'aria-label': 'DSH 官方 Office 技能', style: { display: 'grid', gap: 12 } },
523
+ h('div', null,
524
+ h('h2', { style: { margin: 0, fontSize: 20 } }, 'DSH 官方 Office'),
525
+ h('p', { style: { ...mutedTextStyle, marginTop: 4 } }, '这些能力随对应版本的 DSH 提供,ANT 无需重复安装。在新会话中直接描述任务即可;不计入 ANT 的本机安装筛选。'),
526
+ h('a', { href: officialOfficeGuide, target: '_blank', rel: 'noopener noreferrer', style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看中文官方说明 ↗')),
527
+ h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 14 } },
528
+ ...visibleOfficial.map((skill) => h('article', { key: skill.name, style: { ...panelStyle, gap: 10 } },
529
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 } },
530
+ h('h3', { style: { margin: 0, fontSize: 17 } }, skill.title),
531
+ h('span', { style: mutedTextStyle }, 'DSH 官方')),
532
+ h('p', { style: mutedTextStyle }, skill.description),
533
+ h('p', { style: mutedTextStyle }, `在新会话中说:“${skill.prompt}”。也可输入 /${skill.name}。`),
534
+ h('p', { style: mutedTextStyle }, `DSH ${skill.since} 起提供;旧版请使用 DSH 自带的检查更新。`),
535
+ h('a', { href: officialOfficeEnglish(skill), target: '_blank', rel: 'noopener noreferrer',
536
+ style: { ...secondaryButtonStyle, display: 'inline-flex', alignItems: 'center', width: 'fit-content', textDecoration: 'none' } }, '查看英文原文 ↗'))))) : null,
372
537
  visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
373
538
  ...visible.map((skill) => {
374
- const description = skill.description || ''
539
+ const description = skillIntroduction(skill)
540
+ const originalEnglish = Boolean(curatedSkillIntroduction(skill)) || !hasChineseIntroduction(skill.description)
541
+ const englishSource = skillEnglishSource(skill)
542
+ const sourceRef = skill.source_ref || ''
543
+ const versionLabel = /^[a-f0-9]{40,64}$/iu.test(sourceRef) ? sourceRef.slice(0, 12) : sourceRef
544
+ const sourceLabel = /^https:\/\/github\.com\//u.test(skill.source_url || '') ? 'GitHub'
545
+ : /^https:\/\/clawhub\.ai\//u.test(skill.source_url || '') ? 'ClawHub'
546
+ : skill.source_kind === 'upload' ? '管理员上传' : '原作者来源'
375
547
  return h('article', { key: skill.id, style: { ...panelStyle, gap: 13 } },
376
548
  h('div', { style: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'baseline', gap: 8 } },
377
549
  h('div', { style: { display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', gap: 8 } },
378
550
  h('h2', { style: { margin: 0, fontSize: 19 } }, skill.title),
379
- includePlugins ? h('span', { style: mutedTextStyle }, '技能') : null),
551
+ h('span', { style: { ...mutedTextStyle, padding: '2px 7px', borderRadius: 6, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, skill.scope === 'market' ? '全市场精选' : '本企业'),
552
+ h('span', { style: mutedTextStyle }, skillCategory(skill))),
380
553
  h('span', { style: mutedTextStyle }, statusLabel[skill.local_status] || '状态未知')),
381
554
  h('p', { style: mutedTextStyle }, description.length > 180 ? `${description.slice(0, 180)}…` : description),
555
+ englishSource ? h('a', { href: englishSource, target: '_blank', rel: 'noopener noreferrer',
556
+ style: { color: 'var(--dsw-alias-link)', fontSize: 13 } }, originalEnglish ? '查看英文原文 ↗' : '查看原始说明 ↗')
557
+ : h('button', { type: 'button', onClick: () => void openOriginal(skill),
558
+ style: { border: 0, padding: 0, background: 'transparent', cursor: 'pointer',
559
+ color: 'var(--dsw-alias-link)', fontSize: 13, textAlign: 'left' } },
560
+ originalEnglish ? '查看英文原文 →' : '查看原始说明 →'),
561
+ h('p', { style: mutedTextStyle }, `来源:${sourceLabel} · 许可:${skill.license}`),
382
562
  description.length > 180 ? h('details', null,
383
563
  h('summary', { style: { cursor: 'pointer', fontSize: 13 } }, '查看完整说明'),
384
564
  h('p', { style: { ...mutedTextStyle, marginTop: 8, whiteSpace: 'pre-wrap' } }, description)) : null,
385
- h('p', { style: mutedTextStyle }, `来源:${skill.source_kind === 'github' ? 'GitHub' : '管理员上传'} · 许可证:${skill.license} · ${fileCountFormat.format(skill.file_count)} 个文件`),
386
- h('p', { style: mutedTextStyle }, `本机依赖:${skill.requirements}`),
565
+ h('details', null,
566
+ h('summary', { style: { cursor: 'pointer', fontSize: 13 } }, '查看来源、文件与使用条件'),
567
+ h('div', { style: { display: 'grid', gap: 5, marginTop: 9, overflowWrap: 'anywhere' } },
568
+ h('p', { style: mutedTextStyle }, `${fileCountFormat.format(skill.file_count)} 个文件`),
569
+ h('p', { style: mutedTextStyle }, `本机条件:${skill.requirements}`),
570
+ h('p', { style: mutedTextStyle, title: sourceRef }, `固定版本:${versionLabel || '已锁定制品'}`),
571
+ /^https:\/\//u.test(skill.source_url || '')
572
+ ? h('a', { href: skill.source_url, target: '_blank', rel: 'noopener noreferrer', style: { fontSize: 13, color: 'var(--dsw-alias-link)' } }, '查看原始来源 ↗')
573
+ : h('p', { style: mutedTextStyle }, '来源:管理员上传并审核'))),
387
574
  skill.local_status === 'conflict'
388
- ? h('p', { role: 'status', style: mutedTextStyle }, '本机同名技能有未受 ANT 管理的内容、文件已被修改或备份损坏;请联系管理员处理。')
575
+ ? h('div', { style: { display: 'grid', gap: 8 } },
576
+ h('p', { role: 'status', style: mutedTextStyle }, skill.conflict_reason === 'other-source'
577
+ ? '本机已安装另一个来源的同名技能。可主动卸载本机旧来源,再安装此版本。'
578
+ : '本机同名技能有未受 ANT 管理的内容、文件已被修改或备份损坏;请联系管理员处理。'),
579
+ skill.conflict_reason === 'other-source'
580
+ ? h('button', { type: 'button', disabled: Boolean(workingId),
581
+ onClick: () => void changeInstall(skill, true), style: { ...secondaryButtonStyle, width: 'fit-content' } }, '卸载旧来源')
582
+ : null)
389
583
  : h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, marginTop: 'auto' } },
390
584
  skill.local_status === 'installed' ? null : h('button', { type: 'button', disabled: Boolean(workingId),
391
585
  onClick: () => void changeInstall(skill), style: primaryButtonStyle },
392
586
  skill.local_status === 'update-available' ? '更新技能' : '安装到这台 DSH'),
393
587
  skill.local_status !== 'not-installed' ? h('button', { type: 'button', disabled: Boolean(workingId),
394
588
  onClick: () => void changeInstall(skill, true), style: secondaryButtonStyle }, '卸载') : null))
395
- })) : !includePlugins && !loading && skills.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null,
589
+ })) : !includePlugins && !loading && (skills.length || officialOfficeSkills.length) && !visibleOfficial.length ? h('p', { style: mutedTextStyle }, '没有符合筛选条件的技能。') : null,
396
590
  includePlugins ? h(PluginCenterPage, { remote, onBack, onAll, onSkills, openBundle,
397
591
  embedded: true, query, onQueryChange, externalFilter: filter,
398
- hasSkillMatches: visible.length > 0, skillCatalogReady: !loading && !loadError,
592
+ hasSkillMatches: visible.length + visibleOfficial.length > 0, skillCatalogReady: !loading && !loadError,
399
593
  refreshToken }) : null))
400
594
  }
401
595
 
package/index.js CHANGED
@@ -13,6 +13,7 @@ const DISCONNECT_PATH = '/api/ant-teams/disconnect'
13
13
  const REQUEST_CODE_PATH = '/api/ant-teams/request-code'
14
14
  const CONNECT_PATH = '/api/ant-teams/connect'
15
15
  const SKILLS_PATH = '/api/ant-teams/skills'
16
+ const SKILL_ORIGINAL_PATH = '/api/ant-teams/skills/original'
16
17
  const SKILL_INSTALL_PATH = '/api/ant-teams/skills/install'
17
18
  const SKILL_UNINSTALL_PATH = '/api/ant-teams/skills/uninstall'
18
19
  const PLUGINS_PATH = '/api/ant-teams/plugins'
@@ -21,6 +22,7 @@ const MAX_MODEL_RESPONSE_BYTES = 1024 * 1024
21
22
  const MODEL_TOKEN_REF = 'ANT_MODEL_API_KEY'
22
23
  const SERVICE_RECORD_KEY = 'ant/team-service'
23
24
  const SESSION_HEARTBEAT_INTERVAL_MS = 30_000
25
+ const MARKET_SERVICE_URL = process.env.ANT_MARKET_URL?.trim() || 'https://8.160.178.245'
24
26
 
25
27
  function jsonResponse(status, value) {
26
28
  return new Response(JSON.stringify(value), {
@@ -359,14 +361,72 @@ async function memberSkillConnection(credentials, settings) {
359
361
  return connection
360
362
  }
361
363
 
364
+ function marketSkillConnection() {
365
+ const serviceUrl = normalizeServiceUrl(MARKET_SERVICE_URL)
366
+ if (!serviceUrl) throw new Error('ANT 公共技能市场地址无效。')
367
+ return { serviceUrl, token: null }
368
+ }
369
+
362
370
  async function fetchMemberSkills(request, credentials, settings) {
363
371
  if (request.method !== 'GET') return jsonResponse(405, { message: '只支持 GET 请求' })
364
372
  try {
365
373
  const connection = await memberSkillConnection(credentials, settings)
366
- if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队,或重新验证工作邮箱。' })
367
- return jsonResponse(200, { skills: await listSkills(connection) })
374
+ const settle = async (promise) => {
375
+ try { return { status: 'fulfilled', value: await promise } }
376
+ catch { return { status: 'rejected' } }
377
+ }
378
+ const team = await settle(connection ? listSkills(connection) : Promise.resolve([]))
379
+ const market = await settle(listSkills(marketSkillConnection(), 'market'))
380
+ if (market.status === 'rejected' && (team.status === 'rejected' || !connection)) {
381
+ throw new Error('暂时无法读取公共技能市场;请稍后刷新。')
382
+ }
383
+ const teamSkills = team.status === 'fulfilled' ? team.value : []
384
+ const knownIds = new Set(teamSkills.map((skill) => skill.id))
385
+ const marketSkills = market.status === 'fulfilled'
386
+ ? market.value.filter((skill) => !knownIds.has(skill.id)) : []
387
+ const warning = market.status === 'rejected' ? '公共技能市场暂时无法连接,已显示本企业技能。'
388
+ : team.status === 'rejected' ? '本企业技能暂时无法读取,已显示公共市场技能。'
389
+ : !connection ? '连接团队后,还可以看到本企业发布的技能。' : ''
390
+ return jsonResponse(200, warning
391
+ ? { skills: [...marketSkills, ...teamSkills], warning }
392
+ : { skills: [...marketSkills, ...teamSkills] })
368
393
  } catch (error) {
369
- return jsonResponse(502, { message: error instanceof Error ? error.message : '无法读取企业精选技能。' })
394
+ return jsonResponse(502, { message: error instanceof Error ? error.message : '无法读取技能目录。' })
395
+ }
396
+ }
397
+
398
+ async function fetchSkillOriginal(request, credentials, settings) {
399
+ if (request.method !== 'GET') return jsonResponse(405, { message: '只支持 GET 请求' })
400
+ let url
401
+ try { url = new URL(request.url) } catch { return jsonResponse(400, { message: '技能原文地址无效。' }) }
402
+ const skillId = url.searchParams.get('skill_id')
403
+ const scope = url.searchParams.get('scope') || 'team'
404
+ if (!/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(skillId || '')
405
+ || !['market', 'team'].includes(scope)) {
406
+ return jsonResponse(400, { message: '技能原文地址无效。' })
407
+ }
408
+ try {
409
+ const connection = scope === 'market' ? marketSkillConnection()
410
+ : await memberSkillConnection(credentials, settings)
411
+ if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队。' })
412
+ const path = scope === 'market' ? `/api/market/skills/${skillId}/original`
413
+ : `/api/me/skills/${skillId}/original`
414
+ const upstream = await fetch(new URL(path, connection.serviceUrl), {
415
+ headers: connection.token ? { authorization: `Bearer ${connection.token}`, accept: 'text/plain' }
416
+ : { accept: 'text/plain' },
417
+ redirect: 'manual', signal: AbortSignal.timeout(10_000),
418
+ })
419
+ if (upstream.status === 404) return jsonResponse(404, { message: '技能原文暂不可读取:服务端可能尚未升级,或技能已撤回。' })
420
+ if (upstream.status === 401 || upstream.status === 403) return jsonResponse(401, { message: 'ANT 成员凭证已失效。' })
421
+ if (!upstream.ok || !upstream.headers.get('content-type')?.startsWith('text/plain')) {
422
+ return jsonResponse(502, { message: 'ANT 未返回有效的技能原文。' })
423
+ }
424
+ const text = await readLimitedBody(upstream)
425
+ if (text === null) return jsonResponse(502, { message: '技能原文超过预览上限。' })
426
+ return new Response(text, { headers: { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store',
427
+ 'x-content-type-options': 'nosniff', 'content-security-policy': "default-src 'none'" } })
428
+ } catch {
429
+ return jsonResponse(502, { message: '暂时无法读取技能原文。' })
370
430
  }
371
431
  }
372
432
 
@@ -378,10 +438,15 @@ async function installMemberSkill(request, credentials, settings) {
378
438
  if (text.length > 4096) throw new Error()
379
439
  body = JSON.parse(text)
380
440
  } catch { return jsonResponse(400, { message: '技能安装请求无效。' }) }
441
+ if (body?.scope !== 'market' && body?.scope !== 'team' && body?.scope !== undefined) {
442
+ return jsonResponse(400, { message: '技能来源无效。' })
443
+ }
381
444
  try {
382
- const connection = await memberSkillConnection(credentials, settings)
445
+ const scope = body?.scope || 'team'
446
+ const connection = scope === 'market' ? marketSkillConnection()
447
+ : await memberSkillConnection(credentials, settings)
383
448
  if (!connection) return jsonResponse(401, { message: '请先连接 ANT 团队,或重新验证工作邮箱。' })
384
- return jsonResponse(200, await installSkill(connection, body?.skill_id))
449
+ return jsonResponse(200, await installSkill(connection, body?.skill_id, scope))
385
450
  } catch (error) {
386
451
  return jsonResponse(409, { message: error instanceof Error ? error.message : '技能安装失败。' })
387
452
  }
@@ -544,6 +609,10 @@ export function apply(ctx) {
544
609
  path: SKILLS_PATH, methods: ['GET'], requestBody: 'buffered',
545
610
  fetch: (request) => fetchMemberSkills(request, connectionCtx.credentials, connectionCtx.settings),
546
611
  }), 'ant-teams: enterprise skill catalog')
612
+ connectionCtx.effect(() => connectionCtx.connection.fetch.register({
613
+ path: SKILL_ORIGINAL_PATH, methods: ['GET'], requestBody: 'buffered',
614
+ fetch: (request) => fetchSkillOriginal(request, connectionCtx.credentials, connectionCtx.settings),
615
+ }), 'ant-teams: approved skill original text')
547
616
  connectionCtx.effect(() => connectionCtx.connection.fetch.register({
548
617
  path: SKILL_INSTALL_PATH, methods: ['POST'], requestBody: 'buffered',
549
618
  fetch: (request) => installMemberSkill(request, connectionCtx.credentials, connectionCtx.settings),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-wendao/ant",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
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": {
package/skill-install.js CHANGED
@@ -28,7 +28,9 @@ const skillRoot = () => {
28
28
  ? join(homedir(), configured.slice(2)) : configured
29
29
  return join(resolve(home), 'skills')
30
30
  }
31
+ const markerScope = (marker) => marker.scope === 'market' ? 'market' : 'team'
31
32
  const validManagedMarker = (marker, name) => marker?.dsh_name === name && validHash(marker.content_sha256)
33
+ && (marker.scope === undefined || marker.scope === 'team' || marker.scope === 'market')
32
34
  && Array.isArray(marker.files) && marker.files.length > 0 && marker.files.length <= MAX_FILES
33
35
  && marker.files.every((file) => safePath(file.path) && validHash(file.sha256) && typeof file.executable === 'boolean')
34
36
  && new Set(marker.files.map((file) => file.path)).size === marker.files.length
@@ -134,7 +136,8 @@ async function verifyManagedTree(root, name, marker) {
134
136
  }
135
137
 
136
138
  async function boundedBytes(response, maxBytes) {
137
- if (!response.ok || !response.body) throw new Error(`团队服务无法提供技能文件(HTTP ${response.status})。`)
139
+ if (response.status === 404) throw new Error('此技能可能已下架或不再可访问,请刷新目录后重试。')
140
+ if (!response.ok || !response.body) throw new Error(`技能来源无法提供文件(HTTP ${response.status})。`)
138
141
  const chunks = []
139
142
  let total = 0
140
143
  for await (const chunk of response.body) {
@@ -145,29 +148,37 @@ async function boundedBytes(response, maxBytes) {
145
148
  return Buffer.concat(chunks, total)
146
149
  }
147
150
 
148
- async function fetchJson(url, token) {
149
- const response = await fetch(url, { headers: { authorization: `Bearer ${token}`, accept: 'application/json' }, redirect: 'manual', signal: AbortSignal.timeout(20_000) })
151
+ async function fetchJson(url, token, timeoutMs = 20_000) {
152
+ const headers = token ? { authorization: `Bearer ${token}`, accept: 'application/json' } : { accept: 'application/json' }
153
+ const response = await fetch(url, { headers, redirect: 'manual', signal: AbortSignal.timeout(timeoutMs) })
150
154
  const bytes = await boundedBytes(response, MAX_JSON_BYTES)
151
- try { return JSON.parse(bytes.toString('utf8')) } catch { throw new Error('团队技能目录格式无效。') }
155
+ try { return JSON.parse(bytes.toString('utf8')) } catch { throw new Error('技能目录格式无效。') }
152
156
  }
153
157
 
154
- export async function listSkills(connection) {
155
- const skills = await fetchJson(new URL('/api/me/skills', connection.serviceUrl), connection.token)
156
- if (!Array.isArray(skills)) throw new Error('团队技能目录格式无效。')
158
+ export async function listSkills(connection, scope = 'team') {
159
+ const path = scope === 'market' ? '/api/market/skills' : '/api/me/skills'
160
+ const skills = await fetchJson(new URL(path, connection.serviceUrl), connection.token, scope === 'market' ? 8_000 : 20_000)
161
+ if (!Array.isArray(skills)) throw new Error('技能目录格式无效。')
157
162
  const { root, conflicts } = await safeRoot()
158
163
  return Promise.all(skills.map(async (skill) => {
159
- if (!safeName(skill.dsh_name) || !validHash(skill.content_sha256)) throw new Error('团队技能目录含无效条目。')
164
+ if (!safeName(skill.dsh_name) || !validHash(skill.content_sha256)) throw new Error('技能目录含无效条目。')
160
165
  let local_status = conflicts.has(skill.dsh_name) ? 'conflict' : 'not-installed'
161
- if (local_status === 'conflict') return { ...skill, local_status }
166
+ let conflict_reason = ''
167
+ const actualScope = skill.scope === 'market' ? 'market' : scope
168
+ if (local_status === 'conflict') return { ...skill, scope: actualScope, local_status }
162
169
  try {
163
170
  const marker = await readManaged(root, skill.dsh_name)
164
171
  if (marker) {
165
172
  await verifyManagedTree(root, skill.dsh_name, marker)
166
- if (marker.service_url !== connection.serviceUrl) throw new Error('该目录属于另一个 ANT 服务。')
173
+ if (marker.service_url !== connection.serviceUrl
174
+ || (markerScope(marker) !== actualScope && marker.skill_id !== skill.id)) {
175
+ conflict_reason = 'other-source'
176
+ throw new Error('该目录属于另一个技能来源。')
177
+ }
167
178
  local_status = marker.content_sha256 === skill.content_sha256 ? 'installed' : 'update-available'
168
179
  }
169
180
  } catch { local_status = 'conflict' }
170
- return { ...skill, local_status }
181
+ return { ...skill, scope: actualScope, local_status, conflict_reason }
171
182
  }))
172
183
  }
173
184
 
@@ -203,31 +214,37 @@ function contentDigest(files) {
203
214
  return digest.digest('hex')
204
215
  }
205
216
 
206
- export async function installSkill(connection, skillId) {
217
+ export async function installSkill(connection, skillId, scope = 'team') {
207
218
  if (typeof skillId !== 'string' || !/^[0-9a-f-]{36}$/iu.test(skillId)) throw new Error('技能编号无效。')
208
- const manifest = await fetchJson(new URL(`/api/me/skills/${skillId}/files`, connection.serviceUrl), connection.token)
219
+ if (scope !== 'team' && scope !== 'market') throw new Error('技能来源无效。')
220
+ const basePath = scope === 'market' ? '/api/market/skills' : '/api/me/skills'
221
+ const manifest = await fetchJson(new URL(`${basePath}/${skillId}/files`, connection.serviceUrl), connection.token)
209
222
  validateManifest(manifest, skillId)
210
223
  const { root, conflicts } = await safeRoot()
211
224
  if (conflicts.has(manifest.dsh_name)) throw new Error('本机技能备份已损坏;请联系管理员处理,ANT 不会覆盖。')
212
225
  if (activeSkills.has(manifest.dsh_name)) throw new Error('这个技能正在安装或卸载,请稍后重试。')
213
226
  activeSkills.add(manifest.dsh_name)
214
- try { return await installValidatedSkill(connection, skillId, manifest, root) }
227
+ try { return await installValidatedSkill(connection, skillId, manifest, root, basePath, scope) }
215
228
  finally { activeSkills.delete(manifest.dsh_name) }
216
229
  }
217
230
 
218
- async function installValidatedSkill(connection, skillId, manifest, root) {
231
+ async function installValidatedSkill(connection, skillId, manifest, root, basePath, scope) {
219
232
  const target = join(root, manifest.dsh_name)
220
233
  const oldMarker = await readManaged(root, manifest.dsh_name)
221
234
  if (oldMarker) {
222
- if (oldMarker.service_url !== connection.serviceUrl) throw new Error('同名技能属于另一个 ANT 服务,无法覆盖。')
235
+ if (oldMarker.service_url !== connection.serviceUrl
236
+ || (markerScope(oldMarker) !== scope && oldMarker.skill_id !== skillId)) {
237
+ throw new Error('同名技能属于另一个来源,请先卸载本机旧来源后再安装。')
238
+ }
223
239
  await verifyManagedTree(root, manifest.dsh_name, oldMarker)
224
240
  if (oldMarker.content_sha256 === manifest.content_sha256) {
225
241
  return { status: 'installed', dsh_name: manifest.dsh_name }
226
242
  }
227
243
  }
228
244
 
229
- const packageUrl = new URL(`/api/me/skills/${skillId}/package`, connection.serviceUrl)
230
- const packageResponse = await fetch(packageUrl, { headers: { authorization: `Bearer ${connection.token}` }, redirect: 'manual', signal: AbortSignal.timeout(120_000) })
245
+ const packageUrl = new URL(`${basePath}/${skillId}/package`, connection.serviceUrl)
246
+ const headers = connection.token ? { authorization: `Bearer ${connection.token}` } : {}
247
+ const packageResponse = await fetch(packageUrl, { headers, redirect: 'manual', signal: AbortSignal.timeout(120_000) })
231
248
  const packageBytes = await boundedBytes(packageResponse, manifest.package_size)
232
249
  if (packageBytes.length !== manifest.package_size || hash(packageBytes) !== manifest.package_sha256) {
233
250
  throw new Error('技能包校验失败,未安装。')
@@ -265,7 +282,7 @@ async function installValidatedSkill(connection, skillId, manifest, root) {
265
282
  if (file.executable && process.platform !== 'win32') await chmod(destination, 0o700)
266
283
  }
267
284
  await writeFile(join(staging, MARKER), JSON.stringify({
268
- dsh_name: manifest.dsh_name, skill_id: skillId, service_url: connection.serviceUrl,
285
+ dsh_name: manifest.dsh_name, skill_id: skillId, scope, service_url: connection.serviceUrl,
269
286
  content_sha256: manifest.content_sha256,
270
287
  files: managedFiles,
271
288
  }), { flag: 'wx', mode: 0o600 })