dsh-ant 0.6.0 → 0.6.3
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 +8 -8
- package/client.js +732 -231
- package/conversation.js +244 -78
- package/delegation.js +653 -16
- package/index.js +47 -10
- package/locale/messages.json +1384 -68
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -12,6 +12,37 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
let localeMetadata = {}
|
|
13
13
|
let localeTranslate = (key) => key
|
|
14
14
|
let localeMessagePatterns = []
|
|
15
|
+
const pluginScopeBusyMessage = '本机有一项插件操作仍在完成;结束前不能启动其他插件安装。'
|
|
16
|
+
const pluginRuntimeState = {
|
|
17
|
+
uncertainInstalls: new Map(), uncertainInstallsRef: { current: {} },
|
|
18
|
+
serviceUrl: '', scopeRevision: 0, workingId: '', workingServiceUrl: '', completedId: '',
|
|
19
|
+
message: '', messagePluginId: { current: '' }, messagePackageName: { current: '' },
|
|
20
|
+
messageClearOnResolve: { current: false },
|
|
21
|
+
refreshMessage: '', refreshMessageIsError: false,
|
|
22
|
+
messageVersion: { current: 0 }, refreshGeneration: { current: 0 }, refreshRevision: 0,
|
|
23
|
+
listeners: new Set(),
|
|
24
|
+
}
|
|
25
|
+
const publishPluginRuntimeState = () => {
|
|
26
|
+
pluginRuntimeState.uncertainInstallsRef.current = Object.fromEntries(pluginRuntimeState.uncertainInstalls)
|
|
27
|
+
for (const listener of [...pluginRuntimeState.listeners]) listener()
|
|
28
|
+
}
|
|
29
|
+
const setPluginRuntimeServiceScope = (serviceUrl) => {
|
|
30
|
+
const nextServiceUrl = normalizeServiceUrl(serviceUrl) || ''
|
|
31
|
+
if (nextServiceUrl === pluginRuntimeState.serviceUrl) return
|
|
32
|
+
pluginRuntimeState.serviceUrl = nextServiceUrl
|
|
33
|
+
pluginRuntimeState.scopeRevision += 1
|
|
34
|
+
pluginRuntimeState.completedId = ''
|
|
35
|
+
pluginRuntimeState.message = pluginRuntimeState.workingId ? pluginScopeBusyMessage : ''
|
|
36
|
+
pluginRuntimeState.messagePluginId.current = ''
|
|
37
|
+
pluginRuntimeState.messagePackageName.current = ''
|
|
38
|
+
pluginRuntimeState.messageClearOnResolve.current = false
|
|
39
|
+
pluginRuntimeState.refreshMessage = ''
|
|
40
|
+
pluginRuntimeState.refreshMessageIsError = false
|
|
41
|
+
pluginRuntimeState.messageVersion.current += 1
|
|
42
|
+
pluginRuntimeState.refreshGeneration.current += 1
|
|
43
|
+
pluginRuntimeState.refreshRevision += 1
|
|
44
|
+
publishPluginRuntimeState()
|
|
45
|
+
}
|
|
15
46
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')
|
|
16
47
|
const compileLocalePattern = (source) => {
|
|
17
48
|
const names = []
|
|
@@ -54,6 +85,45 @@ window.__ModuleLoader__.load({
|
|
|
54
85
|
}
|
|
55
86
|
return createElement(type, localizedProps, ...children.map(localizedChild))
|
|
56
87
|
}
|
|
88
|
+
const releaseNoteBlocks = (value) => {
|
|
89
|
+
const blocks = []
|
|
90
|
+
let paragraph = []
|
|
91
|
+
let list = []
|
|
92
|
+
let key = 0
|
|
93
|
+
const flushParagraph = () => {
|
|
94
|
+
if (!paragraph.length) return
|
|
95
|
+
blocks.push(h('p', { key: key++, style: { ...mutedTextStyle, whiteSpace: 'pre-line' } },
|
|
96
|
+
rawLocaleText(paragraph.join('\n'))))
|
|
97
|
+
paragraph = []
|
|
98
|
+
}
|
|
99
|
+
const flushList = () => {
|
|
100
|
+
if (!list.length) return
|
|
101
|
+
blocks.push(h('ul', { key: key++, style: { margin: 0, paddingLeft: 22, display: 'grid', gap: 4,
|
|
102
|
+
color: 'var(--dsw-alias-label-secondary)', fontSize: 13, lineHeight: 1.6 } },
|
|
103
|
+
...list.map((item) => h('li', { key: item.key }, rawLocaleText(item.text)))))
|
|
104
|
+
list = []
|
|
105
|
+
}
|
|
106
|
+
for (const line of String(value ?? '').replace(/\r\n?/gu, '\n').split('\n')) {
|
|
107
|
+
const heading = /^\s{0,3}(#{1,3})\s+(.+)$/u.exec(line)
|
|
108
|
+
const bullet = /^\s*(?:[-*•]|\d+[.)])\s+(.+)$/u.exec(line)
|
|
109
|
+
if (heading) {
|
|
110
|
+
flushParagraph(); flushList()
|
|
111
|
+
const level = heading[1].length
|
|
112
|
+
const tag = level === 1 ? 'h3' : level === 2 ? 'h4' : 'h5'
|
|
113
|
+
blocks.push(h(tag, { key: key++, style: { margin: 0,
|
|
114
|
+
fontSize: level === 1 ? 15 : 13, lineHeight: 1.45 } }, rawLocaleText(heading[2].trim())))
|
|
115
|
+
} else if (bullet) {
|
|
116
|
+
flushParagraph()
|
|
117
|
+
list.push({ key: key++, text: bullet[1].trim() })
|
|
118
|
+
} else if (!line.trim()) {
|
|
119
|
+
flushParagraph(); flushList()
|
|
120
|
+
} else {
|
|
121
|
+
flushList(); paragraph.push(line)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
flushParagraph(); flushList()
|
|
125
|
+
return blocks
|
|
126
|
+
}
|
|
57
127
|
const confirmLocalized = (message) => window.confirm(translateLocaleText(message))
|
|
58
128
|
const promptLocalized = (message, ...args) => window.prompt(translateLocaleText(message), ...args)
|
|
59
129
|
const useLocaleRevision = () => {
|
|
@@ -444,6 +514,7 @@ window.__ModuleLoader__.load({
|
|
|
444
514
|
if (result.service_url != null && !pinned) throw new Error()
|
|
445
515
|
if (pinned) {
|
|
446
516
|
if (current()) {
|
|
517
|
+
setPluginRuntimeServiceScope(pinned)
|
|
447
518
|
setBinding({ state: 'ready', url: pinned })
|
|
448
519
|
setServiceUrl(pinned)
|
|
449
520
|
setDraft(pinned)
|
|
@@ -494,6 +565,7 @@ window.__ModuleLoader__.load({
|
|
|
494
565
|
}
|
|
495
566
|
try {
|
|
496
567
|
localStorage.setItem(SERVICE_URL_STORAGE_KEY, normalized)
|
|
568
|
+
setPluginRuntimeServiceScope(normalized)
|
|
497
569
|
setServiceUrl(normalized)
|
|
498
570
|
setDraft(normalized)
|
|
499
571
|
setMessage('团队服务地址已保存。')
|
|
@@ -509,6 +581,7 @@ window.__ModuleLoader__.load({
|
|
|
509
581
|
const bindingReadyForSummary = binding.state === 'ready' && Boolean(binding.url)
|
|
510
582
|
const onConnectionChange = (url) => {
|
|
511
583
|
bindingRevision.current += 1
|
|
584
|
+
setPluginRuntimeServiceScope(url || '')
|
|
512
585
|
setBinding({ state: 'ready', url: url || null })
|
|
513
586
|
setDelegationRevision((value) => value + 1)
|
|
514
587
|
if (url) {
|
|
@@ -622,15 +695,18 @@ window.__ModuleLoader__.load({
|
|
|
622
695
|
const [defaults, setDefaults] = useState(null)
|
|
623
696
|
const [localConcurrency, setLocalConcurrency] = useState(null)
|
|
624
697
|
const [repositoryCacheLimitGib, setRepositoryCacheLimitGib] = useState(10)
|
|
698
|
+
const [ownerChallengeCode, setOwnerChallengeCode] = useState('')
|
|
699
|
+
const ownerChallengeMetaRef = useRef(null)
|
|
700
|
+
const ownerChallengeTimerRef = useRef(null)
|
|
625
701
|
const [botId, setBotId] = useState('')
|
|
702
|
+
const botIdRef = useRef('')
|
|
626
703
|
const [conversationKind, setConversationKind] = useState('dm')
|
|
627
704
|
const [groupId, setGroupId] = useState('')
|
|
628
705
|
const [ruleDescription, setRuleDescription] = useState('')
|
|
629
706
|
const recognizedBaselineRef = useRef(null)
|
|
630
707
|
const recognizedDescriptionRef = useRef(null)
|
|
631
|
-
const autoAnalyzeDescriptionRef = useRef(null)
|
|
632
708
|
const recognitionRequestRef = useRef(0)
|
|
633
|
-
const
|
|
709
|
+
const recognitionControllerRef = useRef(null)
|
|
634
710
|
const [recognitionBusy, setRecognitionBusy] = useState(false)
|
|
635
711
|
const [recognitionMessage, setRecognitionMessage] = useState('')
|
|
636
712
|
const [missingRequired, setMissingRequired] = useState([])
|
|
@@ -641,8 +717,31 @@ window.__ModuleLoader__.load({
|
|
|
641
717
|
const [taskPrompt, setTaskPrompt] = useState('')
|
|
642
718
|
const [runner, setRunner] = useState('dsh')
|
|
643
719
|
const [reviewers, setReviewers] = useState(['dsh'])
|
|
720
|
+
const [ownerApprovalRequired, setOwnerApprovalRequired] = useState(true)
|
|
644
721
|
const [parameters, setParameters] = useState([])
|
|
645
|
-
const
|
|
722
|
+
const cancelRecognitionRequest = () => {
|
|
723
|
+
recognitionControllerRef.current?.abort()
|
|
724
|
+
recognitionControllerRef.current = null
|
|
725
|
+
setRecognitionBusy(false)
|
|
726
|
+
}
|
|
727
|
+
const clearOwnerChallenge = () => {
|
|
728
|
+
if (ownerChallengeTimerRef.current !== null) clearTimeout(ownerChallengeTimerRef.current)
|
|
729
|
+
ownerChallengeTimerRef.current = null
|
|
730
|
+
ownerChallengeMetaRef.current = null
|
|
731
|
+
setOwnerChallengeCode('')
|
|
732
|
+
}
|
|
733
|
+
const selectBot = (nextBotId) => {
|
|
734
|
+
if (nextBotId !== botIdRef.current) {
|
|
735
|
+
clearOwnerChallenge()
|
|
736
|
+
botIdRef.current = nextBotId
|
|
737
|
+
}
|
|
738
|
+
setBotId(nextBotId)
|
|
739
|
+
}
|
|
740
|
+
useEffect(() => () => {
|
|
741
|
+
recognitionRequestRef.current += 1
|
|
742
|
+
recognitionControllerRef.current?.abort()
|
|
743
|
+
if (ownerChallengeTimerRef.current !== null) clearTimeout(ownerChallengeTimerRef.current)
|
|
744
|
+
}, [])
|
|
646
745
|
const [giteaToken, setGiteaToken] = useState('')
|
|
647
746
|
const [tokenConfigured, setTokenConfigured] = useState(false)
|
|
648
747
|
const [senderScope, setSenderScope] = useState('any')
|
|
@@ -662,9 +761,12 @@ window.__ModuleLoader__.load({
|
|
|
662
761
|
setLoading(true)
|
|
663
762
|
setMessage('')
|
|
664
763
|
try {
|
|
665
|
-
const
|
|
764
|
+
const ownerChallengeAtLoad = ownerChallengeMetaRef.current
|
|
765
|
+
const paths = ['bots', 'enrolled-bots', 'groups', 'cards', 'settings', 'executors', 'local-settings', 'status',
|
|
766
|
+
...(ownerChallengeAtLoad ? ['owner-challenge'] : [])]
|
|
666
767
|
const responses = await Promise.all(paths.map((path) =>
|
|
667
|
-
fetch(`/api/ant-teams/delegation/${path}
|
|
768
|
+
fetch(`/api/ant-teams/delegation/${path}${path === 'owner-challenge'
|
|
769
|
+
? `?bot_fingerprint=${encodeURIComponent(ownerChallengeAtLoad.fingerprint)}` : ''}`, { cache: 'no-store',
|
|
668
770
|
...(path === 'enrolled-bots' ? { headers: { 'x-ant-include-unbound': 'true' } } : {}) })
|
|
669
771
|
.catch(() => null)))
|
|
670
772
|
const data = await Promise.all(responses.map((response) => response
|
|
@@ -676,12 +778,30 @@ window.__ModuleLoader__.load({
|
|
|
676
778
|
Array.isArray,
|
|
677
779
|
(value) => value && typeof value === 'object' && !Array.isArray(value),
|
|
678
780
|
(value) => Array.isArray(value?.items),
|
|
781
|
+
...(ownerChallengeAtLoad ? [(value) => value && typeof value === 'object'
|
|
782
|
+
&& typeof value.pending === 'boolean'] : []),
|
|
679
783
|
]
|
|
680
784
|
const readable = responses.map((response, index) => Boolean(response?.ok && valid[index](data[index])))
|
|
681
|
-
setLoadFailures(paths.filter((path, index) => !readable[index]))
|
|
785
|
+
setLoadFailures(paths.filter((path, index) => !readable[index] && path !== 'owner-challenge'))
|
|
682
786
|
setBots(readable[0] ? data[0].bots : [])
|
|
683
787
|
setBotLoadError(!readable[0])
|
|
684
|
-
|
|
788
|
+
const nextEnrolled = readable[1] ? data[1] : []
|
|
789
|
+
setEnrolled(nextEnrolled)
|
|
790
|
+
const ownerChallenge = ownerChallengeAtLoad
|
|
791
|
+
if (ownerChallenge) {
|
|
792
|
+
const selectedFingerprint = readable[0] ? data[0].bots.find((bot) => bot.bot_id === botId)?.fingerprint : null
|
|
793
|
+
const enrollmentRead = readable[1]
|
|
794
|
+
const nowBound = enrollmentRead && nextEnrolled.some((enrollment) => enrollment.bot_fingerprint === ownerChallenge.fingerprint
|
|
795
|
+
&& enrollment.owner_bound)
|
|
796
|
+
const challengeStatusIndex = paths.indexOf('owner-challenge')
|
|
797
|
+
const challengePending = challengeStatusIndex >= 0 && readable[challengeStatusIndex]
|
|
798
|
+
? data[challengeStatusIndex].pending : null
|
|
799
|
+
if (ownerChallengeMetaRef.current === ownerChallenge
|
|
800
|
+
&& (readable[0] && selectedFingerprint !== ownerChallenge.fingerprint
|
|
801
|
+
|| Date.now() >= ownerChallenge.expiresAt
|
|
802
|
+
|| (!ownerChallenge.ownerWasBound && nowBound)
|
|
803
|
+
|| challengePending === false)) clearOwnerChallenge()
|
|
804
|
+
}
|
|
685
805
|
setGroups(readable[2] ? data[2] : [])
|
|
686
806
|
setCards(readable[3] ? data[3] : [])
|
|
687
807
|
setDefaults(readable[4] ? data[4] : null)
|
|
@@ -697,12 +817,16 @@ window.__ModuleLoader__.load({
|
|
|
697
817
|
useEffect(() => { void load().catch((error) => setMessage(error.message)) }, [remote, revision])
|
|
698
818
|
useEffect(() => { setMessage('') }, [activeTab])
|
|
699
819
|
const selectedBot = bots.find((bot) => bot.bot_id === botId)
|
|
820
|
+
const botPlatformLabel = (bot) => bot?.channel === 'feishu' || !bot?.channel
|
|
821
|
+
? translateLocaleText('飞书')
|
|
822
|
+
: bot.channel === 'wecom' ? translateLocaleText('企业微信') : translateLocaleText('其他 IM')
|
|
700
823
|
const botConnected = Boolean(selectedBot?.available && selectedBot?.connected)
|
|
701
824
|
const selectedEnrollment = enrolled.find((bot) => bot.bot_fingerprint === selectedBot?.fingerprint)
|
|
702
825
|
const selectedGiteaOrigin = normalizeServiceUrl(selectedEnrollment?.gitea_origin)
|
|
703
826
|
const taskPrOrigins = [...new Set(parameters.filter((item) => item.kind === 'gitea_pr')
|
|
704
827
|
.map((item) => normalizeServiceUrl(item.gitea_origin)).filter(Boolean))]
|
|
705
828
|
const selectedEnrollmentBound = selectedEnrollment?.device_bound_to_current === true
|
|
829
|
+
const ownerApprovalBound = selectedEnrollment?.owner_bound === true
|
|
706
830
|
const dmChatSettingAvailable = typeof selectedEnrollment?.dm_chat_enabled === 'boolean'
|
|
707
831
|
const dmChatEnabled = selectedEnrollment?.dm_chat_enabled !== false
|
|
708
832
|
const dmChatStatus = !selectedBot ? '先选择机器人'
|
|
@@ -714,7 +838,7 @@ window.__ModuleLoader__.load({
|
|
|
714
838
|
: !selectedEnrollmentBound ? '请先把机器人绑定到这台 DSH,再修改私聊设置。'
|
|
715
839
|
: !dmChatSettingAvailable ? '当前团队服务端尚未提供私聊开关,请先升级服务端。'
|
|
716
840
|
: dmChatEnabled
|
|
717
|
-
|
|
841
|
+
? '允许此机器人接收私聊。匹配任务卡的请求按预授权执行;没有匹配卡片的明确任务可转给已绑定主人逐次审批。'
|
|
718
842
|
: '新私聊消息不会交给 DSH;已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
|
|
719
843
|
const availableGroups = groups.filter((group) => group.bot_fingerprint === selectedBot?.fingerprint)
|
|
720
844
|
const groupLabel = (group) => {
|
|
@@ -744,9 +868,8 @@ window.__ModuleLoader__.load({
|
|
|
744
868
|
: !selectedBot ? '已有协作机器人;请选择一台,查看已开放的能力和聊天范围。'
|
|
745
869
|
: authorizationLoadError ? `“${selectedBot.name}”已启用协作;已开放任务暂未读取,请刷新状态。`
|
|
746
870
|
: !dmChatEnabled ? `“${selectedBot.name}”已启用协作;私聊已关闭,群聊仍按各自设置工作。`
|
|
747
|
-
: selectedTaskCount === 0 ? `“${selectedBot.name}
|
|
748
|
-
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount}
|
|
749
|
-
const nativeReviewerSelected = reviewers.some((item) => ['codex', 'claude-code'].includes(item))
|
|
871
|
+
: selectedTaskCount === 0 ? `“${selectedBot.name}”已启用协作;绑定审批私聊后,未配置的明确任务会先问你。常用任务可建卡直接放行。`
|
|
872
|
+
: `“${selectedBot.name}”已启用协作 · ${selectedTaskCount} 项预授权能力。其他明确任务可交给主人逐次审批。`
|
|
750
873
|
const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
|
|
751
874
|
const hasSelectedPrRule = activeCards.some((card) => card.bot_fingerprint === selectedBot?.fingerprint
|
|
752
875
|
&& Array.isArray(card.parameters) && card.parameters.some((rule) => rule.kind === 'gitea_pr'))
|
|
@@ -756,17 +879,25 @@ window.__ModuleLoader__.load({
|
|
|
756
879
|
const codexAvailable = executors.some((item) => item.id === 'codex' && item.available)
|
|
757
880
|
const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
|
|
758
881
|
const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
|
|
759
|
-
const
|
|
882
|
+
const executorUnavailableReason = executors.find((item) => item.reason)?.reason || null
|
|
760
883
|
const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
|
|
884
|
+
const usesExternalExecutor = reviewers.some((id) => ['codex', 'claude-code'].includes(id))
|
|
761
885
|
const cardReviewerSummary = (card) => {
|
|
762
886
|
const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
|
|
763
|
-
const unavailable = selected.filter((runner) => runner
|
|
764
|
-
if (card.runner
|
|
887
|
+
const unavailable = selected.filter((runner) => !executors.some((item) => item.id === runner && item.available))
|
|
888
|
+
if (unavailable.includes(card.runner)) return translateLocaleText('{executor} 默认执行方暂不可用',
|
|
765
889
|
{ executor: reviewerLabel(card.runner) })
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
890
|
+
const additional = selected.filter((runner) => runner !== card.runner)
|
|
891
|
+
const selectedSummary = additional.length
|
|
892
|
+
? translateLocaleText('默认 {executor};交叉审查:{reviewers}',
|
|
893
|
+
{ executor: reviewerLabel(card.runner), reviewers: additional.map(reviewerLabel).join('、') })
|
|
894
|
+
: translateLocaleText('{executor} 默认执行', { executor: reviewerLabel(card.runner) })
|
|
895
|
+
const summary = unavailable.length
|
|
896
|
+
? `${selectedSummary};${unavailable.map(reviewerLabel).join('、')} 暂不可用`
|
|
897
|
+
: selectedSummary
|
|
898
|
+
return card.owner_approval_required !== false
|
|
899
|
+
&& selected.some((id) => ['codex', 'claude-code'].includes(id))
|
|
900
|
+
? translateLocaleText('{summary};启动前需要主人审批', { summary }) : summary
|
|
770
901
|
}
|
|
771
902
|
const requesterSummary = senderScope === 'allowlist'
|
|
772
903
|
? translateLocaleText('仅指定成员:{member_ids}',
|
|
@@ -775,8 +906,10 @@ window.__ModuleLoader__.load({
|
|
|
775
906
|
const resultRouteSummary = translateLocaleText(conversationKind === 'group'
|
|
776
907
|
? '回复到原群(群内可见)' : '回复到发起任务的私聊')
|
|
777
908
|
const ruleSummaryCurrent = recognizedDescriptionRef.current === ruleDescription.trim()
|
|
778
|
-
const ruleSummaryPending =
|
|
779
|
-
const
|
|
909
|
+
const ruleSummaryPending = recognitionBusy
|
|
910
|
+
const ruleSummaryDirty = Boolean(ruleDescription.trim() && !ruleSummaryCurrent)
|
|
911
|
+
const ruleSummaryReady = Boolean(!ruleSummaryPending && ruleSummaryCurrent
|
|
912
|
+
&& (title || intent || parameters.length || taskPrompt))
|
|
780
913
|
const missingHarnessInputs = [...new Set([
|
|
781
914
|
...missingRequired,
|
|
782
915
|
...(parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
@@ -814,21 +947,22 @@ window.__ModuleLoader__.load({
|
|
|
814
947
|
const executorSummary = translateLocaleText(runner === 'codex' ? 'DSH Codex 子 Agent'
|
|
815
948
|
: runner === 'claude-code' ? 'DSH Claude Code 子 Agent' : 'DSH · 当前授权模型'
|
|
816
949
|
)
|
|
817
|
-
const parallelAgentsSummary = reviewers.filter((item) => item !== runner).map(reviewerLabel).join('、')
|
|
818
950
|
const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
|
|
819
951
|
: !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
|
|
820
952
|
: loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 3
|
|
821
953
|
? '团队服务端尚未支持 AI 任务 Harness 与运行时授权核验;请先升级 ANT 服务端。'
|
|
822
954
|
: !ruleDescription.trim() ? '请先用自然语言描述要做的任务。'
|
|
955
|
+
: recognitionBusy ? 'AI 正在检查当前描述;分析完成后再开放。'
|
|
823
956
|
: recognizedDescriptionRef.current !== ruleDescription.trim()
|
|
824
|
-
? '
|
|
957
|
+
? '描述已修改,请点击“立即分析”更新任务理解后再开放。'
|
|
825
958
|
: !title.trim() || !intent.trim() || !taskPrompt.trim() ? '等待 AI 生成任务理解和处理要求。'
|
|
826
|
-
:
|
|
959
|
+
: usesExternalExecutor && ownerApprovalRequired && !ownerApprovalBound
|
|
960
|
+
? '这项任务需要主人审批,请先绑定审批私聊,或取消“启动前需要主人审批”。'
|
|
827
961
|
: conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
|
|
828
962
|
: conversationKind === 'group' && !groupId ? '请在“请求范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
|
|
829
963
|
: senderScope === 'allowlist' && !senderIds.trim() ? '请填写可请求成员的 IM ID。'
|
|
830
|
-
|
|
831
|
-
?
|
|
964
|
+
: reviewers.some((item) => !executors.some((executor) => executor.id === item && executor.available))
|
|
965
|
+
? `这台 DSH 尚未启用所选执行器。${executorUnavailableReason || '请先启用对应 DSH 子 Agent 提供方。'}`
|
|
832
966
|
: parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
|
|
833
967
|
: parameters.some((rule) => rule.kind === 'gitea_pr'
|
|
834
968
|
&& (typeof rule.gitea_origin !== 'string' || !normalizeServiceUrl(rule.gitea_origin) || !rule.repository))
|
|
@@ -838,11 +972,6 @@ window.__ModuleLoader__.load({
|
|
|
838
972
|
? `${translateLocaleText('任务描述中的 Gitea 来源必须与此机器人的连接器主机一致({origin});为保护令牌,ANT 不会向其他主机发送凭据。', { origin: selectedGiteaOrigin })} ${translateLocaleText('请在描述中改用该主机下的 PR 链接并重新分析。')}`
|
|
839
973
|
: taskPrOrigins.length > 1
|
|
840
974
|
? '同一任务中的 PR 资源必须来自同一个 Gitea 主机。'
|
|
841
|
-
: nativeReviewerSelected && (parameters.filter((rule) => rule.kind === 'gitea_pr').length !== 1
|
|
842
|
-
|| parameters.some((rule) => !['gitea_pr', 'git_sha'].includes(rule.kind)))
|
|
843
|
-
? 'Codex 和 Claude Code 当前只支持一个 Gitea PR 资源连接器;任务具体做审查、总结或解释仍由自然语言描述决定。'
|
|
844
|
-
: nativeReviewerSelected && !parameters.find((rule) => rule.kind === 'gitea_pr')?.required
|
|
845
|
-
? '使用本机 Provider 时,Gitea PR 资源必须是必填项。'
|
|
846
975
|
: reviewers.includes('dsh') && !dshAvailable
|
|
847
976
|
? '这台 DSH 尚未准备好任务执行器。'
|
|
848
977
|
: ''
|
|
@@ -856,10 +985,8 @@ window.__ModuleLoader__.load({
|
|
|
856
985
|
if (!next.includes(runner)) setRunner(next[0])
|
|
857
986
|
}
|
|
858
987
|
const selectRunner = (id) => {
|
|
988
|
+
if (!reviewers.includes(id)) return
|
|
859
989
|
setRunner(id)
|
|
860
|
-
setReviewers((current) => current.length > 1
|
|
861
|
-
? [...new Set([...current, id])]
|
|
862
|
-
: [id])
|
|
863
990
|
}
|
|
864
991
|
const startNewCard = (context = {}) => {
|
|
865
992
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
@@ -868,16 +995,16 @@ window.__ModuleLoader__.load({
|
|
|
868
995
|
setRecognitionMessage('')
|
|
869
996
|
recognizedBaselineRef.current = null
|
|
870
997
|
recognizedDescriptionRef.current = null
|
|
871
|
-
|
|
998
|
+
cancelRecognitionRequest()
|
|
872
999
|
recognitionRequestRef.current += 1
|
|
873
1000
|
setRuleDescription('')
|
|
874
1001
|
setMissingRequired([]); setSuggestions([]); setRuleExamples([])
|
|
875
1002
|
setTitle(''); setIntent(''); setTaskPrompt(''); setRunner('dsh')
|
|
876
|
-
setReviewers(['dsh']);
|
|
1003
|
+
setReviewers(['dsh']); setOwnerApprovalRequired(true)
|
|
877
1004
|
setParameters([]); setConversationKind(context.conversationKind || 'dm')
|
|
878
1005
|
setGroupId(context.groupId || '')
|
|
879
1006
|
setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
|
|
880
|
-
if (context.botId)
|
|
1007
|
+
if (context.botId) selectBot(context.botId)
|
|
881
1008
|
}
|
|
882
1009
|
const taskRuleDraftFromCard = (card, title) => {
|
|
883
1010
|
const rules = (card.parameters || []).map((rule) => ({
|
|
@@ -901,9 +1028,10 @@ window.__ModuleLoader__.load({
|
|
|
901
1028
|
}
|
|
902
1029
|
const editCard = (card, copy = false) => {
|
|
903
1030
|
setSubmissionError('')
|
|
904
|
-
|
|
1031
|
+
const cardBotId = card.bot_id || bots.find((bot) => bot.fingerprint === card.bot_fingerprint)?.bot_id || ''
|
|
1032
|
+
selectBot(cardBotId)
|
|
905
1033
|
setRecognitionMessage('')
|
|
906
|
-
|
|
1034
|
+
cancelRecognitionRequest()
|
|
907
1035
|
recognitionRequestRef.current += 1
|
|
908
1036
|
setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
|
|
909
1037
|
const taskDraft = taskRuleDraftFromCard(card, copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
|
|
@@ -911,7 +1039,7 @@ window.__ModuleLoader__.load({
|
|
|
911
1039
|
const existingDescription = card.rule_description || ''
|
|
912
1040
|
recognizedDescriptionRef.current = existingDescription.trim() || null
|
|
913
1041
|
setDraftNotice(existingDescription.trim() ? ''
|
|
914
|
-
: '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
|
|
1042
|
+
: translateLocaleText('这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。'))
|
|
915
1043
|
setRuleDescription(existingDescription)
|
|
916
1044
|
setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
|
|
917
1045
|
setTitle(taskDraft.title)
|
|
@@ -919,7 +1047,7 @@ window.__ModuleLoader__.load({
|
|
|
919
1047
|
setIntent(taskDraft.intent)
|
|
920
1048
|
setTaskPrompt(taskDraft.task_prompt); setRunner(card.runner)
|
|
921
1049
|
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
|
|
922
|
-
|
|
1050
|
+
setOwnerApprovalRequired(card.owner_approval_required !== false)
|
|
923
1051
|
setParameters(taskDraft.parameters)
|
|
924
1052
|
setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
|
|
925
1053
|
setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
|
|
@@ -940,13 +1068,18 @@ window.__ModuleLoader__.load({
|
|
|
940
1068
|
setRecognitionMessage('先描述这项任务何时触发、要做什么以及涉及哪些资料。')
|
|
941
1069
|
return
|
|
942
1070
|
}
|
|
943
|
-
|
|
1071
|
+
cancelRecognitionRequest()
|
|
944
1072
|
const requestNumber = ++recognitionRequestRef.current
|
|
1073
|
+
const controller = new AbortController()
|
|
1074
|
+
let timedOut = false
|
|
1075
|
+
recognitionControllerRef.current = controller
|
|
1076
|
+
const timeout = setTimeout(() => { timedOut = true; controller.abort() }, 190_000)
|
|
945
1077
|
setRecognitionBusy(true); setMessage(''); setRecognitionMessage('AI 正在分析必需范围、可选条件和执行方式…')
|
|
946
1078
|
try {
|
|
947
1079
|
const response = await fetch('/api/ant-teams/delegation/recognize', {
|
|
948
1080
|
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
949
1081
|
body: JSON.stringify({ description: requestedDescription }),
|
|
1082
|
+
signal: controller.signal,
|
|
950
1083
|
})
|
|
951
1084
|
const result = await readLocalJsonResponse(response)
|
|
952
1085
|
if (!response.ok) throw new Error(result.message || '规则识别失败。')
|
|
@@ -996,19 +1129,14 @@ window.__ModuleLoader__.load({
|
|
|
996
1129
|
? 'AI 已更新任务理解。请复核必需信息、可选建议和运行方式;任务仍未开放。'
|
|
997
1130
|
: 'AI 已生成待复核的任务 Harness。检查摘要和范围后再开放。')
|
|
998
1131
|
} catch (error) {
|
|
999
|
-
if (requestNumber === recognitionRequestRef.current) setRecognitionMessage(
|
|
1132
|
+
if (requestNumber === recognitionRequestRef.current) setRecognitionMessage(timedOut
|
|
1133
|
+
? translateLocaleText('AI 分析等待超时,请检查团队模型后重试。') : error.message)
|
|
1000
1134
|
} finally {
|
|
1135
|
+
clearTimeout(timeout)
|
|
1136
|
+
if (recognitionControllerRef.current === controller) recognitionControllerRef.current = null
|
|
1001
1137
|
if (requestNumber === recognitionRequestRef.current) setRecognitionBusy(false)
|
|
1002
1138
|
}
|
|
1003
1139
|
}
|
|
1004
|
-
useEffect(() => {
|
|
1005
|
-
if (autoAnalyzeDescriptionRef.current !== ruleDescription || !ruleDescription.trim()) return undefined
|
|
1006
|
-
const description = ruleDescription
|
|
1007
|
-
recognitionTimerRef.current = setTimeout(() => {
|
|
1008
|
-
if (autoAnalyzeDescriptionRef.current === description) void identifyRule(description)
|
|
1009
|
-
}, 900)
|
|
1010
|
-
return () => clearTimeout(recognitionTimerRef.current)
|
|
1011
|
-
}, [ruleDescription])
|
|
1012
1140
|
const saveGiteaToken = async () => {
|
|
1013
1141
|
if (!giteaToken.trim()) return
|
|
1014
1142
|
setBusy(true)
|
|
@@ -1033,10 +1161,41 @@ window.__ModuleLoader__.load({
|
|
|
1033
1161
|
const result = await readLocalJsonResponse(response)
|
|
1034
1162
|
if (!response.ok) throw new Error(result.message || '协作机器人启用失败。')
|
|
1035
1163
|
await load()
|
|
1036
|
-
setMessage('
|
|
1164
|
+
setMessage('协作机器人已启用。任务卡可预授权固定任务;绑定审批私聊后,没有匹配卡片的明确任务会逐次询问主人。')
|
|
1037
1165
|
} catch (error) { setMessage(error.message) }
|
|
1038
1166
|
finally { setBusy(false) }
|
|
1039
1167
|
}
|
|
1168
|
+
const requestOwnerApprovalBinding = async () => {
|
|
1169
|
+
if (!selectedBot || !selectedEnrollmentBound) return
|
|
1170
|
+
const requestedBot = selectedBot
|
|
1171
|
+
setBusy(true); clearOwnerChallenge()
|
|
1172
|
+
try {
|
|
1173
|
+
const response = await fetch('/api/ant-teams/delegation/owner-challenge', {
|
|
1174
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
1175
|
+
body: JSON.stringify({ bot_id: requestedBot.bot_id, bot_fingerprint: requestedBot.fingerprint }),
|
|
1176
|
+
})
|
|
1177
|
+
const result = await readLocalJsonResponse(response)
|
|
1178
|
+
if (botIdRef.current !== requestedBot.bot_id) {
|
|
1179
|
+
await fetch(`/api/ant-teams/delegation/owner-challenge?bot_fingerprint=${encodeURIComponent(requestedBot.fingerprint)}`,
|
|
1180
|
+
{ method: 'DELETE', cache: 'no-store' }).catch(() => null)
|
|
1181
|
+
return
|
|
1182
|
+
}
|
|
1183
|
+
if (!response.ok || typeof result.code !== 'string') throw new Error(result.message || '审批私聊绑定码获取失败。')
|
|
1184
|
+
// The server owns challenge validity. Use a response-relative local display window so clock skew
|
|
1185
|
+
// cannot make a valid server challenge disappear before the owner can use it.
|
|
1186
|
+
const expiresAt = Date.now() + 5 * 60_000
|
|
1187
|
+
const ownerChallenge = { fingerprint: requestedBot.fingerprint, ownerWasBound: ownerApprovalBound, expiresAt }
|
|
1188
|
+
ownerChallengeMetaRef.current = ownerChallenge
|
|
1189
|
+
setOwnerChallengeCode(result.code)
|
|
1190
|
+
ownerChallengeTimerRef.current = setTimeout(() => {
|
|
1191
|
+
if (ownerChallengeMetaRef.current !== ownerChallenge) return
|
|
1192
|
+
clearOwnerChallenge()
|
|
1193
|
+
setMessage(translateLocaleText('绑定码已过期,请重新生成。'))
|
|
1194
|
+
}, Math.max(0, expiresAt - Date.now()))
|
|
1195
|
+
setMessage('绑定码已生成;请按“私聊入口”中的提示完成绑定。')
|
|
1196
|
+
} catch (error) { if (botIdRef.current === requestedBot.bot_id) setMessage(error.message) }
|
|
1197
|
+
finally { setBusy(false) }
|
|
1198
|
+
}
|
|
1040
1199
|
const saveGroupChat = async (enabled, targetGroupId) => {
|
|
1041
1200
|
if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
|
|
1042
1201
|
const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
|
|
@@ -1131,6 +1290,15 @@ window.__ModuleLoader__.load({
|
|
|
1131
1290
|
...collaborationGridStyle, gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))',
|
|
1132
1291
|
}
|
|
1133
1292
|
const collaborationCardStyle = { ...settingsPanelStyle, padding: 12, gap: 10 }
|
|
1293
|
+
const capabilityStepStyle = { display: 'grid', gap: 12, borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 14 }
|
|
1294
|
+
const capabilityStepHeading = (number, title, description) => h('div', { style: {
|
|
1295
|
+
display: 'flex', gap: 10, alignItems: 'flex-start',
|
|
1296
|
+
} },
|
|
1297
|
+
h('span', { style: { display: 'grid', placeItems: 'center', width: 26, height: 26, flex: '0 0 auto',
|
|
1298
|
+
borderRadius: '50%', backgroundColor: 'var(--dsw-alias-state-business-tertiary)',
|
|
1299
|
+
color: 'var(--dsw-alias-state-business-primary)', fontWeight: 700 } }, number),
|
|
1300
|
+
h('div', { style: { display: 'grid', gap: 3 } },
|
|
1301
|
+
h('strong', null, title), h('p', { style: mutedTextStyle }, description)))
|
|
1134
1302
|
const setupStepper = h('section', { 'aria-label': '首次设置引导', style: { display: 'grid', gap: 6 } },
|
|
1135
1303
|
h('p', { style: mutedTextStyle }, '首次设置可按顺序完成;你也可以直接切换页面。普通聊天不需要先开放任务。'),
|
|
1136
1304
|
h('ol', { 'aria-label': '设置步骤', style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 150px), 1fr))',
|
|
@@ -1163,7 +1331,26 @@ window.__ModuleLoader__.load({
|
|
|
1163
1331
|
disabled: busy || !selectedBot || !selectedEnrollmentBound || enrollmentLoadError || !dmChatSettingAvailable,
|
|
1164
1332
|
onChange: (event) => void saveDmChat(event.target.checked), 'aria-label': '允许机器人私聊' }),
|
|
1165
1333
|
h('strong', null, '允许私聊普通聊天'), h('span', { style: mutedTextStyle }, dmChatStatus)),
|
|
1166
|
-
h('p', { style: mutedTextStyle }, dmChatDescription)
|
|
1334
|
+
h('p', { style: mutedTextStyle }, dmChatDescription),
|
|
1335
|
+
selectedEnrollmentBound && botConnected ? h('section', { role: 'group', 'aria-label': '主人审批私聊', style: {
|
|
1336
|
+
display: 'grid', gap: 9, padding: 12, border: '1px solid var(--dsw-alias-state-business-tertiary)',
|
|
1337
|
+
borderRadius: 10, backgroundColor: 'var(--dsw-alias-state-business-tertiary)',
|
|
1338
|
+
} },
|
|
1339
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
1340
|
+
h('strong', null, '主人审批私聊'),
|
|
1341
|
+
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1342
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-1)' } }, ownerApprovalBound ? '已绑定' : '未绑定')),
|
|
1343
|
+
h('p', { style: mutedTextStyle }, ownerApprovalBound
|
|
1344
|
+
? '没有匹配任务卡的明确任务会先发给你逐次审批;批准前不会启动执行器。'
|
|
1345
|
+
: '绑定一次后,没有匹配任务卡的明确任务会先发给你审批。发送验证码后,点击“刷新状态”确认绑定。'),
|
|
1346
|
+
h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1347
|
+
h('button', { type: 'button', disabled: busy,
|
|
1348
|
+
onClick: () => void requestOwnerApprovalBinding(), style: secondaryButtonStyle },
|
|
1349
|
+
ownerApprovalBound ? '重新绑定审批私聊' : '生成绑定码'),
|
|
1350
|
+
ownerChallengeCode ? h('span', { style: mutedTextStyle }, '有效期 5 分钟') : null),
|
|
1351
|
+
ownerChallengeCode ? h('div', { role: 'status', style: { ...feedbackNoticeStyle, position: 'static' } },
|
|
1352
|
+
translateLocaleText('请在 5 分钟内私聊此机器人发送完整指令:“绑定审批身份 {code}”。如果这台 DSH 未重启,也可直接发送 12 位验证码 {code}。',
|
|
1353
|
+
{ code: ownerChallengeCode })) : null) : null)
|
|
1167
1354
|
const createCard = async (event) => {
|
|
1168
1355
|
event.preventDefault()
|
|
1169
1356
|
setDraftNotice('')
|
|
@@ -1173,14 +1360,11 @@ window.__ModuleLoader__.load({
|
|
|
1173
1360
|
setSubmissionError(localizedReason); setMessage(localizedReason)
|
|
1174
1361
|
}
|
|
1175
1362
|
if (!selectedBot || !botConnected || !selectedEnrollmentBound) { reject('请先在这台 DSH 绑定选中的协作机器人。'); return }
|
|
1176
|
-
if (reviewers.some((item) => item !== 'dsh')) {
|
|
1177
|
-
reject('当前无法核验外部子 Agent 的逐任务文件、Git 和网络权限;请将任务执行方设为 DSH。'); return
|
|
1178
|
-
}
|
|
1179
1363
|
if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
|
|
1180
1364
|
reject('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
|
|
1181
1365
|
}
|
|
1182
1366
|
if (!ruleDescription.trim() || !title.trim() || !intent.trim() || !taskPrompt.trim()) {
|
|
1183
|
-
reject('
|
|
1367
|
+
reject('请填写任务描述并点击“立即分析”,检查摘要后再开放。'); return
|
|
1184
1368
|
}
|
|
1185
1369
|
const bytes = (value) => new TextEncoder().encode(value).length
|
|
1186
1370
|
if (bytes(ruleDescription.trim()) > 4000 || bytes(title.trim()) > 100
|
|
@@ -1245,12 +1429,6 @@ window.__ModuleLoader__.load({
|
|
|
1245
1429
|
if (!reviewers.length || !reviewers.includes(runner)) {
|
|
1246
1430
|
reject('请至少选择一位任务执行 Agent,并设置普通请求的默认执行方。'); return
|
|
1247
1431
|
}
|
|
1248
|
-
if (nativeReviewerSelected && !((normalized.length === 1
|
|
1249
|
-
&& normalized[0].kind === 'gitea_pr' && normalized[0].required)
|
|
1250
|
-
|| (normalized.length === 2 && normalized.some((rule) => rule.kind === 'gitea_pr' && rule.required)
|
|
1251
|
-
&& normalized.some((rule) => rule.kind === 'git_sha' && rule.name === 'head_sha' && !rule.required)))) {
|
|
1252
|
-
reject('Codex 和 Claude Code 当前只支持一个必填 Gitea PR 资源连接器;任务行为仍由自然语言描述决定。'); return
|
|
1253
|
-
}
|
|
1254
1432
|
const scope = conversationKind === 'group'
|
|
1255
1433
|
? translateLocaleText('群 {group_id}', { group_id: groupId })
|
|
1256
1434
|
: translateLocaleText('私聊')
|
|
@@ -1258,8 +1436,8 @@ window.__ModuleLoader__.load({
|
|
|
1258
1436
|
? '原群(群内可见)' : '发起任务的私聊')
|
|
1259
1437
|
const executionDetails = {
|
|
1260
1438
|
dsh: translateLocaleText('DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。'),
|
|
1261
|
-
codex: translateLocaleText('Codex
|
|
1262
|
-
'claude-code': translateLocaleText('Claude Code
|
|
1439
|
+
codex: translateLocaleText('Codex 使用这台 DSH 的 Provider Profile 权限;可访问范围由 Codex/DSH 本机配置决定。任务描述不会限制操作系统权限。'),
|
|
1440
|
+
'claude-code': translateLocaleText('Claude Code 使用这台 DSH 的 Provider Profile 权限;可访问范围由 Claude Code/DSH 本机配置决定。任务描述不会限制操作系统权限。'),
|
|
1263
1441
|
}
|
|
1264
1442
|
const executionInfo = reviewers.length > 1
|
|
1265
1443
|
? translateLocaleText('普通请求默认由 {runner} 执行;请求人明确要求多 Agent 分别处理时,会并行启动 {reviewers},分别返回结果。{details}',
|
|
@@ -1280,7 +1458,8 @@ window.__ModuleLoader__.load({
|
|
|
1280
1458
|
bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
|
|
1281
1459
|
title, rule_description: ruleDescription.trim(), trigger_phrases: [],
|
|
1282
1460
|
intent, examples: ruleExamples,
|
|
1283
|
-
parameters: normalized, task_prompt: taskPrompt, runner, reviewers,
|
|
1461
|
+
parameters: normalized, task_prompt: taskPrompt, runner, reviewers,
|
|
1462
|
+
owner_approval_required: ownerApprovalRequired, workspace: null,
|
|
1284
1463
|
sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
|
|
1285
1464
|
? senderIds.split(/[\s,,]+/u).filter(Boolean) : [], conversation_kind: conversationKind,
|
|
1286
1465
|
...(conversationKind === 'group' ? { conversation_id: groupId } : {}),
|
|
@@ -1288,7 +1467,7 @@ window.__ModuleLoader__.load({
|
|
|
1288
1467
|
})
|
|
1289
1468
|
const result = await readLocalJsonResponse(response)
|
|
1290
1469
|
if (!response.ok && result.code === 'IM_TASK_RULE_REQUIRED') {
|
|
1291
|
-
throw new Error('当前 ANT
|
|
1470
|
+
throw new Error('当前 ANT 服务端版本过旧,请升级服务端后再开放任务。')
|
|
1292
1471
|
}
|
|
1293
1472
|
if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
|
|
1294
1473
|
throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
|
|
@@ -1296,26 +1475,35 @@ window.__ModuleLoader__.load({
|
|
|
1296
1475
|
if (!response.ok) throw new Error(result.message || '能力保存失败。')
|
|
1297
1476
|
const savedReviewers = Array.isArray(result.reviewers) && result.reviewers.length
|
|
1298
1477
|
? result.reviewers : [result.runner]
|
|
1299
|
-
|
|
1300
|
-
|
|
1478
|
+
const reviewersDropped = [...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')
|
|
1479
|
+
const approvalDropped = usesExternalExecutor && ownerApprovalRequired && result.owner_approval_required !== true
|
|
1480
|
+
if (reviewersDropped || approvalDropped) {
|
|
1481
|
+
if ((!editingCardId || approvalDropped) && typeof result.id === 'string') {
|
|
1301
1482
|
const cleanup = await fetch('/api/ant-teams/delegation/cards', {
|
|
1302
1483
|
method: 'DELETE', headers: { 'content-type': 'application/json' },
|
|
1303
1484
|
body: JSON.stringify({ id: result.id }),
|
|
1304
1485
|
}).catch(() => null)
|
|
1305
1486
|
if (!cleanup?.ok) {
|
|
1306
1487
|
await load().catch(() => undefined)
|
|
1307
|
-
throw new Error(
|
|
1488
|
+
throw new Error(reviewersDropped
|
|
1489
|
+
? '团队服务端尚不支持多 Agent 审查配置;旧服务端可能已保存单 Agent 授权,请刷新列表并停止它,再升级 ANT 服务端。'
|
|
1490
|
+
: '团队服务端尚不支持任务卡主人审批;旧服务端可能已保存不经审批的授权,请刷新列表并停止它,再升级 ANT 服务端。')
|
|
1491
|
+
}
|
|
1492
|
+
if (editingCardId) {
|
|
1493
|
+
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1494
|
+
await load().catch(() => undefined)
|
|
1308
1495
|
}
|
|
1309
1496
|
}
|
|
1310
|
-
throw new Error(
|
|
1497
|
+
throw new Error(reviewersDropped
|
|
1498
|
+
? '当前团队服务端尚不支持多 Agent 审查配置;请管理员升级 ANT 服务端后重试。'
|
|
1499
|
+
: '当前团队服务端尚不支持任务卡主人审批;请管理员升级 ANT 服务端后重试。')
|
|
1311
1500
|
}
|
|
1312
1501
|
recognizedBaselineRef.current = null
|
|
1313
1502
|
recognizedDescriptionRef.current = null
|
|
1314
|
-
|
|
1503
|
+
cancelRecognitionRequest()
|
|
1315
1504
|
recognitionRequestRef.current += 1
|
|
1316
1505
|
setRuleDescription(''); setMissingRequired([]); setSuggestions([]); setRuleExamples([])
|
|
1317
|
-
setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
|
|
1318
|
-
setParallelAgentsOpen(false)
|
|
1506
|
+
setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh']); setOwnerApprovalRequired(true)
|
|
1319
1507
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1320
1508
|
await load()
|
|
1321
1509
|
setCreateOpen(false)
|
|
@@ -1340,22 +1528,22 @@ window.__ModuleLoader__.load({
|
|
|
1340
1528
|
}
|
|
1341
1529
|
const copyCardToDraft = (card) => {
|
|
1342
1530
|
setEditingCardId(null); setEditingExpiresAt(null)
|
|
1343
|
-
setParallelAgentsOpen(false)
|
|
1344
1531
|
const copiedDescription = card.rule_description || ''
|
|
1345
1532
|
recognizedDescriptionRef.current = copiedDescription.trim() || null
|
|
1346
|
-
|
|
1533
|
+
cancelRecognitionRequest()
|
|
1347
1534
|
recognitionRequestRef.current += 1
|
|
1348
1535
|
setDraftNotice(copiedDescription.trim()
|
|
1349
|
-
? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
|
|
1350
|
-
: '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
|
|
1536
|
+
? translateLocaleText('已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。')
|
|
1537
|
+
: translateLocaleText('这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。'))
|
|
1351
1538
|
const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
|
|
1539
|
+
const copiedBotId = card.bot_id || cardBot?.bot_id || ''
|
|
1540
|
+
selectBot(copiedBotId)
|
|
1352
1541
|
const titleChars = Array.from(translateLocaleText('复制:{title}', {
|
|
1353
1542
|
title: card.title || translateLocaleText('未命名能力'),
|
|
1354
1543
|
}))
|
|
1355
1544
|
while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
|
|
1356
1545
|
const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
|
|
1357
1546
|
recognizedBaselineRef.current = taskDraft
|
|
1358
|
-
setBotId(card.bot_id || cardBot?.bot_id || '')
|
|
1359
1547
|
setRuleDescription(copiedDescription)
|
|
1360
1548
|
setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
|
|
1361
1549
|
setTitle(taskDraft.title)
|
|
@@ -1363,6 +1551,7 @@ window.__ModuleLoader__.load({
|
|
|
1363
1551
|
setTaskPrompt(taskDraft.task_prompt)
|
|
1364
1552
|
setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
|
|
1365
1553
|
setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh'])
|
|
1554
|
+
setOwnerApprovalRequired(card.owner_approval_required !== false)
|
|
1366
1555
|
setParameters(taskDraft.parameters)
|
|
1367
1556
|
setConversationKind(card.conversation_kind === 'group' ? 'group' : 'dm')
|
|
1368
1557
|
setGroupId(card.conversation_id || '')
|
|
@@ -1392,10 +1581,10 @@ window.__ModuleLoader__.load({
|
|
|
1392
1581
|
loading ? '读取中…' : '刷新状态')),
|
|
1393
1582
|
h('p', { role: 'status', style: mutedTextStyle }, collaborationStatus),
|
|
1394
1583
|
bots.length ? h('label', null, '我的机器人', h('select', { id: 'ant-bot-selector', value: botId,
|
|
1395
|
-
onChange: (event) => {
|
|
1584
|
+
onChange: (event) => { selectBot(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1396
1585
|
h('option', { value: '' }, '请选择机器人'),
|
|
1397
1586
|
...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
|
|
1398
|
-
disabled: !bot.available || !bot.connected }, rawLocaleText(bot.name),
|
|
1587
|
+
disabled: !bot.available || !bot.connected }, rawLocaleText(bot.name), ' · ', botPlatformLabel(bot),
|
|
1399
1588
|
bot.available && bot.connected ? null : '(未连接)')))) : null,
|
|
1400
1589
|
connectedBots.length === 0 && !loading && !botLoadError ? h('div', { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
|
|
1401
1590
|
h('button', { type: 'button', onClick: openImSettings, style: secondaryButtonStyle }, '打开 DSH-IM 设置'),
|
|
@@ -1404,7 +1593,9 @@ window.__ModuleLoader__.load({
|
|
|
1404
1593
|
style: secondaryButtonStyle }, '检查 DSH-IM 连接') : null,
|
|
1405
1594
|
selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
|
|
1406
1595
|
onClick: () => void enrollBot(), style: primaryButtonStyle },
|
|
1407
|
-
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null
|
|
1596
|
+
selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null,
|
|
1597
|
+
selectedEnrollmentBound && botConnected ? h('p', { style: mutedTextStyle },
|
|
1598
|
+
'任务能力和设备绑定状态请在此处查看;主人审批身份在“私聊入口”中管理。') : null)
|
|
1408
1599
|
const groupTaskStatusPanel = selectedEnrollmentBound && botConnected ? h('section', {
|
|
1409
1600
|
id: 'ant-group-task-status', role: 'region', 'aria-label': '各群聊天与任务状态', tabIndex: -1,
|
|
1410
1601
|
style: { ...collaborationCardStyle, gap: 10 } },
|
|
@@ -1415,7 +1606,7 @@ window.__ModuleLoader__.load({
|
|
|
1415
1606
|
loading ? '读取中…' : '刷新群列表')),
|
|
1416
1607
|
h('p', { style: mutedTextStyle }, groupChatLoadFailed
|
|
1417
1608
|
? '群列表暂不可用,请刷新状态后重试。'
|
|
1418
|
-
: availableGroups.length ? '
|
|
1609
|
+
: availableGroups.length ? '此开关只控制普通聊天。任务卡可预授权固定任务;绑定主人审批私聊后,群成员 @机器人发起的其他明确任务也可逐次审批。名称取自 DSH-IM 已保存目标。'
|
|
1419
1610
|
: '先在目标群 @机器人,再刷新状态发现群。'),
|
|
1420
1611
|
availableGroups.length ? h('div', { id: 'ant-group-card-grid', style: collaborationGridStyle },
|
|
1421
1612
|
...availableGroups.map((group) => {
|
|
@@ -1447,9 +1638,10 @@ window.__ModuleLoader__.load({
|
|
|
1447
1638
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'start', gap: 12, flexWrap: 'wrap' } },
|
|
1448
1639
|
h('div', { style: { display: 'grid', gap: 4 } },
|
|
1449
1640
|
h('h3', { style: sectionTitleStyle }, '协作总览'),
|
|
1450
|
-
h('p', { style: mutedTextStyle }, '
|
|
1641
|
+
h('p', { style: mutedTextStyle }, '选择机器人并绑定一次主人审批私聊。任务卡可预授权常用任务;其他请求由主人逐次决定。')),
|
|
1451
1642
|
h('span', { style: { ...mutedTextStyle, padding: '4px 9px', borderRadius: 999,
|
|
1452
|
-
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot
|
|
1643
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot
|
|
1644
|
+
? h('span', null, rawLocaleText(selectedBot.name), ' · ', botPlatformLabel(selectedBot)) : '尚未选择机器人')),
|
|
1453
1645
|
loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1454
1646
|
`部分协作信息暂时无法读取:${loadFailures.map((path) => loadNames[path]).join('、')}。已读取的信息仍显示,请稍后刷新状态。`) : null,
|
|
1455
1647
|
h('div', { id: 'ant-collaboration-settings-grid', style: collaborationSettingsGridStyle },
|
|
@@ -1485,7 +1677,10 @@ window.__ModuleLoader__.load({
|
|
|
1485
1677
|
: translateLocaleText('私聊')
|
|
1486
1678
|
const requester = translateLocaleText(card.sender_scope === 'allowlist' ? '指定成员可请求'
|
|
1487
1679
|
: card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求')
|
|
1488
|
-
const
|
|
1680
|
+
const configuredCardRunners = Array.isArray(card.reviewers) && card.reviewers.length
|
|
1681
|
+
? card.reviewers : [card.runner]
|
|
1682
|
+
const blockedExternalExecutor = !configuredCardRunners.every((runner) =>
|
|
1683
|
+
executors.some((executor) => executor.id === runner && executor.available))
|
|
1489
1684
|
const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
|
|
1490
1685
|
const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
|
|
1491
1686
|
const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
|
|
@@ -1504,7 +1699,7 @@ window.__ModuleLoader__.load({
|
|
|
1504
1699
|
})),
|
|
1505
1700
|
blockedExternalExecutor ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1506
1701
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1507
|
-
'
|
|
1702
|
+
'此任务暂不可执行:这台 DSH 尚未启用所选的全部执行器。请启用对应的 DSH 子 Agent 提供方后刷新。') : null,
|
|
1508
1703
|
card.rule_description
|
|
1509
1704
|
? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, rawLocaleText(card.rule_description))
|
|
1510
1705
|
: h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
@@ -1543,9 +1738,10 @@ window.__ModuleLoader__.load({
|
|
|
1543
1738
|
? task.repository
|
|
1544
1739
|
? translateLocaleText('{repository} PR #{pr_number} 差异审查', { repository: task.repository, pr_number: task.pr_number })
|
|
1545
1740
|
: translateLocaleText('PR #{pr_number} 差异审查', { pr_number: task.pr_number })
|
|
1546
|
-
: '任务'
|
|
1741
|
+
: translateLocaleText('任务')
|
|
1547
1742
|
const status = translateLocaleText(task.outcome === 'canceled' ? '已取消' : task.outcome === 'failed' ? '未完成' : ({
|
|
1548
|
-
pending: '排队中',
|
|
1743
|
+
pending: '排队中', claimed: '排队中', running: '处理中', finishing: '结果待核对', completed: '已完成',
|
|
1744
|
+
reply_pending: '结果待发送', reply_attempted: '回复状态待核对', done: '已回复',
|
|
1549
1745
|
}[task.status] || task.status))
|
|
1550
1746
|
const createdAt = task.created_at && Number.isFinite(Date.parse(task.created_at))
|
|
1551
1747
|
? formatLocaleDate(task.created_at) : ''
|
|
@@ -1559,7 +1755,9 @@ window.__ModuleLoader__.load({
|
|
|
1559
1755
|
h('div', { style: mutedTextStyle }, taskStatusLine),
|
|
1560
1756
|
task.failure_reason ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1561
1757
|
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
1562
|
-
'
|
|
1758
|
+
task.status === 'finishing'
|
|
1759
|
+
? '结果状态待确认:{reason}\n处理建议:恢复原团队连接或审批私聊权限后刷新状态;不要重复发起相同任务。'
|
|
1760
|
+
: '任务未完成:{reason}\n处理建议:按错误提示修复配置或请求内容后,在原群或私聊重新发起。',
|
|
1563
1761
|
{ reason: task.failure_reason }))) : null,
|
|
1564
1762
|
task.delivery_error ? h('div', { role: 'alert', style: warningNoticeStyle },
|
|
1565
1763
|
h('span', { style: { ...mutedTextStyle, whiteSpace: 'pre-line' } }, translateLocaleText(
|
|
@@ -1568,7 +1766,8 @@ window.__ModuleLoader__.load({
|
|
|
1568
1766
|
task.status === 'reply_attempted' && !task.delivery_error ? h('div', { role: 'status', style: warningNoticeStyle },
|
|
1569
1767
|
h('span', { style: mutedTextStyle }, translateLocaleText(
|
|
1570
1768
|
'ANT 尚未确认消息平台是否收到结果。请先检查原群或私聊,再决定是否重新发起。'))) : null),
|
|
1571
|
-
['card_task', 'legacy_pr_task'
|
|
1769
|
+
['card_task', 'legacy_pr_task', 'owner_approval'].includes(task.kind)
|
|
1770
|
+
&& ['pending', 'claimed', 'running'].includes(task.status) ? h('button', { type: 'button', disabled: busy,
|
|
1572
1771
|
onClick: () => void cancelTask(task), style: secondaryButtonStyle }, '取消任务') : null)
|
|
1573
1772
|
})
|
|
1574
1773
|
: [h('p', { key: 'none', style: mutedTextStyle }, loadFailures.includes('status')
|
|
@@ -1576,11 +1775,21 @@ window.__ModuleLoader__.load({
|
|
|
1576
1775
|
h('div', { role: 'region', 'aria-label': '任务规则编辑器', style: { display: activeTab === 'collaboration' ? 'grid' : 'none' } },
|
|
1577
1776
|
h('details', { id: 'ant-new-task', open: createOpen,
|
|
1578
1777
|
onToggle: (event) => setCreateOpen(event.currentTarget.open),
|
|
1579
|
-
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius:
|
|
1580
|
-
|
|
1778
|
+
style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12, padding: 16,
|
|
1779
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-1)' } },
|
|
1780
|
+
h('summary', { id: 'ant-new-task-summary', style: { display: 'flex', justifyContent: 'space-between',
|
|
1781
|
+
alignItems: 'center', gap: 12, flexWrap: 'wrap', cursor: 'pointer', listStylePosition: 'inside' } },
|
|
1782
|
+
h('span', { 'aria-hidden': 'true', style: { color: 'var(--dsw-alias-label-secondary)', flex: '0 0 auto' } },
|
|
1783
|
+
createOpen ? '▾' : '▸'),
|
|
1784
|
+
h('span', { style: { display: 'grid', gap: 3 } },
|
|
1785
|
+
h('strong', null, editingCardId ? '编辑能力配置' : '开放一项能力'),
|
|
1786
|
+
h('span', { style: mutedTextStyle }, '任务卡可预授权常用任务;其他明确任务由主人逐次审批。')),
|
|
1787
|
+
h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
|
|
1788
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, editingCardId
|
|
1789
|
+
? translateLocaleText('编辑中 · 尚未保存') : translateLocaleText('草稿 · 未开放'))),
|
|
1581
1790
|
h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr)',
|
|
1582
|
-
gap: 12, alignItems: 'start', marginTop:
|
|
1583
|
-
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap:
|
|
1791
|
+
gap: 12, alignItems: 'start', marginTop: 14 } },
|
|
1792
|
+
h('form', { id: 'ant-capability-create-form', onSubmit: createCard, style: { display: 'grid', gap: 15, minWidth: 0 } },
|
|
1584
1793
|
editingCardId ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1585
1794
|
`保存会生成新的授权版本并停止旧版本;到期时间保持不变:${formatLocaleDate(editingExpiresAt * 1000)}。已提交任务继续使用原配置。`) : null,
|
|
1586
1795
|
h('p', { style: mutedTextStyle }, selectedBot
|
|
@@ -1588,78 +1797,104 @@ window.__ModuleLoader__.load({
|
|
|
1588
1797
|
: '请先在上方选择机器人并启用协作。'),
|
|
1589
1798
|
draftNotice ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1590
1799
|
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, draftNotice) : null,
|
|
1800
|
+
!ruleDescription.trim() && draftNotice
|
|
1801
|
+
=== translateLocaleText('这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。') && title
|
|
1802
|
+
? h('p', { role: 'status', style: mutedTextStyle }, translateLocaleText('已复制任务标题:{title}', {
|
|
1803
|
+
title,
|
|
1804
|
+
})) : null,
|
|
1591
1805
|
selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
|
|
1592
1806
|
'机器人及其任务能力只在这台 DSH 设备接收和执行;同一账号的其他设备不会继承本机授权。') : null,
|
|
1593
1807
|
selectedEnrollment && !selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
|
|
1594
1808
|
'这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;原协作配置会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
|
|
1595
1809
|
selectedBot && conversationKind === 'group' && !selectedBot.history_available ? h('p', { role: 'status', style: mutedTextStyle },
|
|
1596
1810
|
'这个机器人当前不能读取平台群历史;群聊只能参考实际送达的消息和本机近期记录。请检查 DSH-IM 权限。') : null,
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
h('label',
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
h('
|
|
1611
|
-
|
|
1612
|
-
onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? 'AI 正在分析…' : ruleSummaryReady ? '立即重新分析' : '立即分析'),
|
|
1613
|
-
h('span', { style: mutedTextStyle }, '自动分析和手动分析都会调用当前团队模型;只生成建议和待复核草稿,不会开放任务。')),
|
|
1614
|
-
recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null),
|
|
1615
|
-
h('section', { role: 'region', 'aria-label': 'AI 任务助手', style: { ...panelStyle, padding: 10, gap: 7, minWidth: 0 } },
|
|
1811
|
+
capabilityStepHeading('1', '描述任务', '写清要完成什么、处理范围和结果形式。'),
|
|
1812
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1.35fr))',
|
|
1813
|
+
gap: 12, alignItems: 'stretch' } },
|
|
1814
|
+
h('label', { style: { display: 'grid', alignContent: 'start', gap: 7, minWidth: 0 } }, '描述规则',
|
|
1815
|
+
h('textarea', { required: true, maxLength: 4000, rows: 5, value: ruleDescription,
|
|
1816
|
+
onChange: (event) => {
|
|
1817
|
+
cancelRecognitionRequest()
|
|
1818
|
+
recognitionRequestRef.current += 1
|
|
1819
|
+
setRuleDescription(event.target.value)
|
|
1820
|
+
setRecognitionBusy(false)
|
|
1821
|
+
setRecognitionMessage(event.target.value.trim() ? '描述已更新,点击“立即分析”检查任务。' : '')
|
|
1822
|
+
},
|
|
1823
|
+
placeholder: '用日常语言说明要完成什么、可处理哪些资料和希望怎样返回。例如:审查来自 https://gitea.example.com/team/shop 的 PR,指出可定位的问题,不修改代码;请求人可指定 Head SHA,未指定时用最新 Head。', style: { ...inputStyle, minHeight: 120, resize: 'vertical' } })),
|
|
1824
|
+
h('section', { role: 'region', 'aria-label': 'AI 任务助手', style: { ...panelStyle, padding: 14,
|
|
1825
|
+
gap: 9, minWidth: 0, border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
|
|
1616
1826
|
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
1617
|
-
h('strong', null,
|
|
1618
|
-
h('span', { style: mutedTextStyle }, ruleSummaryPending ? '
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
h('
|
|
1636
|
-
|
|
1637
|
-
h('
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
h('
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
h('p', { style: mutedTextStyle }, '
|
|
1650
|
-
h('
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1827
|
+
h('strong', null, 'AI 任务助手'),
|
|
1828
|
+
h('span', { style: mutedTextStyle }, ruleSummaryPending ? '正在分析' : ruleSummaryReady ? '待复核 · 尚未开放'
|
|
1829
|
+
: ruleSummaryDirty ? '描述已更新' : '待分析')),
|
|
1830
|
+
h('p', { style: mutedTextStyle }, ruleSummaryPending
|
|
1831
|
+
? 'AI 正在根据当前描述更新任务理解。上一次结果暂不用于授权。'
|
|
1832
|
+
: '分析只生成待复核草稿,不会自动开放能力。修改描述后需重新分析。'),
|
|
1833
|
+
h('div', { style: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 10, flexWrap: 'wrap' } },
|
|
1834
|
+
h('span', { style: mutedTextStyle }, '调用团队模型并计入用量'),
|
|
1835
|
+
h('button', { type: 'button', disabled: recognitionBusy || busy || !ruleDescription.trim(),
|
|
1836
|
+
onClick: () => void identifyRule(), style: primaryButtonStyle },
|
|
1837
|
+
recognitionBusy ? 'AI 正在分析…' : ruleSummaryReady ? '立即重新分析' : '立即分析')),
|
|
1838
|
+
recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null)),
|
|
1839
|
+
h('section', { role: 'region', 'aria-label': 'AI 分析结果', style: { display: 'grid', gap: 9, padding: 14,
|
|
1840
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 10,
|
|
1841
|
+
backgroundColor: 'var(--dsw-alias-bg-layer-1)', minWidth: 0,
|
|
1842
|
+
height: ruleSummaryReady ? 'min(480px, 55vh)' : undefined,
|
|
1843
|
+
gridTemplateRows: ruleSummaryReady ? 'auto minmax(0, 1fr)' : undefined,
|
|
1844
|
+
overflow: ruleSummaryReady ? 'hidden' : undefined } },
|
|
1845
|
+
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
|
|
1846
|
+
h('strong', null, 'AI 分析结果'),
|
|
1847
|
+
h('span', { style: mutedTextStyle }, ruleSummaryReady ? '草稿 · 未开放' : ruleSummaryPending ? '正在分析' : '等待分析')),
|
|
1848
|
+
ruleSummaryReady ? h('div', { style: { display: 'grid', alignContent: 'start', gap: 8,
|
|
1849
|
+
minHeight: 0, overflowY: 'auto', overscrollBehavior: 'contain', overflowWrap: 'anywhere' } },
|
|
1850
|
+
title ? h('strong', { style: { color: 'var(--dsw-alias-label-primary)' } }, rawLocaleText(title)) : null,
|
|
1851
|
+
h('p', { style: { ...mutedTextStyle, color: 'var(--dsw-alias-label-primary)' } }, ruleSummaryNarrative),
|
|
1852
|
+
missingHarnessInputs.length ? h('p', { role: 'status', style: { ...mutedTextStyle,
|
|
1853
|
+
color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
|
|
1854
|
+
translateLocaleText('待补信息:{items}。一般资料和偏好可留到执行时向请求人追问;PR 来源和仓库仍须先通过资源范围校验。',
|
|
1855
|
+
{ items: missingHarnessInputs.join(';') }))
|
|
1856
|
+
: h('p', { role: 'status', style: mutedTextStyle }, translateLocaleText(
|
|
1857
|
+
'AI 暂未发现运行时待补信息;开放后,执行时如缺少必要资料,机器人仍会向请求人追问。')),
|
|
1858
|
+
suggestions.length ? h('p', { style: mutedTextStyle }, `可选补充:${suggestions.join(';')}`) : null,
|
|
1859
|
+
ruleExamples.length ? h('p', { style: mutedTextStyle }, `自然请求示例:${ruleExamples.join(';')}`) : null,
|
|
1860
|
+
h('p', { style: mutedTextStyle }, translateLocaleText(
|
|
1861
|
+
'待补信息和可选建议仅供复核,不会阻止开放。确定的授权范围与 PR 资源边界仍会单独校验。')))
|
|
1862
|
+
: h('p', { role: 'status', style: mutedTextStyle }, ruleSummaryPending
|
|
1863
|
+
? '分析完成后,摘要、缺失范围和可选条件会显示在这里。'
|
|
1864
|
+
: '填写任务描述后点击右侧“立即分析”,结果会显示在这里。')),
|
|
1865
|
+
h('section', { style: capabilityStepStyle },
|
|
1866
|
+
capabilityStepHeading('2', '执行方', '选择可用于此任务的执行器,并从已勾选项中设一个默认。'),
|
|
1867
|
+
h('div', { style: { ...panelStyle, padding: 12, gap: 8 } },
|
|
1868
|
+
h('p', { style: mutedTextStyle }, translateLocaleText('普通请求只运行默认执行方;发起人明确要求交叉审查时,会并行运行其他已选执行方。')),
|
|
1869
|
+
h('div', { role: 'group', 'aria-label': '执行方', style: { display: 'grid', gap: 7 } },
|
|
1870
|
+
...[
|
|
1871
|
+
{ id: 'dsh', title: 'DSH', detail: '使用获准的团队模型处理对话内容和 ANT 已核验资源。', available: dshAvailable },
|
|
1872
|
+
{ id: 'codex', title: 'Codex', detail: '使用本机 DSH Provider Profile 权限;ANT 不会将文件、Git 和网络权限缩小到单项任务。', available: codexAvailable },
|
|
1873
|
+
{ id: 'claude-code', title: 'Claude Code', detail: '使用本机 DSH Provider Profile 权限;ANT 不会将文件、Git 和网络权限缩小到单项任务。', available: claudeCodeAvailable },
|
|
1874
|
+
].map((option) => {
|
|
1875
|
+
const checked = reviewers.includes(option.id)
|
|
1876
|
+
return h('div', { key: option.id, style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto',
|
|
1877
|
+
gap: 8, alignItems: 'center', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
1878
|
+
padding: '9px 11px' } },
|
|
1879
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
|
|
1880
|
+
h('input', { type: 'checkbox', 'aria-label': `任务 Agent ${option.title}`, checked,
|
|
1881
|
+
disabled: !option.available && !checked,
|
|
1882
|
+
onChange: (event) => changeReviewer(option.id, event.target.checked) }),
|
|
1883
|
+
h('strong', null, option.title),
|
|
1884
|
+
h('span', { style: mutedTextStyle }, option.available ? '可用' : '未在本机启用')),
|
|
1885
|
+
h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, color: mutedTextStyle.color } },
|
|
1886
|
+
h('input', { type: 'radio', name: 'ant-default-runner', 'aria-label': `默认执行方 ${option.title}`,
|
|
1887
|
+
checked: runner === option.id, disabled: !checked || !option.available,
|
|
1888
|
+
onChange: () => selectRunner(option.id) }), '默认'),
|
|
1889
|
+
checked ? h('span', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } }, option.detail) : null)
|
|
1890
|
+
}),
|
|
1891
|
+
),
|
|
1892
|
+
executorUnavailableReason ? h('p', { role: 'status', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1893
|
+
`${executorUnavailableReason} 当前仍可使用 DSH 团队模型。`) : null)),
|
|
1894
|
+
h('section', { style: capabilityStepStyle },
|
|
1895
|
+
capabilityStepHeading('3', '请求范围', '限定从哪里请求、哪些人可以请求,以及授权多久。'),
|
|
1896
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 250px), 1fr))',
|
|
1897
|
+
gap: 12, alignItems: 'start' } },
|
|
1663
1898
|
h('label', null, '使用场景', h('select', { value: conversationKind,
|
|
1664
1899
|
onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
|
|
1665
1900
|
h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
|
|
@@ -1683,10 +1918,16 @@ window.__ModuleLoader__.load({
|
|
|
1683
1918
|
`有效期:${formatLocaleDate(editingExpiresAt * 1000)}(编辑时保持不变)`)
|
|
1684
1919
|
: h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
|
|
1685
1920
|
h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
|
|
1686
|
-
|
|
1687
|
-
'
|
|
1688
|
-
|
|
1689
|
-
|
|
1921
|
+
usesExternalExecutor ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1922
|
+
'Codex 和 Claude Code 沿用本机 DSH Provider Profile 权限;任务授权不会把文件、Git 或网络权限缩小到单项任务。') : null,
|
|
1923
|
+
usesExternalExecutor ? h('label', { style: { gridColumn: '1 / -1' } },
|
|
1924
|
+
h('input', { type: 'checkbox', 'aria-label': '启动前需要主人审批', checked: ownerApprovalRequired,
|
|
1925
|
+
onChange: (event) => setOwnerApprovalRequired(event.target.checked) }),
|
|
1926
|
+
' ', '启动前需要主人审批') : null,
|
|
1927
|
+
usesExternalExecutor && !ownerApprovalRequired ? h('p', { role: 'note', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
|
|
1928
|
+
'取消后,模型匹配到这项任务就会直接启动 Codex 或 Claude Code,主人不再逐次确认。') : null)),
|
|
1929
|
+
h('section', { style: capabilityStepStyle },
|
|
1930
|
+
capabilityStepHeading('4', '开放前复核', '确认任务理解、执行方式和请求范围后再开放。'),
|
|
1690
1931
|
h('aside', { id: 'ant-authorization-review', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
|
|
1691
1932
|
style: { ...settingsPanelStyle, padding: 14, gap: 10, alignSelf: 'stretch' } },
|
|
1692
1933
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
|
|
@@ -1724,14 +1965,14 @@ window.__ModuleLoader__.load({
|
|
|
1724
1965
|
'aria-describedby': cardBlockReason ? 'ant-capability-block-reason'
|
|
1725
1966
|
: submissionError ? 'ant-capability-submission-error' : undefined,
|
|
1726
1967
|
style: { ...primaryButtonStyle, width: '100%' } }, busy ? translateLocaleText('正在处理…')
|
|
1727
|
-
: editingCardId ? '保存能力修改' : '确认并开放能力'))))),
|
|
1968
|
+
: editingCardId ? '保存能力修改' : '确认并开放能力'))))))),
|
|
1728
1969
|
h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
|
|
1729
1970
|
showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
|
|
1730
1971
|
h('strong', null, 'PR 只读凭据(本机)'),
|
|
1731
1972
|
h('p', { style: mutedTextStyle }, tokenConfigured
|
|
1732
1973
|
? '令牌由 DSH 本机凭据服务保存。ANT Host 在读取已授权 PR 时会使用它;不会同步到 ANT 服务端或写入审查提示词。'
|
|
1733
1974
|
: '读取 Gitea PR 需要只读访问令牌。保存后由 DSH 本机凭据服务管理,ANT Host 只在读取已授权 PR 时使用。'),
|
|
1734
|
-
h('p', { role: 'note', style: mutedTextStyle }, 'DSH 默认将凭据保存在 DSH Home 的私有文件中,环境变量也可作为回退来源。文件权限只隔离其他 OS 用户;同一用户权限下的 Agent 进程可能读取它。
|
|
1975
|
+
h('p', { role: 'note', style: mutedTextStyle }, 'DSH 默认将凭据保存在 DSH Home 的私有文件中,环境变量也可作为回退来源。文件权限只隔离其他 OS 用户;同一用户权限下的 Agent 进程可能读取它。Codex 和 Claude Code 会使用本机 DSH Provider Profile 的文件、Git、网络及工具权限;任务描述和单次审批不会创建操作系统沙箱,也不会缩小这些权限。ANT Gitea 凭据只由 Host Connector 使用,不会放入 Agent 请求。'),
|
|
1735
1976
|
h('p', { role: 'note', style: mutedTextStyle }, selectedGiteaOrigin
|
|
1736
1977
|
? `这枚只读令牌仅用于已绑定主机 ${selectedGiteaOrigin};任务来源来自自然语言描述,但必须匹配该主机。`
|
|
1737
1978
|
: '首次确认并开放包含 PR 来源的任务时,会将该来源绑定到这台机器人;确认前 ANT 不会把只读令牌发送到任务描述中的地址。'),
|
|
@@ -1765,26 +2006,26 @@ window.__ModuleLoader__.load({
|
|
|
1765
2006
|
style: { ...inputStyle, width: 128, maxWidth: '100%', boxSizing: 'border-box' } })),
|
|
1766
2007
|
h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置'),
|
|
1767
2008
|
h('p', { style: { ...mutedTextStyle, flex: '1 1 100%', margin: 0 } },
|
|
1768
|
-
'
|
|
2009
|
+
'需要完整 PR 文件时,ANT 会在本机准备已核验 Head 的临时快照;刷新授权状态时会清理已停用或到期能力的旧缓存。')),
|
|
1769
2010
|
),
|
|
1770
2011
|
h('section', { 'aria-label': '执行方状态与权限边界', style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
|
|
1771
2012
|
h('strong', null, '执行方状态与权限边界'),
|
|
1772
|
-
h('p', { style: mutedTextStyle }, '这里显示 DSH
|
|
2013
|
+
h('p', { style: mutedTextStyle }, '这里显示 DSH 本机的执行器状态。已注册的 Codex 或 Claude Code 可由主人选入预授权任务卡,或在本次 IM 审批中选择。'),
|
|
1773
2014
|
loadFailures.includes('executors') ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法读取执行方状态,请刷新后重试。') : null,
|
|
1774
2015
|
...executors.filter((item) => ['dsh', 'codex', 'claude-code'].includes(item.id)).map((item) => h('div', {
|
|
1775
2016
|
key: item.id, style: { display: 'grid', gap: 3, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
|
|
1776
2017
|
h('strong', null, item.title + ' · ' + (item.id === 'dsh'
|
|
1777
2018
|
? item.available ? '可用' : item.reason || '未就绪'
|
|
1778
|
-
: item.available ? '已注册 ·
|
|
2019
|
+
: item.available ? '已注册 · 可用于协作' : '尚未注册')),
|
|
1779
2020
|
h('span', { style: mutedTextStyle }, item.id === 'codex'
|
|
1780
|
-
? '
|
|
2021
|
+
? 'Codex 使用本机 Provider Profile 权限;ANT 不会把文件、Git 或网络权限缩小到任务范围。'
|
|
1781
2022
|
: item.id === 'claude-code'
|
|
1782
|
-
? '
|
|
2023
|
+
? 'Claude Code 使用本机 Provider Profile 权限;ANT 不会把文件、Git 或网络权限缩小到任务范围。'
|
|
1783
2024
|
: '处理授权任务提供的内容,不调用本机工具。'))))))
|
|
1784
2025
|
}
|
|
1785
2026
|
|
|
1786
2027
|
function SkillCenterPage({ remote, onBack, onAll, onSkills, onPlugins, openBundle,
|
|
1787
|
-
includePlugins, query, onQueryChange }) {
|
|
2028
|
+
includePlugins, query, onQueryChange, pluginPageState }) {
|
|
1788
2029
|
const [skills, setSkills] = useState([])
|
|
1789
2030
|
const [loading, setLoading] = useState(false)
|
|
1790
2031
|
const [loadError, setLoadError] = useState(false)
|
|
@@ -2133,6 +2374,7 @@ window.__ModuleLoader__.load({
|
|
|
2133
2374
|
style: { color: 'var(--dsw-alias-link)', fontSize: 13, lineHeight: 1.6, padding: '4px 0' } }, '英文原文 ↗'))))) : null,
|
|
2134
2375
|
includePlugins ? h(PluginCenterPage, { remote, onBack, onAll, onSkills, openBundle,
|
|
2135
2376
|
embedded: true, query, onQueryChange, externalFilter: filter, externalCategoryFilter: categoryFilter,
|
|
2377
|
+
pluginPageState,
|
|
2136
2378
|
externalScopeFilter: scopeFilter,
|
|
2137
2379
|
hasSkillMatches: visible.length + visibleOfficial.length > 0, skillCatalogReady: !loading && !loadError,
|
|
2138
2380
|
refreshToken }) : null))
|
|
@@ -2141,49 +2383,193 @@ window.__ModuleLoader__.load({
|
|
|
2141
2383
|
function PluginCenterPage({ remote, onBack, onAll, onSkills, openBundle,
|
|
2142
2384
|
embedded = false, query, onQueryChange, externalFilter = 'all',
|
|
2143
2385
|
externalCategoryFilter = 'all', externalScopeFilter = 'all',
|
|
2144
|
-
hasSkillMatches = false, skillCatalogReady = false, refreshToken }) {
|
|
2386
|
+
hasSkillMatches = false, skillCatalogReady = false, refreshToken, pluginPageState }) {
|
|
2387
|
+
const { uncertainInstalls, setUncertainInstalls, uncertainInstallsRef, workingId, setWorkingId,
|
|
2388
|
+
completedId, setCompletedId, message, setMessage, refreshMessage, refreshMessageIsError,
|
|
2389
|
+
setRefreshFeedback, messageVersion, messagePluginId, messagePackageName,
|
|
2390
|
+
messageClearOnResolve, refreshGeneration, refreshRevision, requestPluginRefresh,
|
|
2391
|
+
workingAny } = pluginPageState
|
|
2145
2392
|
const [plugins, setPlugins] = useState([])
|
|
2146
2393
|
const [bundles, setBundles] = useState([])
|
|
2147
2394
|
const [loading, setLoading] = useState(true)
|
|
2148
2395
|
const [loadError, setLoadError] = useState(false)
|
|
2149
|
-
const [
|
|
2150
|
-
const [uncertainId, setUncertainId] = useState('')
|
|
2151
|
-
const [completedId, setCompletedId] = useState('')
|
|
2152
|
-
const [message, setMessage] = useState('')
|
|
2396
|
+
const [bundleListReady, setBundleListReady] = useState(false)
|
|
2153
2397
|
const [filter, setFilter] = useState('all')
|
|
2154
2398
|
const [categoryFilter, setCategoryFilter] = useState('all')
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2399
|
+
const waitForInstallBeforeDeadline = (manager, requestId) => {
|
|
2400
|
+
let timeoutId
|
|
2401
|
+
const waitPromise = Promise.resolve().then(() => manager.waitForInstall(requestId))
|
|
2402
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2403
|
+
timeoutId = setTimeout(() => reject(new Error()), 30_000)
|
|
2404
|
+
})
|
|
2405
|
+
return Promise.race([waitPromise, timeoutPromise])
|
|
2406
|
+
.finally(() => { if (timeoutId !== undefined) clearTimeout(timeoutId) })
|
|
2407
|
+
}
|
|
2408
|
+
const refresh = async (focusPluginId = null) => {
|
|
2157
2409
|
const generation = ++refreshGeneration.current
|
|
2410
|
+
const uncertaintySnapshot = uncertainInstallsRef.current
|
|
2411
|
+
const messageVersionAtStart = messageVersion.current
|
|
2158
2412
|
setLoading(true)
|
|
2413
|
+
setBundleListReady(false)
|
|
2414
|
+
let catalogLoaded = false
|
|
2159
2415
|
try {
|
|
2160
2416
|
const manager = remote?.pluginManager
|
|
2161
2417
|
if (typeof manager?.listBundles !== 'function') throw new Error('当前 DSH 未提供插件管理接口。')
|
|
2162
|
-
const
|
|
2163
|
-
fetch('/api/ant-teams/plugins', { cache: 'no-store' }), manager.listBundles(),
|
|
2164
|
-
])
|
|
2418
|
+
const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
|
|
2165
2419
|
const result = await readLocalJsonResponse(response)
|
|
2166
2420
|
if (!response.ok) throw new Error(result.message || `读取插件目录失败(${response.status})。`)
|
|
2167
2421
|
if (!Array.isArray(result.plugins)) throw new Error('企业插件目录格式无效。')
|
|
2168
|
-
if (!listed.ok) throw new Error(listed.error?.message || '无法读取 DSH 本机插件。')
|
|
2169
2422
|
if (generation !== refreshGeneration.current) return
|
|
2170
2423
|
setPlugins(result.plugins)
|
|
2424
|
+
setLoadError(false)
|
|
2425
|
+
catalogLoaded = true
|
|
2426
|
+
const initialListed = await manager.listBundles()
|
|
2427
|
+
if (!initialListed.ok) throw new Error(initialListed.error?.message || '无法读取 DSH 本机插件。')
|
|
2428
|
+
if (generation !== refreshGeneration.current) return
|
|
2429
|
+
setBundles(initialListed.value)
|
|
2430
|
+
setBundleListReady(true)
|
|
2431
|
+
const catalogPackageNames = new Set(result.plugins
|
|
2432
|
+
.map((plugin) => plugin.package_name).filter((name) => typeof name === 'string' && name))
|
|
2433
|
+
const catalogUncertaintySnapshot = Object.fromEntries(Object.entries(uncertaintySnapshot)
|
|
2434
|
+
.filter(([, uncertainInstall]) => catalogPackageNames.has(uncertainInstall.packageName)))
|
|
2435
|
+
const requestIds = [...new Set(Object.values(catalogUncertaintySnapshot)
|
|
2436
|
+
.flatMap((uncertainInstall) => [uncertainInstall.requestId,
|
|
2437
|
+
...(uncertainInstall.relatedRequestIds || [])])
|
|
2438
|
+
.filter((requestId) => typeof requestId === 'string' && requestId))]
|
|
2439
|
+
if (requestIds.length) {
|
|
2440
|
+
if (typeof manager.waitForInstall !== 'function') {
|
|
2441
|
+
if (generation === refreshGeneration.current) setRefreshFeedback(translateLocaleText(
|
|
2442
|
+
'当前 DSH 无法确认上次安装是否结束;请稍后重新核对,勿重复安装。'), true)
|
|
2443
|
+
return
|
|
2444
|
+
}
|
|
2445
|
+
setRefreshFeedback(translateLocaleText('正在等待 DSH 上一次插件安装请求结束,再读取本机状态。'))
|
|
2446
|
+
for (const requestId of requestIds) {
|
|
2447
|
+
let waitResult
|
|
2448
|
+
try { waitResult = await waitForInstallBeforeDeadline(manager, requestId) }
|
|
2449
|
+
catch {
|
|
2450
|
+
if (generation === refreshGeneration.current) setRefreshFeedback(translateLocaleText(
|
|
2451
|
+
'无法确认 DSH 上一项插件安装是否已结束;待核对锁保持,请稍后重试。'), true)
|
|
2452
|
+
return
|
|
2453
|
+
}
|
|
2454
|
+
if (!waitResult?.ok) {
|
|
2455
|
+
if (generation === refreshGeneration.current) setRefreshFeedback(translateLocaleText(
|
|
2456
|
+
'无法确认 DSH 上一项插件安装是否已结束;待核对锁保持,请稍后重试。'), true)
|
|
2457
|
+
return
|
|
2458
|
+
}
|
|
2459
|
+
if (generation !== refreshGeneration.current) return
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
if (generation !== refreshGeneration.current) return
|
|
2463
|
+
let listed = initialListed
|
|
2464
|
+
if (requestIds.length) {
|
|
2465
|
+
listed = await manager.listBundles()
|
|
2466
|
+
if (!listed.ok) throw new Error(listed.error?.message || '无法读取 DSH 本机插件。')
|
|
2467
|
+
}
|
|
2468
|
+
if (generation !== refreshGeneration.current) return
|
|
2469
|
+
const canUpdateMessage = messageVersion.current === messageVersionAtStart
|
|
2470
|
+
const currentUncertainties = uncertainInstallsRef.current
|
|
2471
|
+
const currentCatalogUncertainties = Object.fromEntries(Object.entries(currentUncertainties)
|
|
2472
|
+
.filter(([, uncertainInstall]) => catalogPackageNames.has(uncertainInstall.packageName)))
|
|
2473
|
+
const focusPackageName = focusPluginId
|
|
2474
|
+
? plugins.find((plugin) => plugin.id === focusPluginId)?.package_name : null
|
|
2475
|
+
let nextUncertainties = currentUncertainties
|
|
2476
|
+
const resolvedPluginIds = new Set()
|
|
2477
|
+
for (const [pluginId, uncertainInstall] of Object.entries(catalogUncertaintySnapshot)) {
|
|
2478
|
+
if (currentUncertainties[pluginId] !== uncertainInstall) continue
|
|
2479
|
+
const installed = listed.value.find((bundle) => bundle.name === uncertainInstall.packageName && bundle.installed)
|
|
2480
|
+
const resolved = !installed
|
|
2481
|
+
|| (installed.version === uncertainInstall.targetVersion
|
|
2482
|
+
&& (!uncertainInstall.expectedSource || installed.source === uncertainInstall.expectedSource))
|
|
2483
|
+
|| Boolean(uncertainInstall.previousVersion
|
|
2484
|
+
&& installed.version === uncertainInstall.previousVersion
|
|
2485
|
+
&& (installed.source === uncertainInstall.previousSource
|
|
2486
|
+
|| installed.source === uncertainInstall.expectedPreviousSource)
|
|
2487
|
+
&& installed.enabled === uncertainInstall.previousEnabled)
|
|
2488
|
+
|| Boolean(focusPluginId
|
|
2489
|
+
&& (pluginId === focusPluginId || uncertainInstall.packageName === focusPackageName)
|
|
2490
|
+
&& installed && typeof installed.version === 'string' && installed.version.trim()
|
|
2491
|
+
&& installed.version !== uncertainInstall.targetVersion
|
|
2492
|
+
&& installed.version !== uncertainInstall.previousVersion)
|
|
2493
|
+
if (!resolved) continue
|
|
2494
|
+
if (nextUncertainties === currentUncertainties) nextUncertainties = { ...currentUncertainties }
|
|
2495
|
+
delete nextUncertainties[pluginId]
|
|
2496
|
+
resolvedPluginIds.add(pluginId)
|
|
2497
|
+
}
|
|
2498
|
+
if (nextUncertainties !== currentUncertainties) {
|
|
2499
|
+
uncertainInstallsRef.current = nextUncertainties
|
|
2500
|
+
setUncertainInstalls(nextUncertainties)
|
|
2501
|
+
}
|
|
2502
|
+
setPlugins(result.plugins)
|
|
2171
2503
|
setBundles(listed.value)
|
|
2172
2504
|
setLoadError(false)
|
|
2173
|
-
|
|
2505
|
+
if (canUpdateMessage && messageClearOnResolve.current && messagePluginId.current
|
|
2506
|
+
&& (resolvedPluginIds.has(messagePluginId.current)
|
|
2507
|
+
|| [...resolvedPluginIds].some((pluginId) => uncertaintySnapshot[pluginId]?.packageName
|
|
2508
|
+
=== messagePackageName.current))) setMessage('')
|
|
2509
|
+
const focusedUncertainty = focusPluginId
|
|
2510
|
+
? catalogUncertaintySnapshot[focusPluginId] || Object.values(catalogUncertaintySnapshot)
|
|
2511
|
+
.find((item) => item.packageName === focusPackageName) : null
|
|
2512
|
+
const focusedUncertaintyId = focusedUncertainty?.id
|
|
2513
|
+
const hasFocusedUncertainty = Boolean(focusedUncertainty
|
|
2514
|
+
&& currentUncertainties[focusedUncertaintyId] === focusedUncertainty)
|
|
2515
|
+
const refreshSnapshotIsCurrent = Object.keys(currentCatalogUncertainties).length
|
|
2516
|
+
=== Object.keys(catalogUncertaintySnapshot).length
|
|
2517
|
+
&& Object.entries(catalogUncertaintySnapshot)
|
|
2518
|
+
.every(([pluginId, uncertainInstall]) => currentCatalogUncertainties[pluginId] === uncertainInstall)
|
|
2519
|
+
const hasUncertainInstalls = Object.values(nextUncertainties)
|
|
2520
|
+
.some((uncertainInstall) => catalogPackageNames.has(uncertainInstall.packageName))
|
|
2521
|
+
if (canUpdateMessage && hasFocusedUncertainty && refreshSnapshotIsCurrent) {
|
|
2522
|
+
if (resolvedPluginIds.has(focusedUncertaintyId)) {
|
|
2523
|
+
setRefreshFeedback(hasUncertainInstalls
|
|
2524
|
+
? translateLocaleText('已重新读取本机列表,但安装状态仍无法确认;请在 DSH 插件管理核对后再操作。')
|
|
2525
|
+
: translateLocaleText('本机状态已确认;可以再次检查或按目录版本操作。'))
|
|
2526
|
+
} else {
|
|
2527
|
+
setRefreshFeedback(translateLocaleText('已重新读取本机列表,但安装状态仍无法确认;请在 DSH 插件管理核对后再操作。'))
|
|
2528
|
+
}
|
|
2529
|
+
} else if (canUpdateMessage && !focusPluginId && refreshSnapshotIsCurrent) {
|
|
2530
|
+
setRefreshFeedback(hasUncertainInstalls
|
|
2531
|
+
? translateLocaleText('已重新读取本机列表,但安装状态仍无法确认;请在 DSH 插件管理核对后再操作。')
|
|
2532
|
+
: result.plugins.length ? '' : '团队暂未发布精选插件。')
|
|
2533
|
+
}
|
|
2174
2534
|
} catch (error) {
|
|
2175
2535
|
if (generation !== refreshGeneration.current) return
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2536
|
+
setBundleListReady(false)
|
|
2537
|
+
const errorMessage = error instanceof Error ? error.message : '无法读取企业精选插件。'
|
|
2538
|
+
if (!catalogLoaded) {
|
|
2539
|
+
setPlugins([])
|
|
2540
|
+
setBundles([])
|
|
2541
|
+
setLoadError(true)
|
|
2542
|
+
}
|
|
2543
|
+
setRefreshFeedback(errorMessage, true)
|
|
2180
2544
|
} finally { if (generation === refreshGeneration.current) setLoading(false) }
|
|
2181
2545
|
}
|
|
2182
|
-
useEffect(() => { void refresh() }, [refreshToken])
|
|
2546
|
+
useEffect(() => { void refresh() }, [refreshToken, refreshRevision])
|
|
2547
|
+
const uncertainInstallFor = (plugin) => uncertainInstalls[plugin.id]
|
|
2548
|
+
|| Object.values(uncertainInstallsRef.current).find((item) => item.packageName === plugin.package_name)
|
|
2549
|
+
const markInstallUncertain = (plugin, previous = null, expectedPreviousSource = null,
|
|
2550
|
+
requestId = null, relatedRequestIds = []) => {
|
|
2551
|
+
const uncertainInstall = {
|
|
2552
|
+
id: plugin.id,
|
|
2553
|
+
packageName: plugin.package_name,
|
|
2554
|
+
targetVersion: plugin.version,
|
|
2555
|
+
requestId,
|
|
2556
|
+
relatedRequestIds: [...new Set(relatedRequestIds.filter((value) => typeof value === 'string' && value))],
|
|
2557
|
+
expectedSource: approvedPluginArtifactUrl(plugin),
|
|
2558
|
+
previousVersion: previous?.version || null,
|
|
2559
|
+
previousSource: previous?.source,
|
|
2560
|
+
expectedPreviousSource,
|
|
2561
|
+
previousEnabled: previous ? previous.enabled !== false : null,
|
|
2562
|
+
}
|
|
2563
|
+
const next = { ...uncertainInstallsRef.current, [plugin.id]: uncertainInstall }
|
|
2564
|
+
uncertainInstallsRef.current = next
|
|
2565
|
+
setUncertainInstalls(next)
|
|
2566
|
+
}
|
|
2183
2567
|
const install = async (plugin) => {
|
|
2184
2568
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
2185
2569
|
const installSpec = pluginInstallSpec(plugin)
|
|
2186
|
-
|
|
2570
|
+
const uncertainInstall = uncertainInstallFor(plugin)
|
|
2571
|
+
if (!installSpec || workingId || pluginRuntimeState.workingId
|
|
2572
|
+
|| uncertainInstall || completedId === plugin.id) return
|
|
2187
2573
|
const manager = remote?.pluginManager
|
|
2188
2574
|
if (typeof manager?.installBundle !== 'function') {
|
|
2189
2575
|
setMessage('当前 DSH 未提供插件安装接口,请联系管理员。')
|
|
@@ -2203,8 +2589,15 @@ window.__ModuleLoader__.load({
|
|
|
2203
2589
|
'在这台 DSH {action}团队审核的 {plugin_title} {version}?{source_notice}{update_notice} 插件将在本机运行,完成后需要完全重启 DSH。',
|
|
2204
2590
|
{ action: translateLocaleText(action), plugin_title: plugin.title, version: plugin.version, source_notice: sourceNotice, update_notice: updateNotice })
|
|
2205
2591
|
if (!confirmLocalized(installConfirmation)) return
|
|
2592
|
+
const operationScopeRevision = pluginRuntimeState.scopeRevision
|
|
2593
|
+
const isOperationScopeCurrent = () => operationScopeRevision === pluginRuntimeState.scopeRevision
|
|
2594
|
+
const setInstallMessage = (value, staleScopeValue = '本机此前启动的插件操作已结束;请查看当前插件目录和本机状态。') => {
|
|
2595
|
+
setMessage(isOperationScopeCurrent() ? value : staleScopeValue,
|
|
2596
|
+
plugin.id, plugin.package_name, Boolean(uncertainInstallFor(plugin)))
|
|
2597
|
+
}
|
|
2206
2598
|
setWorkingId(plugin.id)
|
|
2207
|
-
|
|
2599
|
+
setInstallMessage(translateLocaleText('正在安装 {plugin_title},请不要退出 DSH…', { plugin_title: plugin.title }),
|
|
2600
|
+
'本机有一项插件操作仍在完成;结束前不能启动其他插件安装。')
|
|
2208
2601
|
try {
|
|
2209
2602
|
const response = await fetch('/api/ant-teams/plugins', { cache: 'no-store' })
|
|
2210
2603
|
const catalog = await readLocalJsonResponse(response)
|
|
@@ -2215,6 +2608,9 @@ window.__ModuleLoader__.load({
|
|
|
2215
2608
|
throw new Error('插件审核信息已变化,请刷新目录后重试。')
|
|
2216
2609
|
}
|
|
2217
2610
|
const requestId = crypto.randomUUID()
|
|
2611
|
+
let relatedRequestIds = []
|
|
2612
|
+
const markCurrentInstallUncertain = (previous = null, expectedPreviousSource = null) =>
|
|
2613
|
+
markInstallUncertain(plugin, previous, expectedPreviousSource, requestId, relatedRequestIds)
|
|
2218
2614
|
const requestReference = requestId.slice(0, 8).toUpperCase()
|
|
2219
2615
|
const previousSourceIsRegistry = Boolean(local && (!local.source
|
|
2220
2616
|
|| local.source === plugin.package_name || local.source.startsWith(`${plugin.package_name}@`)))
|
|
@@ -2254,17 +2650,26 @@ window.__ModuleLoader__.load({
|
|
|
2254
2650
|
if (installed !== null || !previousInstallSpec) return 'unknown'
|
|
2255
2651
|
if (previousSourceIsRegistry && !previousRegistry) return 'unknown'
|
|
2256
2652
|
const restoreRequestId = crypto.randomUUID()
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2653
|
+
const restoreOptions = {
|
|
2654
|
+
enabled: local.enabled !== false, requestId: restoreRequestId,
|
|
2655
|
+
saveExact: true,
|
|
2656
|
+
}
|
|
2657
|
+
if (previousSourceIsRegistry && previousRegistry) restoreOptions.registry = previousRegistry
|
|
2658
|
+
try { await manager.installBundle(previousInstallSpec, restoreOptions) } catch {}
|
|
2659
|
+
if (typeof manager.waitForInstall !== 'function') {
|
|
2660
|
+
relatedRequestIds = [restoreRequestId]
|
|
2661
|
+
return 'unknown'
|
|
2662
|
+
}
|
|
2663
|
+
let restoreWaitResult
|
|
2664
|
+
try { restoreWaitResult = await manager.waitForInstall(restoreRequestId) }
|
|
2665
|
+
catch {
|
|
2666
|
+
relatedRequestIds = [restoreRequestId]
|
|
2667
|
+
return 'unknown'
|
|
2668
|
+
}
|
|
2669
|
+
if (!restoreWaitResult?.ok) {
|
|
2670
|
+
relatedRequestIds = [restoreRequestId]
|
|
2671
|
+
return 'unknown'
|
|
2672
|
+
}
|
|
2268
2673
|
installed = await currentBundle()
|
|
2269
2674
|
return isPreviousVersion(installed) && installed.enabled === (local.enabled !== false)
|
|
2270
2675
|
? 'restored' : 'unknown'
|
|
@@ -2287,7 +2692,7 @@ window.__ModuleLoader__.load({
|
|
|
2287
2692
|
} catch (error) { installFailure = error; result = null }
|
|
2288
2693
|
} else {
|
|
2289
2694
|
if (replacingInstalled && typeof manager.removeBundle !== 'function') {
|
|
2290
|
-
|
|
2695
|
+
setInstallMessage('当前 DSH 不支持通过 ANT 安全替换已安装插件。请在 DSH 插件管理查看状态,或联系管理员。')
|
|
2291
2696
|
return
|
|
2292
2697
|
}
|
|
2293
2698
|
if (replacingInstalled) {
|
|
@@ -2298,7 +2703,7 @@ window.__ModuleLoader__.load({
|
|
|
2298
2703
|
if (!removed?.ok || !['applied', 'restart-required'].includes(removed.value?.application)) {
|
|
2299
2704
|
const diagnostic = safeInstallDiagnostic(removed?.value?.error || removed?.error || removeFailure)
|
|
2300
2705
|
const recovery = await restorePreviousVersion()
|
|
2301
|
-
if (recovery !== 'restored')
|
|
2706
|
+
if (recovery !== 'restored') markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2302
2707
|
const recoveryNotice = recovery === 'restored'
|
|
2303
2708
|
? translateLocaleText('旧版本号和原启用状态已恢复。{source_notice}', {
|
|
2304
2709
|
source_notice: previousSourceIsRegistry
|
|
@@ -2306,7 +2711,7 @@ window.__ModuleLoader__.load({
|
|
|
2306
2711
|
: recovery === 'target' ? translateLocaleText(
|
|
2307
2712
|
'本机列表显示审核版本 {version} 已安装,请核对后再操作。', { version: plugin.version })
|
|
2308
2713
|
: translateLocaleText('无法确认旧版本及启用状态,请先在 DSH 插件管理核对。')
|
|
2309
|
-
|
|
2714
|
+
setInstallMessage(`无法开始更新 ${plugin.title}:DSH 未能确认旧版本移除完成。${diagnostic
|
|
2310
2715
|
? `DSH 诊断:${diagnostic}。` : ''}${recoveryNotice}`)
|
|
2311
2716
|
return
|
|
2312
2717
|
}
|
|
@@ -2327,7 +2732,6 @@ window.__ModuleLoader__.load({
|
|
|
2327
2732
|
} catch (error) { waitFailure = error }
|
|
2328
2733
|
}
|
|
2329
2734
|
if (!recovered) {
|
|
2330
|
-
setUncertainId(plugin.id)
|
|
2331
2735
|
let restoreNotice = ''
|
|
2332
2736
|
if (removedForUpdate) {
|
|
2333
2737
|
const restoreState = await restorePreviousVersion()
|
|
@@ -2340,23 +2744,27 @@ window.__ModuleLoader__.load({
|
|
|
2340
2744
|
'本机列表显示审核版本 {version} 已安装,但操作结果未确认。', { version: plugin.version })}`
|
|
2341
2745
|
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2342
2746
|
}
|
|
2747
|
+
markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2343
2748
|
const details = [installFailure && `installBundle:${safeInstallDiagnostic(installFailure)}`,
|
|
2344
2749
|
waitFailure && `waitForInstall:${safeInstallDiagnostic(waitFailure)}`].filter(Boolean)
|
|
2345
|
-
|
|
2346
|
-
? `本机诊断:${details.join(';')}。` : ''}${restoreNotice}请先在 DSH
|
|
2750
|
+
setInstallMessage(`暂时无法确认 ${plugin.title} 的安装结果(请求编号 ${requestReference})。${details.length
|
|
2751
|
+
? `本机诊断:${details.join(';')}。` : ''}${restoreNotice}请先在 DSH 插件列表核对,勿重复点击安装。`,
|
|
2752
|
+
'本机插件安装结果待核对,请先确认本机状态,勿重复安装。')
|
|
2347
2753
|
return
|
|
2348
2754
|
}
|
|
2349
2755
|
result = recovered
|
|
2350
2756
|
}
|
|
2351
2757
|
const outcome = result.value
|
|
2352
2758
|
if (!outcome || typeof outcome !== 'object') {
|
|
2353
|
-
|
|
2354
|
-
|
|
2759
|
+
markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2760
|
+
setInstallMessage(`DSH 未返回 ${plugin.title} 的安装结果。请先在插件列表核对,勿重复点击安装。`,
|
|
2761
|
+
'本机插件安装结果待核对,请先确认本机状态,勿重复安装。')
|
|
2355
2762
|
return
|
|
2356
2763
|
}
|
|
2357
2764
|
if (outcome.application === 'failed' || outcome.application === 'cancelled') {
|
|
2358
2765
|
if (outcome.error?.code === 'ambiguous-install' && local?.version === plugin.version) {
|
|
2359
|
-
|
|
2766
|
+
setInstallMessage(`DSH 无法确定 ${plugin.title} 本次安装的唯一结果,未确认完成。请先在 DSH 插件列表核对;若已经正常使用同版则无需重复安装,仍需核对来源时请联系管理员。`,
|
|
2767
|
+
'本机插件安装结果待核对,请先确认本机状态,勿重复安装。')
|
|
2360
2768
|
return
|
|
2361
2769
|
}
|
|
2362
2770
|
const diagnostic = safeInstallDiagnostic({ name: outcome.error?.name,
|
|
@@ -2364,11 +2772,14 @@ window.__ModuleLoader__.load({
|
|
|
2364
2772
|
message: outcome.error?.diagnostic || outcome.error?.message || outcome.packageResult?.output })
|
|
2365
2773
|
let restoreNotice = ''
|
|
2366
2774
|
if (updateRestore) {
|
|
2367
|
-
|
|
2775
|
+
const restoreConfirmed = ['applied', 'restart-required'].includes(updateRestore.application)
|
|
2776
|
+
if (!restoreConfirmed) markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2777
|
+
restoreNotice = restoreConfirmed
|
|
2368
2778
|
? ` ${translateLocaleText('DSH 已恢复旧版本 {version}。', { version: local.version })}`
|
|
2369
2779
|
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2370
2780
|
} else if (removedForUpdate) {
|
|
2371
2781
|
const restoreState = await restorePreviousVersion()
|
|
2782
|
+
if (restoreState !== 'restored') markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2372
2783
|
restoreNotice = restoreState === 'restored'
|
|
2373
2784
|
? translateLocaleText('DSH 已恢复旧版本号 {version}。{source_notice}', {
|
|
2374
2785
|
version: local.version,
|
|
@@ -2378,14 +2789,15 @@ window.__ModuleLoader__.load({
|
|
|
2378
2789
|
'本机列表显示审核版本 {version} 已安装,但结果未确认。', { version: plugin.version })}`
|
|
2379
2790
|
: ` ${translateLocaleText('DSH 未能确认旧版本恢复,请先在插件列表核对。')}`
|
|
2380
2791
|
}
|
|
2381
|
-
|
|
2792
|
+
setInstallMessage(`${plugin.title} 安装未完成(请求编号 ${requestReference})。${diagnostic
|
|
2382
2793
|
? `DSH 诊断:${diagnostic}。` : 'DSH 没有返回失败诊断。'}${restoreNotice}请在 DSH 插件管理查看状态或联系管理员。`)
|
|
2383
2794
|
return
|
|
2384
2795
|
}
|
|
2385
2796
|
if (outcome.bundle !== plugin.package_name
|
|
2386
2797
|
|| !['restart-required', 'applied'].includes(outcome.application)) {
|
|
2387
|
-
|
|
2388
|
-
|
|
2798
|
+
markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2799
|
+
setInstallMessage(`DSH 未确认 ${plugin.title} 已安装。请先在插件列表核对,勿重复点击安装。`,
|
|
2800
|
+
'本机插件安装结果待核对,请先确认本机状态,勿重复安装。')
|
|
2389
2801
|
return
|
|
2390
2802
|
}
|
|
2391
2803
|
let observed = null
|
|
@@ -2396,19 +2808,24 @@ window.__ModuleLoader__.load({
|
|
|
2396
2808
|
}
|
|
2397
2809
|
} catch {}
|
|
2398
2810
|
if ((outcome.version && outcome.version !== plugin.version) || observed?.version !== plugin.version) {
|
|
2399
|
-
|
|
2400
|
-
|
|
2811
|
+
markCurrentInstallUncertain(local, expectedPreviousSource)
|
|
2812
|
+
setInstallMessage(`DSH 未确认 ${plugin.title} 的审核版本已安装。请先在插件列表核对,勿重复点击安装。`,
|
|
2813
|
+
'本机插件安装结果待核对,请先确认本机状态,勿重复安装。')
|
|
2401
2814
|
return
|
|
2402
2815
|
}
|
|
2403
|
-
await refresh()
|
|
2404
2816
|
if (artifactUrl && plugin.artifact_sha256) {
|
|
2405
2817
|
try { localStorage.setItem(`${PLUGIN_INSTALL_RECORD_PREFIX}${plugin.id}`, plugin.artifact_sha256) } catch {}
|
|
2406
2818
|
}
|
|
2407
|
-
setCompletedId(plugin.id)
|
|
2408
|
-
|
|
2819
|
+
if (isOperationScopeCurrent()) setCompletedId(plugin.id)
|
|
2820
|
+
setInstallMessage(`${plugin.title} 已安装。请完全退出并重新启动 DSH 后使用。`,
|
|
2821
|
+
'本机插件已安装或更新;请完全退出并重新启动 DSH 后使用。')
|
|
2822
|
+
requestPluginRefresh()
|
|
2409
2823
|
} catch (error) {
|
|
2410
|
-
|
|
2411
|
-
|
|
2824
|
+
setInstallMessage(error instanceof Error ? error.message : '插件安装未完成,请稍后重试。',
|
|
2825
|
+
'本机此前启动的插件操作已结束;请刷新本机状态后再操作。')
|
|
2826
|
+
} finally {
|
|
2827
|
+
setWorkingId('')
|
|
2828
|
+
}
|
|
2412
2829
|
}
|
|
2413
2830
|
const activeFilter = embedded ? externalFilter : filter
|
|
2414
2831
|
const activeCategoryFilter = embedded ? externalCategoryFilter : categoryFilter
|
|
@@ -2467,13 +2884,18 @@ window.__ModuleLoader__.load({
|
|
|
2467
2884
|
h('div', null,
|
|
2468
2885
|
h('h1', { style: { margin: 0, fontSize: 28 } }, '企业精选插件'),
|
|
2469
2886
|
h('p', { style: { ...mutedTextStyle, marginTop: 6 } }, '这里展示团队审核过的插件版本及本机状态。DSH 0.2.0-rc.2 不会自动更新 Bundle;插件自身的更新器可能绕过此目录安装其他版本。')),
|
|
2470
|
-
h('button', { type: 'button', disabled: loading, onClick: () =>
|
|
2887
|
+
h('button', { type: 'button', disabled: loading, onClick: () => {
|
|
2888
|
+
if (!workingAny) setMessage('')
|
|
2889
|
+
void refresh()
|
|
2890
|
+
}, style: secondaryButtonStyle }, loading ? '读取中…' : '刷新目录')),
|
|
2471
2891
|
!loading && !plugins.length && !loadError ? embedded
|
|
2472
2892
|
? h('p', { role: 'status', style: mutedTextStyle }, '团队暂未发布精选插件。')
|
|
2473
2893
|
: h('section', { role: 'status', style: panelStyle },
|
|
2474
2894
|
h('h2', { style: { margin: 0, fontSize: 18 } }, '团队暂未发布精选插件'),
|
|
2475
2895
|
h('p', { style: mutedTextStyle }, '需要某个插件?请联系企业管理员,说明用途或提供插件地址。审核发布后会显示在这里。')) : null,
|
|
2476
|
-
|
|
2896
|
+
refreshMessage && (refreshMessageIsError || plugins.length)
|
|
2897
|
+
? h('p', { role: refreshMessageIsError ? 'alert' : 'status', style: feedbackNoticeStyle }, refreshMessage) : null,
|
|
2898
|
+
message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
2477
2899
|
plugins.length && !embedded ? h(ExtensionRequestNotice) : null,
|
|
2478
2900
|
plugins.length && !embedded ? h('div', { style: panelStyle },
|
|
2479
2901
|
h('input', { type: 'search', value: query, onChange: (event) => onQueryChange(event.target.value), placeholder: '搜索插件名称或用途', 'aria-label': '搜索企业精选插件', style: inputStyle }),
|
|
@@ -2486,7 +2908,6 @@ window.__ModuleLoader__.load({
|
|
|
2486
2908
|
'aria-label': '筛选插件用途类别', style: { ...inputStyle, width: 'auto', minHeight: 34 } },
|
|
2487
2909
|
h('option', { value: 'all' }, '全部用途'),
|
|
2488
2910
|
...extensionCategories.map(([category, label]) => h('option', { key: category, value: category }, label)))))) : null,
|
|
2489
|
-
plugins.length && message ? h('p', { role: 'status', style: feedbackNoticeStyle }, message) : null,
|
|
2490
2911
|
visible.length ? h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 420px), 1fr))', gap: 16 } },
|
|
2491
2912
|
...visible.map((plugin) => {
|
|
2492
2913
|
const local = bundles.find((bundle) => bundle.name === plugin.package_name && bundle.installed)
|
|
@@ -2495,11 +2916,13 @@ window.__ModuleLoader__.load({
|
|
|
2495
2916
|
const artifactUrl = approvedPluginArtifactUrl(plugin)
|
|
2496
2917
|
const installSpec = pluginInstallSpec(plugin)
|
|
2497
2918
|
const localAhead = local?.version && newerVersion(local.version, plugin.version)
|
|
2919
|
+
const uncertainInstall = uncertainInstallFor(plugin)
|
|
2498
2920
|
const sourceKnown = typeof local?.source === 'string'
|
|
2499
2921
|
const verifiedLocal = sameVersion && artifactUrl && sourceKnown && local.source === artifactUrl
|
|
2500
2922
|
const recordedInstall = sameVersion && !sourceKnown && previouslyInstalledReviewedPlugin(plugin)
|
|
2501
2923
|
const status = security ? '已废弃(安全风险)'
|
|
2502
|
-
:
|
|
2924
|
+
: !bundleListReady ? loading ? '读取中…' : translateLocaleText('本机状态无法读取')
|
|
2925
|
+
: plugin.status === 'deprecated' ? local?.version ? `已废弃 · 本机 ${local.version}` : '已废弃' : local?.error ? '加载异常' : localAhead
|
|
2503
2926
|
? '本机版本高于团队目录版本' : sameVersion
|
|
2504
2927
|
? verifiedLocal ? local.enabled ? '本机已装(团队来源,已启用)' : '本机已装(团队来源,未启用)'
|
|
2505
2928
|
: sourceKnown ? '本机同版,来源不同' : recordedInstall
|
|
@@ -2536,14 +2959,18 @@ window.__ModuleLoader__.load({
|
|
|
2536
2959
|
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8 } },
|
|
2537
2960
|
installSpec && (!local || (local.version && local.version !== plugin.version && (!localAhead || artifactUrl))
|
|
2538
2961
|
|| (artifactUrl && sameVersion && !verifiedLocal))
|
|
2539
|
-
? h('button', { type: 'button', disabled:
|
|
2962
|
+
? h('button', { type: 'button', disabled: loading || !bundleListReady
|
|
2963
|
+
|| workingAny || Boolean(uncertainInstall) || completedId === plugin.id,
|
|
2540
2964
|
onClick: () => void install(plugin), style: primaryButtonStyle },
|
|
2541
|
-
workingId === plugin.id ? '安装中…' :
|
|
2965
|
+
workingId === plugin.id ? '安装中…' : uncertainInstall ? '先核对安装结果'
|
|
2542
2966
|
: completedId === plugin.id ? '重启 DSH 后生效'
|
|
2543
2967
|
: local ? sameVersion || localAhead ? '按团队审核包重新安装' : '更新到审核版本' : '安装到这台 DSH') : null,
|
|
2968
|
+
uncertainInstall ? h('button', { type: 'button', disabled: loading || workingAny,
|
|
2969
|
+
onClick: () => { if (!workingAny) setMessage(''); void refresh(plugin.id) },
|
|
2970
|
+
style: secondaryButtonStyle }, translateLocaleText('重新核对本机状态')) : null,
|
|
2544
2971
|
plugin.status === 'published' && !local && !installSpec
|
|
2545
2972
|
? h('p', { style: mutedTextStyle }, '如需安装,请联系企业管理员安排。') : null,
|
|
2546
|
-
local ||
|
|
2973
|
+
local || uncertainInstall ? h('button', { type: 'button', onClick: () => openBundle(plugin.package_name), style: secondaryButtonStyle }, '打开 DSH 插件管理') : null,
|
|
2547
2974
|
local && !sameVersion && plugin.status === 'published' && !installSpec
|
|
2548
2975
|
? h('p', { style: mutedTextStyle }, '本机版本与团队审核版本不同;请联系企业管理员核对。') : null))
|
|
2549
2976
|
})) : !loading && plugins.length && (!embedded || !hasSkillMatches)
|
|
@@ -3316,6 +3743,7 @@ window.__ModuleLoader__.load({
|
|
|
3316
3743
|
setMessage(`未清除本机凭证和模型设置:${error instanceof Error ? error.message : '请稍后重试。'}`)
|
|
3317
3744
|
return
|
|
3318
3745
|
}
|
|
3746
|
+
setPluginRuntimeServiceScope('')
|
|
3319
3747
|
try {
|
|
3320
3748
|
await clearLocalConnection()
|
|
3321
3749
|
setMessage('此设备的 ANT 凭证已撤销,ANT 模型设置已清除。团队服务地址已保留。')
|
|
@@ -3738,7 +4166,8 @@ window.__ModuleLoader__.load({
|
|
|
3738
4166
|
dshCompatibilityCard,
|
|
3739
4167
|
showReleaseNotes ? h('section', { style: { ...infoNoticeStyle, padding: 12 } },
|
|
3740
4168
|
h('strong', { style: { fontSize: 14, lineHeight: 1.4 } }, '本版更新'),
|
|
3741
|
-
h('
|
|
4169
|
+
h('div', { 'aria-label': '更新说明内容', style: { display: 'grid', gap: 8 } },
|
|
4170
|
+
...releaseNoteBlocks(bundleRelease.release_notes))) : null,
|
|
3742
4171
|
['incompatible', 'unknown'].includes(serviceCompatibility.state) ? h('p', { role: 'status', style: {
|
|
3743
4172
|
...feedbackNoticeStyle,
|
|
3744
4173
|
color: serviceCompatibility.state === 'incompatible' ? 'var(--dsw-alias-state-error-primary)'
|
|
@@ -3781,14 +4210,86 @@ window.__ModuleLoader__.load({
|
|
|
3781
4210
|
useLocaleRevision()
|
|
3782
4211
|
const [tab, setTab] = useState('all')
|
|
3783
4212
|
const [query, setQuery] = useState('')
|
|
4213
|
+
const serviceUrl = loadServiceUrl()
|
|
4214
|
+
const [, forcePluginRuntimeRender] = useState(0)
|
|
4215
|
+
useEffect(() => {
|
|
4216
|
+
const refresh = () => forcePluginRuntimeRender((revision) => revision + 1)
|
|
4217
|
+
pluginRuntimeState.listeners.add(refresh)
|
|
4218
|
+
setPluginRuntimeServiceScope(serviceUrl)
|
|
4219
|
+
return () => pluginRuntimeState.listeners.delete(refresh)
|
|
4220
|
+
}, [serviceUrl])
|
|
4221
|
+
const runtimeScopeMatches = pluginRuntimeState.serviceUrl === serviceUrl
|
|
4222
|
+
const uncertainInstalls = pluginRuntimeState.uncertainInstallsRef.current
|
|
4223
|
+
const uncertainInstallsRef = pluginRuntimeState.uncertainInstallsRef
|
|
4224
|
+
const workingAny = Boolean(pluginRuntimeState.workingId)
|
|
4225
|
+
const workingId = runtimeScopeMatches && pluginRuntimeState.workingServiceUrl === serviceUrl
|
|
4226
|
+
? pluginRuntimeState.workingId : ''
|
|
4227
|
+
const completedId = runtimeScopeMatches ? pluginRuntimeState.completedId : ''
|
|
4228
|
+
const message = runtimeScopeMatches ? pluginRuntimeState.message : ''
|
|
4229
|
+
const refreshMessage = runtimeScopeMatches ? pluginRuntimeState.refreshMessage : ''
|
|
4230
|
+
const refreshMessageIsError = runtimeScopeMatches && pluginRuntimeState.refreshMessageIsError
|
|
4231
|
+
const setUncertainInstalls = (next) => {
|
|
4232
|
+
const value = typeof next === 'function' ? next(pluginRuntimeState.uncertainInstallsRef.current) : next
|
|
4233
|
+
pluginRuntimeState.uncertainInstalls.clear()
|
|
4234
|
+
for (const [pluginId, uncertainInstall] of Object.entries(value)) {
|
|
4235
|
+
pluginRuntimeState.uncertainInstalls.set(pluginId, uncertainInstall)
|
|
4236
|
+
}
|
|
4237
|
+
publishPluginRuntimeState()
|
|
4238
|
+
}
|
|
4239
|
+
const setWorkingId = (value) => {
|
|
4240
|
+
pluginRuntimeState.workingId = value
|
|
4241
|
+
pluginRuntimeState.workingServiceUrl = value ? serviceUrl : ''
|
|
4242
|
+
if (!value && pluginRuntimeState.message === pluginScopeBusyMessage) {
|
|
4243
|
+
pluginRuntimeState.message = ''
|
|
4244
|
+
pluginRuntimeState.messagePluginId.current = ''
|
|
4245
|
+
pluginRuntimeState.messagePackageName.current = ''
|
|
4246
|
+
pluginRuntimeState.messageClearOnResolve.current = false
|
|
4247
|
+
pluginRuntimeState.messageVersion.current += 1
|
|
4248
|
+
}
|
|
4249
|
+
publishPluginRuntimeState()
|
|
4250
|
+
}
|
|
4251
|
+
const setCompletedId = (value) => {
|
|
4252
|
+
pluginRuntimeState.completedId = value
|
|
4253
|
+
publishPluginRuntimeState()
|
|
4254
|
+
}
|
|
4255
|
+
const setMessage = (value, pluginId = '', packageName = '', clearOnResolve = false) => {
|
|
4256
|
+
pluginRuntimeState.message = value
|
|
4257
|
+
pluginRuntimeState.messagePluginId.current = value ? pluginId : ''
|
|
4258
|
+
pluginRuntimeState.messagePackageName.current = value ? packageName : ''
|
|
4259
|
+
pluginRuntimeState.messageClearOnResolve.current = Boolean(value) && clearOnResolve
|
|
4260
|
+
pluginRuntimeState.messageVersion.current += 1
|
|
4261
|
+
pluginRuntimeState.refreshMessage = ''
|
|
4262
|
+
pluginRuntimeState.refreshMessageIsError = false
|
|
4263
|
+
publishPluginRuntimeState()
|
|
4264
|
+
}
|
|
4265
|
+
const setRefreshFeedback = (value, isError = false) => {
|
|
4266
|
+
pluginRuntimeState.refreshMessage = value
|
|
4267
|
+
pluginRuntimeState.refreshMessageIsError = isError
|
|
4268
|
+
publishPluginRuntimeState()
|
|
4269
|
+
}
|
|
4270
|
+
const requestPluginRefresh = () => {
|
|
4271
|
+
pluginRuntimeState.refreshRevision += 1
|
|
4272
|
+
publishPluginRuntimeState()
|
|
4273
|
+
}
|
|
4274
|
+
const pluginPageState = { uncertainInstalls, setUncertainInstalls, uncertainInstallsRef,
|
|
4275
|
+
workingId, setWorkingId, completedId, setCompletedId, message, setMessage,
|
|
4276
|
+
refreshMessage, refreshMessageIsError, setRefreshFeedback, requestPluginRefresh,
|
|
4277
|
+
workingAny, serviceUrl,
|
|
4278
|
+
messageVersion: pluginRuntimeState.messageVersion,
|
|
4279
|
+
messagePluginId: pluginRuntimeState.messagePluginId,
|
|
4280
|
+
messagePackageName: pluginRuntimeState.messagePackageName,
|
|
4281
|
+
messageClearOnResolve: pluginRuntimeState.messageClearOnResolve,
|
|
4282
|
+
refreshGeneration: pluginRuntimeState.refreshGeneration,
|
|
4283
|
+
refreshRevision: pluginRuntimeState.refreshRevision }
|
|
3784
4284
|
const navigation = {
|
|
3785
4285
|
onAll: () => setTab('all'), onSkills: () => setTab('skills'),
|
|
3786
4286
|
onPlugins: () => setTab('plugins'),
|
|
3787
4287
|
}
|
|
3788
4288
|
return tab === 'plugins'
|
|
3789
|
-
? h(PluginCenterPage, { remote, onBack, openBundle, query, onQueryChange: setQuery,
|
|
4289
|
+
? h(PluginCenterPage, { remote, onBack, openBundle, query, onQueryChange: setQuery,
|
|
4290
|
+
pluginPageState, ...navigation })
|
|
3790
4291
|
: h(SkillCenterPage, { remote, onBack, openBundle, query, onQueryChange: setQuery,
|
|
3791
|
-
includePlugins: tab === 'all', ...navigation })
|
|
4292
|
+
pluginPageState, includePlugins: tab === 'all', ...navigation })
|
|
3792
4293
|
}
|
|
3793
4294
|
|
|
3794
4295
|
function brandingCacheKey() {
|