@ai-wendao/ant 0.5.8 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -517,23 +517,26 @@ window.__ModuleLoader__.load({
517
517
  const [localConcurrency, setLocalConcurrency] = useState(null)
518
518
  const [repositoryCacheLimitGib, setRepositoryCacheLimitGib] = useState(10)
519
519
  const [botId, setBotId] = useState('')
520
- const [trustedOrigin, setTrustedOrigin] = useState('')
521
520
  const [conversationKind, setConversationKind] = useState('dm')
522
521
  const [groupId, setGroupId] = useState('')
523
522
  const [ruleDescription, setRuleDescription] = useState('')
524
- const [triggerPhrases, setTriggerPhrases] = useState('')
525
523
  const recognizedBaselineRef = useRef(null)
526
524
  const recognizedDescriptionRef = useRef(null)
525
+ const autoAnalyzeDescriptionRef = useRef(null)
526
+ const recognitionRequestRef = useRef(0)
527
+ const recognitionTimerRef = useRef(null)
527
528
  const [recognitionBusy, setRecognitionBusy] = useState(false)
528
529
  const [recognitionMessage, setRecognitionMessage] = useState('')
530
+ const [missingRequired, setMissingRequired] = useState([])
531
+ const [suggestions, setSuggestions] = useState([])
532
+ const [ruleExamples, setRuleExamples] = useState([])
529
533
  const [title, setTitle] = useState('')
530
534
  const [intent, setIntent] = useState('')
531
535
  const [taskPrompt, setTaskPrompt] = useState('')
532
536
  const [runner, setRunner] = useState('dsh')
533
537
  const [reviewers, setReviewers] = useState(['dsh'])
534
- const [crossReviewAlways, setCrossReviewAlways] = useState(false)
535
538
  const [parameters, setParameters] = useState([])
536
- const [crossReviewOpen, setCrossReviewOpen] = useState(false)
539
+ const [parallelAgentsOpen, setParallelAgentsOpen] = useState(false)
537
540
  const [giteaToken, setGiteaToken] = useState('')
538
541
  const [tokenConfigured, setTokenConfigured] = useState(false)
539
542
  const [senderScope, setSenderScope] = useState('any')
@@ -589,6 +592,9 @@ window.__ModuleLoader__.load({
589
592
  const selectedBot = bots.find((bot) => bot.bot_id === botId)
590
593
  const botConnected = Boolean(selectedBot?.available && selectedBot?.connected)
591
594
  const selectedEnrollment = enrolled.find((bot) => bot.bot_fingerprint === selectedBot?.fingerprint)
595
+ const selectedGiteaOrigin = normalizeServiceUrl(selectedEnrollment?.gitea_origin)
596
+ const taskPrOrigins = [...new Set(parameters.filter((item) => item.kind === 'gitea_pr')
597
+ .map((item) => normalizeServiceUrl(item.gitea_origin)).filter(Boolean))]
592
598
  const selectedEnrollmentBound = selectedEnrollment?.device_bound_to_current === true
593
599
  const dmChatSettingAvailable = typeof selectedEnrollment?.dm_chat_enabled === 'boolean'
594
600
  const dmChatEnabled = selectedEnrollment?.dm_chat_enabled !== false
@@ -604,9 +610,12 @@ window.__ModuleLoader__.load({
604
610
  ? '允许此机器人接收私聊;任务仍按已开放的规则执行。'
605
611
  : '新私聊消息不会交给 DSH;已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
606
612
  const availableGroups = groups.filter((group) => group.bot_fingerprint === selectedBot?.fingerprint)
613
+ const groupLabel = (group) => {
614
+ if (!group) return ''
615
+ const name = typeof group?.group_name === 'string' ? group.group_name.trim() : ''
616
+ return name ? `${name}(${group.group_code.slice(-2)})` : group.group_code
617
+ }
607
618
  const groupChatLoadFailed = loadFailures.includes('groups')
608
- const groupSummary = (group) => group?.group_name
609
- ? `${group.group_name} · ${group.group_code.slice(-2)}` : group?.group_code || ''
610
619
  const activeCards = cards.filter((card) => !card.revoked_at && card.expires_at * 1000 > Date.now())
611
620
  const connectedBots = bots.filter((bot) => bot.available && bot.connected)
612
621
  const authorizationLoadError = loadFailures.includes('cards')
@@ -614,8 +623,6 @@ window.__ModuleLoader__.load({
614
623
  const enrolledCount = enrolled.filter((item) => item.device_bound_to_current
615
624
  && connectedBots.some((bot) => bot.fingerprint === item.bot_fingerprint)).length
616
625
  const selectedTaskCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint).length
617
- const legacyTriggerCardCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint
618
- && (!Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length)).length
619
626
  const collaborationStatus = loading ? '正在读取机器人与任务状态…'
620
627
  : botLoadError ? '暂时无法读取机器人状态。请刷新后再试。'
621
628
  : connectedBots.length === 0 ? '尚未发现已连接的机器人。请先在 DSH-IM 中连接飞书机器人。'
@@ -636,34 +643,42 @@ window.__ModuleLoader__.load({
636
643
  const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
637
644
  const hasSelectedPrRule = activeCards.some((card) => card.bot_fingerprint === selectedBot?.fingerprint
638
645
  && Array.isArray(card.parameters) && card.parameters.some((rule) => rule.kind === 'gitea_pr'))
639
- const showPrSecuritySettings = Boolean(selectedBot && (hasPrParameter || hasSelectedPrRule))
640
- const selectedGroupSummary = groupSummary(availableGroups.find((group) => group.group_id === groupId)) || groupId
641
- const codexAvailable = executors.some((item) => item.id === 'codex-readonly' && item.available)
646
+ const showPrSecuritySettings = Boolean(selectedBot
647
+ && (hasPrParameter || hasSelectedPrRule))
648
+ const selectedGroupSummary = groupLabel(availableGroups.find((group) => group.group_id === groupId)) || groupId
649
+ const codexAvailable = executors.some((item) => item.id === 'codex' && item.available)
642
650
  const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
643
651
  const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
644
652
  const nativePrUnavailableReason = executors.find((item) => item.reason)?.reason || null
645
653
  const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
646
654
  const cardReviewerSummary = (card) => {
647
655
  const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
648
- return selected.length > 1
649
- ? card.cross_review_always === true
650
- ? `每次交叉审查:${selected.map(reviewerLabel).join('、')}`
651
- : `交叉可用:${selected.map(reviewerLabel).join('、')};默认 ${reviewerLabel(card.runner)}`
652
- : card.runner === 'codex' ? 'Codex PR 快照审查'
653
- : card.runner === 'claude-code' ? 'Claude Code PR 快照审查'
654
- : 'DSH 文字处理'
656
+ const unavailable = selected.filter((runner) => runner !== 'dsh')
657
+ if (card.runner !== 'dsh') return `${reviewerLabel(card.runner)} 默认执行方暂不可用`
658
+ return unavailable.length
659
+ ? `默认 DSH;${unavailable.map(reviewerLabel).join('、')} 暂不可用`
660
+ : 'DSH 任务 Agent'
655
661
  }
656
662
  const requesterSummary = senderScope === 'allowlist'
657
663
  ? `仅指定成员:${senderIds.trim() || '尚未填写成员 ID'}`
658
664
  : conversationKind === 'group' ? '群内所有成员' : '任何能私聊此机器人的人'
659
665
  const resultRouteSummary = conversationKind === 'group' ? '回复到原群(群内可见)' : '回复到发起任务的私聊'
660
- const ruleTriggerSummary = triggerPhrases.split('\n').map((item) => item.trim()).filter(Boolean)
661
- const ruleSummaryReady = Boolean(title || intent || ruleTriggerSummary.length || parameters.length || taskPrompt)
666
+ const ruleSummaryCurrent = recognizedDescriptionRef.current === ruleDescription.trim()
667
+ const ruleSummaryPending = Boolean(ruleDescription.trim() && !ruleSummaryCurrent)
668
+ const ruleSummaryReady = Boolean(ruleSummaryCurrent && (title || intent || parameters.length || taskPrompt))
669
+ const missingHarnessInputs = [...new Set([
670
+ ...missingRequired,
671
+ ...(parameters.some((rule) => rule.kind === 'gitea_pr'
672
+ && (typeof rule.gitea_origin !== 'string' || !normalizeServiceUrl(rule.gitea_origin) || !rule.repository))
673
+ ? ['PR 来源和仓库'] : []),
674
+ ])]
662
675
  const parameterSummary = (rule) => {
663
676
  const parts = [rule.required === false ? '可选' : '必填']
664
677
  const choices = Array.isArray(rule.choices) ? rule.choices
665
678
  : typeof rule.choices === 'string' ? rule.choices.split(/[,,\n]/u).map((item) => item.trim()).filter(Boolean) : []
666
679
  if (rule.kind === 'gitea_pr' && rule.repository) parts.push(`仓库 ${rule.repository}`)
680
+ if (rule.kind === 'gitea_pr' && rule.gitea_origin) parts.push(`来源 ${rule.gitea_origin}`)
681
+ if (rule.kind === 'git_sha') parts.push('可指定 Head SHA;未指定时用最新 Head')
667
682
  else if (rule.kind === 'choice' && choices.length) parts.push(`选项 ${choices.join('、')}`)
668
683
  else if (rule.kind === 'integer') {
669
684
  const minValue = rule.min == null || rule.min === '' ? null : Number(rule.min)
@@ -683,54 +698,49 @@ window.__ModuleLoader__.load({
683
698
  ? `请求时还需提供:${parameters.map(parameterSummary).join(';')}`
684
699
  : '无需额外参数,按请求中的内容处理'
685
700
  const ruleSummaryNarrative = ruleSummaryReady
686
- ? `${ruleTriggerSummary.length ? `消息以“${ruleTriggerSummary.join('”或“')}”开头时,` : ''}${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
701
+ ? `${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
687
702
  : '识别后会用一段摘要呈现系统理解的任务。'
688
- const executorSummary = reviewers.length > 1
689
- ? crossReviewAlways ? `每次并行交叉审查(${reviewers.map(reviewerLabel).join('、')})`
690
- : `默认 ${reviewerLabel(runner)};可逐次交叉审查`
691
- : runner === 'codex' ? '本机 Codex · PR 快照'
692
- : runner === 'claude-code' ? '本机 Claude Code · PR 快照' : 'DSH · 当前授权模型'
693
- const crossReviewSummary = reviewers.filter((item) => item !== runner).map(reviewerLabel).join('、')
694
- const crossReviewHeading = crossReviewAlways && reviewers.length > 1
695
- ? `每次交叉审查:${reviewers.map(reviewerLabel).join('、')}`
696
- : crossReviewSummary ? `交叉审查:${crossReviewSummary}` : '交叉审查(可选)'
703
+ const executorSummary = runner === 'codex' ? 'DSH Codex 子 Agent'
704
+ : runner === 'claude-code' ? 'DSH Claude Code 子 Agent' : 'DSH · 当前授权模型'
705
+ const parallelAgentsSummary = reviewers.filter((item) => item !== runner).map(reviewerLabel).join('、')
697
706
  const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
698
707
  : !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
699
- : loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 2
700
- ? '团队服务端尚未支持触发规则的持久化与执行前核验;请先升级 ANT 服务端。'
708
+ : loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 3
709
+ ? '团队服务端尚未支持 AI 任务 Harness 与运行时授权核验;请先升级 ANT 服务端。'
701
710
  : !ruleDescription.trim() ? '请先用自然语言描述要做的任务。'
702
- : recognizedDescriptionRef.current !== null && recognizedDescriptionRef.current !== ruleDescription.trim()
703
- ? '描述已修改,请重新识别,让执行规则与描述保持一致。'
704
- : !title.trim() || !intent.trim() || !taskPrompt.trim() ? '先识别任务描述并检查摘要,再开放任务。'
705
- : !triggerPhrases.trim() ? '描述里还没有明确写出何时触发。请补充触发方式并重新识别。'
711
+ : recognizedDescriptionRef.current !== ruleDescription.trim()
712
+ ? 'AI 正在根据最新描述检查任务 Harness;分析完成后再开放。'
713
+ : !title.trim() || !intent.trim() || !taskPrompt.trim() ? '等待 AI 生成任务理解和处理要求。'
714
+ : missingHarnessInputs.length ? `任务范围还有必需信息未明确:${missingHarnessInputs.join('、')}。请补充描述。`
706
715
  : conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
707
716
  : conversationKind === 'group' && !groupId ? '请在“请求范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
708
717
  : senderScope === 'allowlist' && !senderIds.trim() ? '请填写可请求成员的 IM ID。'
718
+ : reviewers.some((item) => item !== 'dsh')
719
+ ? '当前只允许 DSH 执行协作任务。请将默认执行方改为 DSH,并移除未获准的外部 Agent。'
709
720
  : parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
710
- : parameters.some((rule) => rule.kind === 'gitea_pr') && !selectedEnrollment?.gitea_origin
711
- ? 'PR 来源尚未信任。请到“本机与任务”设置可信 Gitea 来源。'
712
- : parameters.some((rule) => rule.kind === 'gitea_pr')
713
- && normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
714
- ? '请先在“本机与任务”保存可信 Gitea 来源;任务描述中的 PR 链接会使用这个来源。'
715
- : nativeReviewerSelected && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
716
- ? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
717
- : nativeReviewerSelected && !parameters[0].required
718
- ? '本机代码审查的 Gitea PR 编号必须设为必填。'
719
- : reviewers.includes('dsh') && !dshAvailable
720
- ? '这台 DSH 尚未准备好文字审查执行器。'
721
- : reviewers.includes('codex') && !codexAvailable
722
- ? '这台 DSH 尚未找到可用的 Codex CLI,请先安装或检查 DSH 的 PATH。'
723
- : reviewers.includes('claude-code') && !claudeCodeAvailable
724
- ? '这台 DSH 尚未注册 Claude Code 子代理,请先在 DSH 中安装并启用它。'
721
+ : parameters.some((rule) => rule.kind === 'gitea_pr'
722
+ && (typeof rule.gitea_origin !== 'string' || !normalizeServiceUrl(rule.gitea_origin) || !rule.repository))
723
+ ? 'PR 来源或仓库尚未从自然语言描述中明确识别,请补充完整的 PR 来源链接或仓库。'
724
+ : parameters.some((rule) => rule.kind === 'gitea_pr'
725
+ && selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)
726
+ ? `任务描述中的 Gitea 来源必须与此机器人的连接器主机一致(${selectedGiteaOrigin});为保护令牌,ANT 不会向其他主机发送凭据。`
727
+ : taskPrOrigins.length > 1
728
+ ? '同一任务中的 PR 资源必须来自同一个 Gitea 主机。'
729
+ : nativeReviewerSelected && (parameters.filter((rule) => rule.kind === 'gitea_pr').length !== 1
730
+ || parameters.some((rule) => !['gitea_pr', 'git_sha'].includes(rule.kind)))
731
+ ? 'Codex 和 Claude Code 当前只支持一个 Gitea PR 资源连接器;任务具体做审查、总结或解释仍由自然语言描述决定。'
732
+ : nativeReviewerSelected && !parameters.find((rule) => rule.kind === 'gitea_pr')?.required
733
+ ? '使用本机 Provider 时,Gitea PR 资源必须是必填项。'
734
+ : reviewers.includes('dsh') && !dshAvailable
735
+ ? '这台 DSH 尚未准备好任务执行器。'
725
736
  : ''
726
737
  const loadNames = { bots: '机器人', 'enrolled-bots': '协作启用状态', groups: '群聊列表',
727
738
  cards: '任务授权', settings: '团队默认值', executors: '本机执行器',
728
739
  'local-settings': '本机设置', status: '最近任务' }
729
740
  const changeReviewer = (id, enabled) => {
730
741
  const next = enabled ? [...reviewers, id] : reviewers.filter((item) => item !== id)
731
- if (!next.length) { setMessage('至少保留一位审查 Agent。'); return }
742
+ if (!next.length) { setMessage('至少保留一位任务执行 Agent。'); return }
732
743
  setReviewers([...new Set(next)])
733
- if (next.length < 2) setCrossReviewAlways(false)
734
744
  if (!next.includes(runner)) setRunner(next[0])
735
745
  }
736
746
  const selectRunner = (id) => {
@@ -745,48 +755,57 @@ window.__ModuleLoader__.load({
745
755
  setRecognitionMessage('')
746
756
  recognizedBaselineRef.current = null
747
757
  recognizedDescriptionRef.current = null
758
+ autoAnalyzeDescriptionRef.current = null
759
+ recognitionRequestRef.current += 1
748
760
  setRuleDescription('')
749
- setTriggerPhrases('')
761
+ setMissingRequired([]); setSuggestions([]); setRuleExamples([])
750
762
  setTitle(''); setIntent(''); setTaskPrompt(''); setRunner('dsh')
751
- setReviewers(['dsh']); setCrossReviewAlways(false); setCrossReviewOpen(false)
763
+ setReviewers(['dsh']); setParallelAgentsOpen(false)
752
764
  setParameters([]); setConversationKind(context.conversationKind || 'dm')
753
765
  setGroupId(context.groupId || '')
754
766
  setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
755
767
  if (context.botId) setBotId(context.botId)
756
768
  }
757
- const taskRuleDraftFromCard = (card, title) => ({
758
- title: title || '', intent: card.intent || '', task_prompt: card.task_prompt || '',
759
- trigger_phrases: Array.isArray(card.trigger_phrases) ? card.trigger_phrases.map((item) => item.trim()).filter(Boolean) : [],
760
- parameters: (card.parameters || []).map((rule) => ({
769
+ const taskRuleDraftFromCard = (card, title) => {
770
+ const rules = (card.parameters || []).map((rule) => ({
761
771
  name: rule.name, kind: rule.kind, required: rule.required,
762
772
  description: rule.description || '', min: rule.min == null ? '' : String(rule.min),
763
773
  max: rule.max == null ? '' : String(rule.max),
764
774
  choices: Array.isArray(rule.choices) ? rule.choices.join(', ') : String(rule.choices ?? ''),
765
775
  gitea_origin: rule.gitea_origin || '', repository: rule.repository || '',
766
- })),
767
- })
776
+ }))
777
+ if (rules.some((rule) => rule.kind === 'gitea_pr')) {
778
+ const headIndex = rules.findIndex((rule) => rule.name === 'head_sha')
779
+ const headRule = { name: 'head_sha', kind: 'git_sha', required: false,
780
+ description: 'PR Head SHA(可选;未指定时使用任务开始处理时的最新 Head)',
781
+ min: '', max: '', choices: '', gitea_origin: '', repository: '' }
782
+ if (headIndex >= 0 && rules[headIndex].kind === 'git_sha') rules[headIndex] = headRule
783
+ else if (headIndex < 0 && rules.length < 12) rules.push(headRule)
784
+ }
785
+ return { title: title || '', intent: card.intent || '', task_prompt: card.task_prompt || '',
786
+ parameters: rules, examples: Array.isArray(card.examples) ? card.examples : [],
787
+ missing_required: [], suggestions: [] }
788
+ }
768
789
  const editCard = (card, copy = false) => {
769
790
  setBotId(card.bot_id)
770
791
  setRecognitionMessage('')
771
- setTrustedOrigin(enrolled.find((bot) => bot.bot_id === card.bot_id)?.gitea_origin || '')
792
+ autoAnalyzeDescriptionRef.current = null
793
+ recognitionRequestRef.current += 1
772
794
  setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
773
795
  const taskDraft = taskRuleDraftFromCard(card, copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
774
796
  recognizedBaselineRef.current = taskDraft
775
- const existingDescription = card.rule_description || card.intent || ''
797
+ const existingDescription = card.rule_description || ''
776
798
  recognizedDescriptionRef.current = existingDescription.trim() || null
777
- const legacyTriggerRule = !Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length
778
- setDraftNotice(legacyTriggerRule
779
- ? '这项旧任务没有明确触发方式;新版插件不会按它启动任务。请升级 ANT 服务端后重新描述、识别并开放规则。'
780
- : '')
799
+ setDraftNotice(existingDescription.trim() ? ''
800
+ : '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
781
801
  setRuleDescription(existingDescription)
782
- setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
802
+ setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
783
803
  setTitle(taskDraft.title)
784
804
  if (copy) setDays(7)
785
805
  setIntent(taskDraft.intent)
786
806
  setTaskPrompt(taskDraft.task_prompt); setRunner(card.runner)
787
807
  setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
788
- setCrossReviewAlways(card.cross_review_always === true)
789
- setCrossReviewOpen(false)
808
+ setParallelAgentsOpen(false)
790
809
  setParameters(taskDraft.parameters)
791
810
  setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
792
811
  setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
@@ -795,28 +814,35 @@ window.__ModuleLoader__.load({
795
814
  const applyRuleDraft = (draft) => {
796
815
  setTitle(draft.title)
797
816
  setIntent(draft.intent)
798
- setTriggerPhrases(draft.trigger_phrases.join('\n'))
799
817
  setTaskPrompt(draft.task_prompt)
800
818
  setParameters(draft.parameters)
819
+ setMissingRequired(draft.missing_required || [])
820
+ setSuggestions(draft.suggestions || [])
821
+ setRuleExamples(draft.examples || [])
801
822
  }
802
- const identifyRule = async () => {
803
- if (!ruleDescription.trim()) {
823
+ const identifyRule = async (description = ruleDescription) => {
824
+ const requestedDescription = description.trim()
825
+ if (!requestedDescription) {
804
826
  setRecognitionMessage('先描述这项任务何时触发、要做什么以及涉及哪些资料。')
805
827
  return
806
828
  }
807
- setRecognitionBusy(true); setMessage(''); setRecognitionMessage('正在调用团队模型识别规则…')
829
+ if (recognitionTimerRef.current) clearTimeout(recognitionTimerRef.current)
830
+ const requestNumber = ++recognitionRequestRef.current
831
+ setRecognitionBusy(true); setMessage(''); setRecognitionMessage('AI 正在分析必需范围、可选条件和执行方式…')
808
832
  try {
809
833
  const response = await fetch('/api/ant-teams/delegation/recognize', {
810
834
  method: 'POST', headers: { 'content-type': 'application/json' },
811
- body: JSON.stringify({ description: ruleDescription }),
835
+ body: JSON.stringify({ description: requestedDescription }),
812
836
  })
813
837
  const result = await readLocalJsonResponse(response)
814
838
  if (!response.ok) throw new Error(result.message || '规则识别失败。')
815
839
  const candidate = {
816
840
  title: typeof result.title === 'string' ? result.title : '',
817
841
  intent: typeof result.intent === 'string' ? result.intent : '',
818
- trigger_phrases: Array.isArray(result.trigger_phrases) ? result.trigger_phrases : [],
819
842
  task_prompt: typeof result.task_prompt === 'string' ? result.task_prompt : '',
843
+ missing_required: Array.isArray(result.missing_required) ? result.missing_required : [],
844
+ suggestions: Array.isArray(result.suggestions) ? result.suggestions : [],
845
+ examples: Array.isArray(result.examples) ? result.examples : [],
820
846
  parameters: Array.isArray(result.parameters) ? result.parameters.map((rule) => ({
821
847
  name: typeof rule?.name === 'string' ? rule.name : '',
822
848
  kind: typeof rule?.kind === 'string' ? rule.kind : 'text',
@@ -830,19 +856,45 @@ window.__ModuleLoader__.load({
830
856
  repository: typeof rule?.repository === 'string' ? rule.repository : '',
831
857
  })) : [],
832
858
  }
833
- if (!candidate.title || !candidate.intent || !candidate.task_prompt || !candidate.trigger_phrases.length) {
834
- throw new Error('识别结果不完整。请在描述中写清楚何时执行,再重新识别。')
859
+ if (candidate.parameters.some((rule) => rule.kind === 'gitea_pr')) {
860
+ const headIndex = candidate.parameters.findIndex((rule) => rule.name === 'head_sha')
861
+ if (headIndex >= 0 && candidate.parameters[headIndex].kind !== 'git_sha') {
862
+ throw new Error('PR Head SHA 与另一项请求信息重名,请调整任务描述后重新分析。')
863
+ }
864
+ const headRule = { name: 'head_sha', kind: 'git_sha', required: false,
865
+ description: 'PR Head SHA(可选;未指定时使用任务开始处理时的最新 Head)',
866
+ min: '', max: '', choices: '', gitea_origin: '', repository: '' }
867
+ if (headIndex >= 0) candidate.parameters[headIndex] = headRule
868
+ else {
869
+ if (candidate.parameters.length >= 12) throw new Error('PR 任务包含的信息项过多,请简化描述后重新分析。')
870
+ candidate.parameters.push(headRule)
871
+ }
872
+ }
873
+ if (requestNumber !== recognitionRequestRef.current || requestedDescription !== ruleDescription.trim()) return
874
+ if (!candidate.title || !candidate.intent || !candidate.task_prompt) {
875
+ throw new Error('AI 返回的任务理解不完整。请补充描述后重试。')
835
876
  }
836
877
  const previous = recognizedBaselineRef.current
837
878
  applyRuleDraft(candidate)
838
879
  recognizedBaselineRef.current = candidate
839
- recognizedDescriptionRef.current = ruleDescription.trim()
880
+ recognizedDescriptionRef.current = requestedDescription
840
881
  setRecognitionMessage(previous
841
- ? '已根据新描述更新草稿,请检查摘要和授权范围。'
842
- : '已识别为任务草稿。检查摘要和授权范围;此操作不会开放任务。')
843
- } catch (error) { setRecognitionMessage(error.message) }
844
- finally { setRecognitionBusy(false) }
882
+ ? 'AI 已更新任务理解。请复核必需信息、可选建议和运行方式;任务仍未开放。'
883
+ : 'AI 已生成待复核的任务 Harness。检查摘要和范围后再开放。')
884
+ } catch (error) {
885
+ if (requestNumber === recognitionRequestRef.current) setRecognitionMessage(error.message)
886
+ } finally {
887
+ if (requestNumber === recognitionRequestRef.current) setRecognitionBusy(false)
888
+ }
845
889
  }
890
+ useEffect(() => {
891
+ if (autoAnalyzeDescriptionRef.current !== ruleDescription || !ruleDescription.trim()) return undefined
892
+ const description = ruleDescription
893
+ recognitionTimerRef.current = setTimeout(() => {
894
+ if (autoAnalyzeDescriptionRef.current === description) void identifyRule(description)
895
+ }, 900)
896
+ return () => clearTimeout(recognitionTimerRef.current)
897
+ }, [ruleDescription])
846
898
  const saveGiteaToken = async () => {
847
899
  if (!giteaToken.trim()) return
848
900
  setBusy(true)
@@ -856,12 +908,7 @@ window.__ModuleLoader__.load({
856
908
  }
857
909
  const enrollBot = async () => {
858
910
  if (!selectedBot) return
859
- const requestedOrigin = trustedOrigin
860
- const origin = requestedOrigin?.trim() ? normalizeServiceUrl(requestedOrigin) : ''
861
- if (requestedOrigin?.trim() && !origin) {
862
- setMessage('请填写有效的 HTTPS Gitea 地址后再启用协作。')
863
- return
864
- }
911
+ const origin = selectedEnrollment?.gitea_origin || ''
865
912
  setBusy(true)
866
913
  try {
867
914
  const response = await fetch('/api/ant-teams/delegation/enrolled-bots', {
@@ -872,29 +919,10 @@ window.__ModuleLoader__.load({
872
919
  const result = await readLocalJsonResponse(response)
873
920
  if (!response.ok) throw new Error(result.message || '协作机器人启用失败。')
874
921
  await load()
875
- if (origin) setTrustedOrigin(origin)
876
922
  setMessage('协作机器人已启用。你可以分别设置私聊和群聊,再开放需要的能力。')
877
923
  } catch (error) { setMessage(error.message) }
878
924
  finally { setBusy(false) }
879
925
  }
880
- const saveTrustedOrigin = async () => {
881
- if (!selectedBot || !selectedEnrollmentBound) return
882
- const origin = normalizeServiceUrl(trustedOrigin)
883
- if (!origin) { setMessage('请填写有效的 HTTPS Gitea 地址。'); return }
884
- if (!window.confirm(`将这个机器人的可信 Gitea 地址设为 ${origin}?已开放能力中的 PR 参数需要重新核对。`)) return
885
- setBusy(true)
886
- try {
887
- const response = await fetch('/api/ant-teams/delegation/enrolled-bots', {
888
- method: 'POST', headers: { 'content-type': 'application/json' },
889
- body: JSON.stringify({ bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
890
- gitea_origin: origin }),
891
- })
892
- const result = await readLocalJsonResponse(response)
893
- if (!response.ok) throw new Error(result.message || '可信地址保存失败。')
894
- await load(); setTrustedOrigin(origin); setMessage('可信 Gitea 地址已保存。')
895
- } catch (error) { setMessage(error.message) }
896
- finally { setBusy(false) }
897
- }
898
926
  const saveGroupChat = async (enabled, targetGroupId) => {
899
927
  if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
900
928
  const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
@@ -956,7 +984,7 @@ window.__ModuleLoader__.load({
956
984
  if (!response.ok) throw new Error(result.message || '本机处理设置保存失败。')
957
985
  setLocalConcurrency(result.max_concurrency)
958
986
  setRepositoryCacheLimitGib(result.repository_cache_limit_gib)
959
- setMessage('本机并发与仓库缓存设置已保存。')
987
+ setMessage('本机并发设置已保存;仓库缓存上限保留原值。')
960
988
  } catch (error) { setMessage(error.message) }
961
989
  finally { setBusy(false) }
962
990
  }
@@ -1026,8 +1054,8 @@ window.__ModuleLoader__.load({
1026
1054
  event.preventDefault()
1027
1055
  setDraftNotice('')
1028
1056
  if (!selectedBot || !botConnected || !selectedEnrollmentBound) { setMessage('请先在这台 DSH 绑定选中的协作机器人。'); return }
1029
- if (crossReviewAlways && defaults?.cross_review_policy_version !== 1) {
1030
- setMessage('当前团队服务端尚不支持“每次都交叉审查”,请先升级 ANT 服务端。'); return
1057
+ if (reviewers.some((item) => item !== 'dsh')) {
1058
+ setMessage('当前无法核验外部子 Agent 的逐任务文件、Git 和网络权限;请将任务执行方设为 DSH。'); return
1031
1059
  }
1032
1060
  if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
1033
1061
  setMessage('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
@@ -1040,18 +1068,14 @@ window.__ModuleLoader__.load({
1040
1068
  || bytes(intent.trim()) > 1000 || bytes(taskPrompt.trim()) > 4000) {
1041
1069
  setMessage('任务描述或识别摘要过长,请缩短描述并重新识别。'); return
1042
1070
  }
1043
- const triggerLines = triggerPhrases.split('\n').map((item) => item.normalize('NFKC').trim()).filter(Boolean)
1044
- if (!triggerLines.length || triggerLines.length > 8 || triggerLines.some((item) => bytes(item) > 120)
1045
- || new Set(triggerLines.map((item) => item.toLowerCase())).size !== triggerLines.length) {
1046
- setMessage('没能从描述中识别出清楚的触发方式。请写明消息以什么开头时执行,再重新识别。'); return
1047
- }
1048
1071
  const normalized = parameters.map((rule) => ({ name: rule.name.trim(), kind: rule.kind,
1049
1072
  description: rule.description.trim() || null,
1050
1073
  required: rule.required, min: rule.min == null || rule.min === '' ? null : Number(rule.min),
1051
1074
  max: rule.max == null || rule.max === '' ? null : Number(rule.max),
1052
1075
  choices: rule.kind === 'choice' ? (Array.isArray(rule.choices) ? rule.choices
1053
1076
  : String(rule.choices ?? '').split(/[,,\n]/u)).map((item) => item.trim()).filter(Boolean) : null,
1054
- gitea_origin: rule.kind === 'gitea_pr' ? selectedEnrollment?.gitea_origin || null : null,
1077
+ gitea_origin: rule.kind === 'gitea_pr'
1078
+ ? (typeof rule.gitea_origin === 'string' ? normalizeServiceUrl(rule.gitea_origin) : null) : null,
1055
1079
  repository: rule.kind === 'gitea_pr' ? rule.repository.trim().toLowerCase() : null }))
1056
1080
  if (normalized.some((rule) => !/^[A-Za-z][A-Za-z0-9_]{0,31}$/u.test(rule.name))) {
1057
1081
  setMessage('没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
@@ -1059,6 +1083,9 @@ window.__ModuleLoader__.load({
1059
1083
  if (new Set(normalized.map((rule) => rule.name)).size !== normalized.length) {
1060
1084
  setMessage('请求信息识别有重复项。请修改任务描述并重新识别。'); return
1061
1085
  }
1086
+ if (normalized.some((rule) => rule.name === 'head_sha' && rule.kind !== 'git_sha')) {
1087
+ setMessage('head_sha 是 PR 提交版本的保留字段;请在自然语言描述中使用 PR Head SHA。'); return
1088
+ }
1062
1089
  if (normalized.some((rule) => rule.description && bytes(rule.description) > 100)) {
1063
1090
  setMessage('请求信息说明过长。请缩短任务描述并重新识别。'); return
1064
1091
  }
@@ -1078,6 +1105,16 @@ window.__ModuleLoader__.load({
1078
1105
  if (normalized.some((rule) => rule.kind === 'gitea_pr' && (!rule.gitea_origin || !rule.repository))) {
1079
1106
  setMessage('没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
1080
1107
  }
1108
+ if (new Set(normalized.filter((rule) => rule.kind === 'gitea_pr')
1109
+ .map((rule) => normalizeServiceUrl(rule.gitea_origin)).filter(Boolean)).size > 1) {
1110
+ setMessage('同一任务中的 PR 资源必须来自同一个 Gitea 主机。')
1111
+ return
1112
+ }
1113
+ if (normalized.some((rule) => rule.kind === 'gitea_pr'
1114
+ && selectedGiteaOrigin && normalizeServiceUrl(rule.gitea_origin) !== selectedGiteaOrigin)) {
1115
+ setMessage(`任务描述中的 Gitea 来源必须与此机器人的连接器主机一致(${selectedGiteaOrigin});令牌不会发送到其他主机。`)
1116
+ return
1117
+ }
1081
1118
  const validRepository = (value) => {
1082
1119
  const parts = value.split('/')
1083
1120
  return parts.length === 2 && parts.every((part) => part !== '.' && part !== '..'
@@ -1087,23 +1124,23 @@ window.__ModuleLoader__.load({
1087
1124
  setMessage('仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
1088
1125
  }
1089
1126
  if (!reviewers.length || !reviewers.includes(runner)) {
1090
- setMessage('请至少选择一位审查 Agent,并设置普通请求的默认执行方。'); return
1127
+ setMessage('请至少选择一位任务执行 Agent,并设置普通请求的默认执行方。'); return
1091
1128
  }
1092
- if (nativeReviewerSelected && (normalized.length !== 1
1093
- || normalized[0].kind !== 'gitea_pr' || !normalized[0].required)) {
1094
- setMessage('Codex 和 Claude Code 目前只支持审查一个 PR。请在任务描述中写明 PR 链接,再重新识别。'); return
1129
+ if (nativeReviewerSelected && !((normalized.length === 1
1130
+ && normalized[0].kind === 'gitea_pr' && normalized[0].required)
1131
+ || (normalized.length === 2 && normalized.some((rule) => rule.kind === 'gitea_pr' && rule.required)
1132
+ && normalized.some((rule) => rule.kind === 'git_sha' && rule.name === 'head_sha' && !rule.required)))) {
1133
+ setMessage('Codex 和 Claude Code 当前只支持一个必填 Gitea PR 资源连接器;任务行为仍由自然语言描述决定。'); return
1095
1134
  }
1096
1135
  const scope = conversationKind === 'group' ? `群 ${groupId}` : '私聊'
1097
1136
  const resultDestination = conversationKind === 'group' ? '原群(群内可见)' : '发起任务的私聊'
1098
1137
  const executionDetails = {
1099
- dsh: 'DSH 只根据已提供的 PR diff 审查,不读取完整仓库。',
1100
- codex: 'Codex 使用已核验 PR Head 的临时代码快照,并启用本机固定只读权限;不接收 Gitea 令牌。',
1101
- 'claude-code': 'Claude Code 使用同一份临时代码快照,沿用这台 DSH 的本机权限设置;请确认该权限适合由获准成员请求审查。',
1138
+ dsh: 'DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。',
1139
+ codex: 'Codex 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
1140
+ 'claude-code': 'Claude Code 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
1102
1141
  }
1103
1142
  const executionInfo = reviewers.length > 1
1104
- ? crossReviewAlways
1105
- ? `每次任务请求都会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回审查结果,无需请求人额外提出交叉 review。${reviewers.map((item) => executionDetails[item]).join(' ')}`
1106
- : `普通请求默认由 ${reviewerLabel(runner)} 执行;请求人明确说“交叉 review”时,会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回审查结果。${reviewers.map((item) => executionDetails[item]).join(' ')}`
1143
+ ? `普通请求默认由 ${reviewerLabel(runner)} 执行;请求人明确要求多 Agent 分别处理时,会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回结果。${reviewers.map((item) => executionDetails[item]).join(' ')}`
1107
1144
  : executionDetails[reviewers[0]]
1108
1145
  const actionDescription = editingCardId
1109
1146
  ? `更新“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求。旧待确认请求将失效,已提交任务继续使用提交时的配置。结果${resultDestination}。到期时间保持为 ${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。`
@@ -1115,10 +1152,9 @@ window.__ModuleLoader__.load({
1115
1152
  method: editingCardId ? 'PUT' : 'POST', headers: { 'content-type': 'application/json' },
1116
1153
  body: JSON.stringify({ ...(editingCardId ? { id: editingCardId } : {}),
1117
1154
  bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
1118
- title, rule_description: ruleDescription.trim(), trigger_phrases: triggerLines,
1119
- intent, examples: [],
1120
- parameters: normalized, task_prompt: taskPrompt, runner, reviewers,
1121
- cross_review_always: crossReviewAlways, workspace: null,
1155
+ title, rule_description: ruleDescription.trim(), trigger_phrases: [],
1156
+ intent, examples: ruleExamples,
1157
+ parameters: normalized, task_prompt: taskPrompt, runner, reviewers, workspace: null,
1122
1158
  sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
1123
1159
  ? senderIds.split(/[\s,,]+/u).filter(Boolean) : [], conversation_kind: conversationKind,
1124
1160
  ...(conversationKind === 'group' ? { conversation_id: groupId } : {}),
@@ -1126,7 +1162,7 @@ window.__ModuleLoader__.load({
1126
1162
  })
1127
1163
  const result = await readLocalJsonResponse(response)
1128
1164
  if (!response.ok && result.code === 'IM_TASK_RULE_REQUIRED') {
1129
- throw new Error('当前 ANT 插件版本不支持任务触发规则,请升级插件后再开放任务。')
1165
+ throw new Error('当前 ANT 服务端不支持自然语言任务 Harness,请升级 ANT 服务端后再开放任务。')
1130
1166
  }
1131
1167
  if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
1132
1168
  throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
@@ -1134,8 +1170,7 @@ window.__ModuleLoader__.load({
1134
1170
  if (!response.ok) throw new Error(result.message || '能力保存失败。')
1135
1171
  const savedReviewers = Array.isArray(result.reviewers) && result.reviewers.length
1136
1172
  ? result.reviewers : [result.runner]
1137
- if ([...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')
1138
- || (result.cross_review_always === true) !== crossReviewAlways) {
1173
+ if ([...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')) {
1139
1174
  if (!editingCardId && typeof result.id === 'string') {
1140
1175
  const cleanup = await fetch('/api/ant-teams/delegation/cards', {
1141
1176
  method: 'DELETE', headers: { 'content-type': 'application/json' },
@@ -1143,17 +1178,18 @@ window.__ModuleLoader__.load({
1143
1178
  }).catch(() => null)
1144
1179
  if (!cleanup?.ok) {
1145
1180
  await load().catch(() => undefined)
1146
- throw new Error('团队服务端尚不支持完整的多 Agent 审查策略;旧服务端可能已保存不匹配的授权,请刷新列表并检查,再升级 ANT 服务端。')
1181
+ throw new Error('团队服务端尚不支持多 Agent 审查配置;旧服务端可能已保存单 Agent 授权,请刷新列表并停止它,再升级 ANT 服务端。')
1147
1182
  }
1148
1183
  }
1149
- throw new Error('当前团队服务端尚不支持完整的多 Agent 审查策略;请管理员升级 ANT 服务端后重试。')
1184
+ throw new Error('当前团队服务端尚不支持多 Agent 审查配置;请管理员升级 ANT 服务端后重试。')
1150
1185
  }
1151
1186
  recognizedBaselineRef.current = null
1152
1187
  recognizedDescriptionRef.current = null
1153
- setRuleDescription(''); setTriggerPhrases('')
1188
+ autoAnalyzeDescriptionRef.current = null
1189
+ recognitionRequestRef.current += 1
1190
+ setRuleDescription(''); setMissingRequired([]); setSuggestions([]); setRuleExamples([])
1154
1191
  setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
1155
- setCrossReviewAlways(false)
1156
- setCrossReviewOpen(false)
1192
+ setParallelAgentsOpen(false)
1157
1193
  setEditingCardId(null); setEditingExpiresAt(null)
1158
1194
  await load()
1159
1195
  setCreateOpen(false)
@@ -1177,29 +1213,27 @@ window.__ModuleLoader__.load({
1177
1213
  }
1178
1214
  const copyCardToDraft = (card) => {
1179
1215
  setEditingCardId(null); setEditingExpiresAt(null)
1180
- setCrossReviewOpen(false)
1181
- const hasTriggerRule = Array.isArray(card.trigger_phrases) && card.trigger_phrases.length > 0
1182
- const copiedDescription = card.rule_description || card.intent || ''
1183
- recognizedDescriptionRef.current = hasTriggerRule ? copiedDescription.trim() || null : null
1184
- setDraftNotice(hasTriggerRule
1216
+ setParallelAgentsOpen(false)
1217
+ const copiedDescription = card.rule_description || ''
1218
+ recognizedDescriptionRef.current = copiedDescription.trim() || null
1219
+ autoAnalyzeDescriptionRef.current = null
1220
+ recognitionRequestRef.current += 1
1221
+ setDraftNotice(copiedDescription.trim()
1185
1222
  ? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
1186
- : '复制的旧任务没有明确触发方式。请修改描述并重新识别,再在支持触发规则的服务端开放;副本不保留原有效期。')
1223
+ : '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
1187
1224
  const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
1188
- const cardEnrollment = enrolled.find((bot) => bot.bot_fingerprint === card.bot_fingerprint)
1189
1225
  const titleChars = Array.from(`复制:${card.title || '未命名能力'}`)
1190
1226
  while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
1191
1227
  const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
1192
- recognizedBaselineRef.current = hasTriggerRule ? taskDraft : null
1228
+ recognizedBaselineRef.current = taskDraft
1193
1229
  setBotId(card.bot_id || cardBot?.bot_id || '')
1194
- setTrustedOrigin(cardEnrollment?.gitea_origin || '')
1195
1230
  setRuleDescription(copiedDescription)
1196
- setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
1231
+ setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
1197
1232
  setTitle(taskDraft.title)
1198
1233
  setIntent(taskDraft.intent)
1199
1234
  setTaskPrompt(taskDraft.task_prompt)
1200
1235
  setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
1201
1236
  setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh'])
1202
- setCrossReviewAlways(card.cross_review_always === true)
1203
1237
  setParameters(taskDraft.parameters)
1204
1238
  setConversationKind(card.conversation_kind === 'group' ? 'group' : 'dm')
1205
1239
  setGroupId(card.conversation_id || '')
@@ -1229,8 +1263,7 @@ window.__ModuleLoader__.load({
1229
1263
  loading ? '读取中…' : '刷新状态')),
1230
1264
  h('p', { role: 'status', style: mutedTextStyle }, collaborationStatus),
1231
1265
  bots.length ? h('label', null, '我的机器人', h('select', { id: 'ant-bot-selector', value: botId,
1232
- onChange: (event) => { setBotId(event.target.value); setGroupId('');
1233
- setTrustedOrigin(enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
1266
+ onChange: (event) => { setBotId(event.target.value); setGroupId('') }, style: inputStyle },
1234
1267
  h('option', { value: '' }, '请选择机器人'),
1235
1268
  ...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
1236
1269
  disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))) : null,
@@ -1242,23 +1275,6 @@ window.__ModuleLoader__.load({
1242
1275
  selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
1243
1276
  onClick: () => void enrollBot(), style: primaryButtonStyle },
1244
1277
  selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null)
1245
- const trustedOriginCard = showPrSecuritySettings ? h('section', { style: collaborationCardStyle },
1246
- h('strong', null, 'PR 读取来源(可选)'),
1247
- h('p', { style: mutedTextStyle }, '只有任务需要读取 Gitea PR 时才需设置。PR 链接、仓库和编号写在任务描述里;这里仅登记本机允许读取的 Gitea 来源。'),
1248
- h('label', null, '机器人', h('select', { 'aria-label': 'Gitea 来源对应机器人', value: botId,
1249
- onChange: (event) => { setBotId(event.target.value); setTrustedOrigin(
1250
- enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
1251
- h('option', { value: '' }, '请选择机器人'),
1252
- ...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
1253
- disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))),
1254
- selectedBot ? h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
1255
- h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
1256
- onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
1257
- style: { ...inputStyle, flex: '1 1 220px' } }),
1258
- h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
1259
- onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
1260
- selectedEnrollmentBound ? '保存可信来源' : botConnected ? '启用并保存来源' : '先连接机器人'))
1261
- : null) : null
1262
1278
  const groupTaskStatusPanel = selectedEnrollmentBound && botConnected ? h('section', {
1263
1279
  id: 'ant-group-task-status', role: 'region', 'aria-label': '各群聊天与任务状态', tabIndex: -1,
1264
1280
  style: { ...collaborationCardStyle, gap: 10 } },
@@ -1269,7 +1285,7 @@ window.__ModuleLoader__.load({
1269
1285
  loading ? '读取中…' : '刷新群列表')),
1270
1286
  h('p', { style: mutedTextStyle }, groupChatLoadFailed
1271
1287
  ? '群列表暂不可用,请刷新状态后重试。'
1272
- : availableGroups.length ? '每个群分别控制普通聊天和任务;群名称取自 DSH-IM 已保存目标,识别码可用于核对。'
1288
+ : availableGroups.length ? '每个群分别控制普通聊天和任务;名称取自 DSH-IM 已保存目标,括号内为识别码末两位。未匹配名称时显示完整识别码。'
1273
1289
  : '先在目标群 @机器人,再刷新状态发现群。'),
1274
1290
  availableGroups.length ? h('div', { id: 'ant-group-card-grid', style: collaborationGridStyle },
1275
1291
  ...availableGroups.map((group) => {
@@ -1277,16 +1293,13 @@ window.__ModuleLoader__.load({
1277
1293
  && card.conversation_kind === 'group' && card.conversation_id === group.group_id).length
1278
1294
  return h('article', { key: group.group_id, style: { ...collaborationCardStyle, padding: 10, gap: 8 } },
1279
1295
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
1280
- h('div', { style: { display: 'grid', gap: 2, minWidth: 0 } },
1281
- h('strong', { style: { overflowWrap: 'anywhere' } }, group.group_name || group.group_code),
1282
- group.group_name ? h('small', { style: mutedTextStyle, title: `完整识别码 ${group.group_code}` },
1283
- `识别码尾号 ${group.group_code.slice(-2)}`) : null),
1296
+ h('strong', { title: `完整识别码 ${group.group_code}` }, groupLabel(group)),
1284
1297
  h('span', { style: mutedTextStyle }, `已开放任务 ${taskCount} 项`)),
1285
1298
  h('label', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
1286
1299
  h('input', { type: 'checkbox', checked: group.chat_enabled,
1287
1300
  disabled: busy || groupChatLoadFailed,
1288
1301
  onChange: (event) => void saveGroupChat(event.target.checked, group.group_id),
1289
- 'aria-label': `允许群 ${group.group_name ? `${group.group_name},完整识别码 ${group.group_code}` : group.group_code} 普通聊天` }), '普通聊天'),
1302
+ 'aria-label': '允许群 ' + groupLabel(group) + ' 普通聊天' }), '普通聊天'),
1290
1303
  h('button', { type: 'button', disabled: busy, style: secondaryButtonStyle,
1291
1304
  onClick: () => { startNewCard({ conversationKind: 'group', groupId: group.group_id }); focusAfterTabChange('collaboration', 'ant-new-task-summary') } },
1292
1305
  '为此群开放任务'))
@@ -1305,9 +1318,6 @@ window.__ModuleLoader__.load({
1305
1318
  h('p', { style: mutedTextStyle }, '选择机器人,设置聊天入口,再按需要开放任务规则。聊天和任务授权分别控制。')),
1306
1319
  h('span', { style: { ...mutedTextStyle, padding: '4px 9px', borderRadius: 999,
1307
1320
  backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot ? selectedBot.name : '尚未选择机器人')),
1308
- legacyTriggerCardCount ? h('p', { role: 'status', style: { ...mutedTextStyle,
1309
- color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1310
- '当前有 ' + legacyTriggerCardCount + ' 项旧任务规则没有明确触发方式;新版插件不会按这些旧规则触发任务。升级 ANT 服务端后,再重新描述并开放需要的规则。') : null,
1311
1321
  loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
1312
1322
  `部分协作信息暂时无法读取:${loadFailures.map((path) => loadNames[path]).join('、')}。已读取的信息仍显示,请稍后刷新状态。`) : null,
1313
1323
  h('div', { id: 'ant-collaboration-settings-grid', style: collaborationSettingsGridStyle },
@@ -1335,9 +1345,11 @@ window.__ModuleLoader__.load({
1335
1345
  const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
1336
1346
  const expiry = remainingDays <= 7 ? `${remainingDays} 天后到期` : `至 ${new Date(expiresAt).toLocaleDateString('zh-CN')}`
1337
1347
  const conversation = card.conversation_kind === 'group'
1338
- ? `群 ${groupSummary(groups.find((group) => group.group_id === card.conversation_id)) || card.conversation_id}` : '私聊'
1348
+ ? `群 ${groupLabel(groups.find((group) => group.bot_fingerprint === card.bot_fingerprint
1349
+ && group.group_id === card.conversation_id)) || card.conversation_id}` : '私聊'
1339
1350
  const requester = card.sender_scope === 'allowlist' ? '指定成员可请求'
1340
1351
  : card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'
1352
+ const blockedExternalExecutor = card.runner !== 'dsh' || card.cross_review_always === true
1341
1353
  const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
1342
1354
  const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
1343
1355
  const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
@@ -1349,10 +1361,13 @@ window.__ModuleLoader__.load({
1349
1361
  h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
1350
1362
  backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
1351
1363
  h('span', { style: mutedTextStyle }, `${cardReviewerSummary(card)} · ${conversation} · ${requester} · 结果回复原${card.conversation_kind === 'group' ? '群' : '私聊'}`),
1364
+ blockedExternalExecutor ? h('p', { role: 'status', style: { ...mutedTextStyle,
1365
+ color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1366
+ '此任务暂不可执行:当前无法核验外部 Agent 的逐任务权限。点击“编辑”,改用 DSH 并移除外部 Agent 后可重新开放。') : null,
1352
1367
  card.rule_description
1353
1368
  ? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description)
1354
1369
  : h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1355
- '旧规则没有明确触发方式;新版插件不会按它启动任务。升级 ANT 服务端后需重新描述并开放。'),
1370
+ '旧任务卡没有自然语言描述。请编辑并补充描述,让 AI 生成 Harness 后再开放。'),
1356
1371
  h('details', null,
1357
1372
  h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看规则说明'),
1358
1373
  h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
@@ -1417,26 +1432,42 @@ window.__ModuleLoader__.load({
1417
1432
  selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
1418
1433
  '机器人及其任务能力只在这台 DSH 设备接收和执行;同一账号的其他设备不会继承本机授权。') : null,
1419
1434
  selectedEnrollment && !selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
1420
- '这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;Gitea 地址和配置记录会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
1435
+ '这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;原协作配置会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
1421
1436
  selectedBot && conversationKind === 'group' && !selectedBot.history_available ? h('p', { role: 'status', style: mutedTextStyle },
1422
1437
  '这个机器人当前不能读取平台群历史;群聊只能参考实际送达的消息和本机近期记录。请检查 DSH-IM 权限。') : null,
1438
+ h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))',
1439
+ gap: 12, alignItems: 'start' } },
1440
+ h('div', { style: { display: 'grid', gap: 9, minWidth: 0 } },
1423
1441
  h('label', null, '描述规则', h('textarea', { required: true, maxLength: 4000, rows: 4, value: ruleDescription,
1424
- onChange: (event) => { setRuleDescription(event.target.value); setRecognitionMessage('') },
1425
- placeholder: '用日常的话说明何时执行、要参考哪些资料和结果怎么写。例如:群里有人以“审查 PR”开头并贴出完整 PR 链接时,检查代码差异,列出可定位的问题,不修改代码。', style: inputStyle })),
1426
- h('p', { style: mutedTextStyle }, '描述只写任务何时执行和怎么处理;执行方由下方选项决定,未触发消息是否普通聊天由上方聊天开关决定。'),
1442
+ onChange: (event) => {
1443
+ autoAnalyzeDescriptionRef.current = event.target.value
1444
+ recognitionRequestRef.current += 1
1445
+ setRuleDescription(event.target.value)
1446
+ setRecognitionBusy(false)
1447
+ setRecognitionMessage(event.target.value.trim() ? '你暂停输入后,AI 会自动检查任务是否完整。' : '')
1448
+ },
1449
+ placeholder: '用日常语言说明要完成什么、可处理哪些资料和希望怎样返回。例如:审查来自 https://gitea.example.com/team/shop 的 PR,指出可定位的问题,不修改代码;请求人可指定 Head SHA,未指定时用最新 Head。', style: inputStyle })),
1450
+ h('p', { style: mutedTextStyle }, 'AI 会在输入停顿后分析必需范围、缺失信息和可选条件。执行方、请求人、群范围和有效期由下方通用授权控制。'),
1427
1451
  h('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
1428
1452
  h('button', { type: 'button', disabled: recognitionBusy || busy || !ruleDescription.trim(),
1429
- onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? '正在识别…' : ruleSummaryReady ? '重新识别' : '识别规则'),
1430
- h('span', { style: mutedTextStyle }, '每次点击调用一次团队模型并计入用量;只整理草稿,不会开放任务。改完描述后可重新识别。')),
1431
- recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null,
1432
- h('section', { role: 'region', 'aria-label': '识别结果摘要', style: { ...panelStyle, padding: 10, gap: 7 } },
1453
+ onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? 'AI 正在分析…' : ruleSummaryReady ? '立即重新分析' : '立即分析'),
1454
+ h('span', { style: mutedTextStyle }, '自动分析和手动分析都会调用当前团队模型;只生成建议和待复核草稿,不会开放任务。')),
1455
+ recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null),
1456
+ h('section', { role: 'region', 'aria-label': 'AI 任务助手', style: { ...panelStyle, padding: 10, gap: 7, minWidth: 0 } },
1433
1457
  h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
1434
- h('strong', null, ruleSummaryReady ? (title || '识别结果') : '识别结果预览'),
1435
- h('span', { style: mutedTextStyle }, ruleSummaryReady ? '草稿 · 尚未开放' : '填写描述后可识别')),
1436
- h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, ruleSummaryNarrative),
1458
+ h('strong', null, ruleSummaryPending ? 'AI 正在检查当前描述' : title || 'AI 任务助手'),
1459
+ h('span', { style: mutedTextStyle }, ruleSummaryPending ? '描述已更新' : ruleSummaryReady ? '待复核 · 尚未开放' : '填写描述后开始分析')),
1460
+ h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, ruleSummaryPending
1461
+ ? 'AI 正在根据当前描述更新任务理解。上一次分析结果暂不用于授权开放。' : ruleSummaryNarrative),
1462
+ ruleSummaryReady && missingHarnessInputs.length ? h('p', { role: 'status', style: { ...mutedTextStyle,
1463
+ color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1464
+ `还需要明确:${missingHarnessInputs.join(';')}`) : ruleSummaryReady
1465
+ ? h('p', { role: 'status', style: mutedTextStyle }, 'AI 暂未发现必需信息缺项;请确认任务来源和处理范围符合预期。') : null,
1466
+ ruleSummaryReady && suggestions.length ? h('p', { style: mutedTextStyle }, `可选补充:${suggestions.join(';')}`) : null,
1467
+ ruleSummaryReady && ruleExamples.length ? h('p', { style: mutedTextStyle }, `自然请求示例:${ruleExamples.join(';')}`) : null,
1437
1468
  h('p', { style: mutedTextStyle }, ruleSummaryReady
1438
- ? '草稿未开放。检查这段理解;有误就改上方描述并重新识别。'
1439
- : '填写描述后识别;识别只生成草稿,不会开放任务。')),
1469
+ ? '草稿未开放。检查任务理解、必需范围和建议;有误就改描述,AI 会重新分析。'
1470
+ : ruleSummaryPending ? '分析完成前不能开放任务。' : 'AI 会整理任务理解;草稿不会自动开放。'))),
1440
1471
  h('section', { style: { ...panelStyle, padding: 10, gap: 9, display: 'grid',
1441
1472
  gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 280px), 1fr))' } },
1442
1473
  h('strong', { style: { gridColumn: '1 / -1' } }, '通用授权'),
@@ -1445,42 +1476,31 @@ window.__ModuleLoader__.load({
1445
1476
  h('label', null, '执行方', h('select', { value: runner, onChange: (event) => selectRunner(event.target.value),
1446
1477
  style: inputStyle },
1447
1478
  h('option', { value: 'dsh', disabled: !dshAvailable && runner !== 'dsh' }, 'DSH · 当前团队模型'),
1448
- h('option', { value: 'codex', disabled: (!codexAvailable || !hasPrParameter) && runner !== 'codex' }, 'Codex · PR 快照只读'),
1449
- h('option', { value: 'claude-code', disabled: (!claudeCodeAvailable || !hasPrParameter) && runner !== 'claude-code' }, 'Claude Code · 本机权限')),
1479
+ h('option', { value: 'codex', disabled: runner !== 'codex' }, 'Codex · DSH 子 Agent'),
1480
+ h('option', { value: 'claude-code', disabled: runner !== 'claude-code' }, 'Claude Code · DSH 子 Agent')),
1450
1481
  h('span', { style: mutedTextStyle }, runner === 'codex'
1451
- ? 'Codex 使用本机登录,仅读取已核验的 PR 快照。'
1452
- : runner === 'claude-code' ? 'Claude Code 沿用这台 DSH 的权限设置。'
1453
- : 'DSH 使用获准团队模型处理会话中可见或已提供的内容。')),
1454
- h('details', { id: 'ant-cross-review-details', open: crossReviewOpen,
1455
- onToggle: (event) => setCrossReviewOpen(event.currentTarget.open),
1482
+ ? 'Codex 暂不可用于协作任务:ANT 无法核验它的逐任务文件、Git 和网络权限。'
1483
+ : runner === 'claude-code' ? 'Claude Code 暂不可用于协作任务:ANT 无法核验它的逐任务文件、Git 和网络权限。'
1484
+ : 'DSH 使用获准团队模型处理对话内容和 ANT 已核验后提供的资源。')),
1485
+ h('details', { id: 'ant-parallel-agents-details', open: parallelAgentsOpen,
1486
+ onToggle: (event) => setParallelAgentsOpen(event.currentTarget.open),
1456
1487
  style: { gridColumn: '1 / -1', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 10 } },
1457
- h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, crossReviewHeading),
1458
- h('p', { style: mutedTextStyle }, '多个 Agent 可按需要设置为每次并行,或由请求人逐次提出交叉审查。'),
1488
+ h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, parallelAgentsSummary
1489
+ ? `并行执行:${parallelAgentsSummary}` : '多 Agent 并行(可选)'),
1490
+ h('p', { style: mutedTextStyle }, '当前只开放 DSH。外部 Agent 的逐任务文件、Git 和网络权限无法由 ANT 核验;编辑旧任务时可取消其选择并改用 DSH。'),
1459
1491
  h('div', { style: { display: 'flex', gap: 14, flexWrap: 'wrap' } },
1460
- h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent DSH', checked: reviewers.includes('dsh'),
1492
+ h('label', null, h('input', { type: 'checkbox', 'aria-label': '任务 Agent DSH', checked: reviewers.includes('dsh'),
1461
1493
  disabled: !dshAvailable && !reviewers.includes('dsh'),
1462
1494
  onChange: (event) => changeReviewer('dsh', event.target.checked) }), ' DSH'),
1463
- h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Codex', checked: reviewers.includes('codex'),
1464
- disabled: (!codexAvailable || !hasPrParameter) && !reviewers.includes('codex'),
1495
+ h('label', null, h('input', { type: 'checkbox', 'aria-label': '任务 Agent Codex', checked: reviewers.includes('codex'),
1496
+ disabled: !reviewers.includes('codex'),
1465
1497
  onChange: (event) => changeReviewer('codex', event.target.checked) }), ' Codex'),
1466
- h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Claude Code', checked: reviewers.includes('claude-code'),
1467
- disabled: (!claudeCodeAvailable || !hasPrParameter) && !reviewers.includes('claude-code'),
1498
+ h('label', null, h('input', { type: 'checkbox', 'aria-label': '任务 Agent Claude Code', checked: reviewers.includes('claude-code'),
1499
+ disabled: !reviewers.includes('claude-code'),
1468
1500
  onChange: (event) => changeReviewer('claude-code', event.target.checked) }), ' Claude Code')),
1469
- reviewers.length > 1 && defaults?.cross_review_policy_version === 1
1470
- ? h('fieldset', { style: { margin: 0, padding: '8px 10px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
1471
- h('legend', { style: mutedTextStyle }, '运行策略'),
1472
- h('label', { style: { display: 'flex', gap: 8, alignItems: 'start', marginBottom: 6 } },
1473
- h('input', { type: 'radio', name: 'ant-cross-review-policy', checked: !crossReviewAlways,
1474
- onChange: () => setCrossReviewAlways(false) }),
1475
- h('span', null, `默认由 ${reviewerLabel(runner)} 执行;请求人可逐次要求交叉审查。`)),
1476
- h('label', { style: { display: 'flex', gap: 8, alignItems: 'start' } },
1477
- h('input', { type: 'radio', name: 'ant-cross-review-policy', checked: crossReviewAlways,
1478
- onChange: () => setCrossReviewAlways(true) }),
1479
- h('span', null, '每次任务都并行运行全部已选 Agent;请求人无需额外提出交叉审查。')))
1480
- : reviewers.length > 1 ? h('p', { style: mutedTextStyle }, '每次并行模式需升级 ANT 服务端;当前仍可由请求人逐次提出交叉审查。') : null,
1481
- !hasPrParameter ? h('p', { style: mutedTextStyle }, '从描述识别出 PR 任务后,才能使用 Codex 或 Claude Code。') : null),
1501
+ null),
1482
1502
  nativePrUnavailableReason ? h('p', { role: 'status', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1483
- `${nativePrUnavailableReason},已禁用 Codex/Claude Code 完整快照选项;可选择 DSH 差异审查。`) : null,
1503
+ `${nativePrUnavailableReason}。当前仍可使用 DSH 团队模型处理任务。`) : null,
1484
1504
  h('label', null, '使用场景', h('select', { value: conversationKind,
1485
1505
  onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
1486
1506
  h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
@@ -1489,8 +1509,8 @@ window.__ModuleLoader__.load({
1489
1509
  h('label', null, '群聊', h('select', { required: true, value: groupId,
1490
1510
  onChange: (event) => setGroupId(event.target.value), style: inputStyle },
1491
1511
  h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
1492
- ...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id, title: group.group_code },
1493
- groupSummary(group))))),
1512
+ ...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
1513
+ groupLabel(group))))),
1494
1514
  h('button', { type: 'button', disabled: busy,
1495
1515
  onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
1496
1516
  h('label', null, '谁可以请求', h('select', { value: senderScope,
@@ -1504,9 +1524,10 @@ window.__ModuleLoader__.load({
1504
1524
  `有效期:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}(编辑时保持不变)`)
1505
1525
  : h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
1506
1526
  h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
1507
- nativeReviewerSelected ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1508
- '本机 PR 审查需要 Gitea 只读权限 read:repository 和 read:user;ANT 不会把令牌交给执行方。') : null),
1509
- ),
1527
+ (runner === 'codex' || runner === 'claude-code') ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1528
+ 'Provider Profile 的本机权限由 DSH 管理;ANT 当前不会用 Codex 或 Claude Code 执行协作任务,也不会把 Gitea 凭据交给它们。') : null),
1529
+ !hasPrParameter ? h('p', { style: mutedTextStyle },
1530
+ 'Codex 和 Claude Code 即使已在 DSH 注册,目前也不用于任何协作任务。') : null),
1510
1531
  h('aside', { id: 'ant-authorization-review', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
1511
1532
  style: { ...settingsPanelStyle, padding: 14, gap: 10, alignSelf: 'stretch' } },
1512
1533
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
@@ -1516,17 +1537,28 @@ window.__ModuleLoader__.load({
1516
1537
  h('p', { style: mutedTextStyle }, `这项任务将在${conversationKind === 'group'
1517
1538
  ? `群“${selectedGroupSummary || '尚未选择'}”` : '私聊'}开放给${requesterSummary},由${executorSummary}执行;`
1518
1539
  + `有效期 ${editingCardId ? new Date(editingExpiresAt * 1000).toLocaleString('zh-CN') : `${days} 天`},${resultRouteSummary}。`),
1540
+ hasPrParameter && !selectedGiteaOrigin && taskPrOrigins.length === 1
1541
+ ? h('p', { role: 'note', style: mutedTextStyle },
1542
+ `首次确认开放后,这台机器人会绑定到 ${taskPrOrigins[0]};后续 PR 任务只允许使用此主机,令牌不会发往其他主机。`)
1543
+ : null,
1544
+ hasPrParameter && selectedGiteaOrigin
1545
+ ? h('p', { role: 'note', style: mutedTextStyle },
1546
+ `这台机器人已绑定 ${selectedGiteaOrigin};任务来源需与此主机一致,令牌不会发往其他主机。`)
1547
+ : null,
1519
1548
  cardBlockReason ? h('p', { role: 'status', style: { ...mutedTextStyle,
1520
1549
  color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, cardBlockReason) : null,
1521
1550
  h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
1522
1551
  style: { ...primaryButtonStyle, width: '100%' } }, editingCardId ? '保存能力修改' : '确认并开放能力'))))),
1523
1552
  h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
1524
- trustedOriginCard,
1525
1553
  showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
1526
1554
  h('strong', null, 'PR 只读凭据(本机)'),
1527
1555
  h('p', { style: mutedTextStyle }, tokenConfigured
1528
- ? '只读访问令牌已保存在这台 DSH 的安全凭据中;不会同步到 ANT 服务端或发送给执行器。'
1529
- : '读取 Gitea PR 需要只读访问令牌。令牌只保存在这台 DSH,不提交给 ANT 服务端或执行器。'),
1556
+ ? '令牌由 DSH 本机凭据服务保存。ANT Host 在读取已授权 PR 时会使用它;不会同步到 ANT 服务端或写入审查提示词。'
1557
+ : '读取 Gitea PR 需要只读访问令牌。保存后由 DSH 本机凭据服务管理,ANT Host 只在读取已授权 PR 时使用。'),
1558
+ h('p', { role: 'note', style: mutedTextStyle }, 'DSH 默认将凭据保存在 DSH Home 的私有文件中,环境变量也可作为回退来源。文件权限只隔离其他 OS 用户;同一用户权限下的 Agent 进程可能读取它。ANT 当前只允许无 DSH 工具的任务会话处理对话内容和 Host 核验后提供的资源。Codex 和 Claude Code 子 Agent 暂停用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。ANT Gitea 凭据只由 Host Connector 使用,不会放入 Agent 请求。'),
1559
+ h('p', { role: 'note', style: mutedTextStyle }, selectedGiteaOrigin
1560
+ ? `这枚只读令牌仅用于已绑定主机 ${selectedGiteaOrigin};任务来源来自自然语言描述,但必须匹配该主机。`
1561
+ : '首次确认并开放包含 PR 来源的任务时,会将该来源绑定到这台机器人;确认前 ANT 不会把只读令牌发送到任务描述中的地址。'),
1530
1562
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
1531
1563
  h('input', { 'aria-label': 'Gitea 只读访问令牌', type: 'password', autoComplete: 'new-password', value: giteaToken,
1532
1564
  onChange: (event) => setGiteaToken(event.target.value),
@@ -1536,7 +1568,7 @@ window.__ModuleLoader__.load({
1536
1568
  onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))) : null,
1537
1569
  h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
1538
1570
  h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '本机处理设置'),
1539
- h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务;PR 仓库缓存上限按这台 DSH 的长期仓库缓存总量计算,不包括任务临时工作目录。'),
1571
+ h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务。外部代码执行器已暂停,当前不创建 PR 仓库缓存;缓存上限仅保留用于旧缓存回收,暂不可调整。'),
1540
1572
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
1541
1573
  h('span', { style: mutedTextStyle }, loadFailures.includes('settings') ? '团队默认值暂未读取。'
1542
1574
  : `团队默认:先看 ${defaults?.context_messages ?? 10} 条前文、最多翻阅 ${defaults?.history_max_records ?? 100} 条平台记录、同时处理 ${defaults?.max_concurrency ?? 3} 项任务。`),
@@ -1550,26 +1582,28 @@ window.__ModuleLoader__.load({
1550
1582
  h('option', { value: '' }, '跟随团队默认'),
1551
1583
  ...Array.from({ length: 8 }, (_, index) => h('option', { key: index + 1, value: index + 1 }, `${index + 1} 项任务`)))),
1552
1584
  h('label', { style: { display: 'grid', gap: 5, flex: '0 1 200px', maxWidth: '100%' } },
1553
- '这台电脑 PR 仓库缓存上限(GiB)',
1554
- h('input', { 'aria-label': '这台电脑 PR 仓库缓存上限(GiB)', type: 'number', min: 1, max: 100, step: 1, required: true,
1585
+ '这台电脑 PR 仓库缓存上限(GiB,暂不可调整)',
1586
+ h('input', { 'aria-label': '这台电脑 PR 仓库缓存上限(GiB)', type: 'number', min: 1, max: 100, step: 1,
1587
+ disabled: true,
1555
1588
  value: repositoryCacheLimitGib,
1556
- onChange: (event) => setRepositoryCacheLimitGib(event.target.value),
1557
1589
  style: { ...inputStyle, width: 128, maxWidth: '100%', boxSizing: 'border-box' } })),
1558
1590
  h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置'),
1559
1591
  h('p', { style: { ...mutedTextStyle, flex: '1 1 100%', margin: 0 } },
1560
- '默认 10 GiB,范围 1–100 GiB,按长期仓库文件逻辑大小计算(不含临时工作区和文件系统分配开销)。日常不会淘汰仍启用能力的缓存;停用或到期能力会在刷新时回收。单次拉取若实际越界,为恢复预算会清理这项能力的仓库缓存(旧 PR 可重新下载)。')),
1592
+ '当前不拉取 PR 仓库快照;刷新授权状态时仍会清理已停用或到期能力的旧缓存。')),
1561
1593
  ),
1562
- h('section', { style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
1594
+ h('section', { 'aria-label': '执行方状态与权限边界', style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
1563
1595
  h('strong', null, '执行方状态与权限边界'),
1564
- h('p', { style: mutedTextStyle }, '这里显示本机可用情况;具体哪些执行方能处理某项任务,由“协作”页中的规则决定。'),
1596
+ h('p', { style: mutedTextStyle }, '这里显示 DSH 本机的执行器状态;Codex 或 Claude Code 已注册,不代表它们获准执行协作任务。'),
1565
1597
  loadFailures.includes('executors') ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法读取执行方状态,请刷新后重试。') : null,
1566
- ...executors.filter((item) => ['dsh', 'codex-readonly', 'claude-code'].includes(item.id)).map((item) => h('div', {
1598
+ ...executors.filter((item) => ['dsh', 'codex', 'claude-code'].includes(item.id)).map((item) => h('div', {
1567
1599
  key: item.id, style: { display: 'grid', gap: 3, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
1568
- h('strong', null, item.title + ' · ' + (item.available ? '可用' : item.reason || '未启用')),
1569
- h('span', { style: mutedTextStyle }, item.id === 'codex-readonly'
1570
- ? '读取固定到 PR Head 的快照,使用固定只读配置;Gitea 令牌不会交给 Codex。'
1600
+ h('strong', null, item.title + ' · ' + (item.id === 'dsh'
1601
+ ? item.available ? '可用' : item.reason || '未就绪'
1602
+ : item.available ? '已注册 · 协作任务暂不可用' : '协作任务暂不可用')),
1603
+ h('span', { style: mutedTextStyle }, item.id === 'codex'
1604
+ ? 'ANT 暂不启动 Codex 子 Agent,因为当前无法核验逐任务文件、Git 和网络权限。'
1571
1605
  : item.id === 'claude-code'
1572
- ? '读取相同 PR 快照,但沿用本机 DSH 权限设置,不具备 Codex 路径的固定只读隔离。'
1606
+ ? 'ANT 暂不启动 Claude Code 子 Agent,因为当前无法核验逐任务文件、Git 和网络权限。'
1573
1607
  : '处理授权任务提供的内容,不调用本机工具。'))))))
1574
1608
  }
1575
1609