@ai-wendao/ant 0.5.9 → 0.5.11

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,25 +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 [multiAgentMode, setMultiAgentMode] = useState(false)
537
- const [formError, setFormError] = useState(null)
538
- const triggerPhrasesEditedRef = useRef(false)
539
+ const [parallelAgentsOpen, setParallelAgentsOpen] = useState(false)
539
540
  const [giteaToken, setGiteaToken] = useState('')
540
541
  const [tokenConfigured, setTokenConfigured] = useState(false)
541
542
  const [senderScope, setSenderScope] = useState('any')
@@ -591,6 +592,9 @@ window.__ModuleLoader__.load({
591
592
  const selectedBot = bots.find((bot) => bot.bot_id === botId)
592
593
  const botConnected = Boolean(selectedBot?.available && selectedBot?.connected)
593
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))]
594
598
  const selectedEnrollmentBound = selectedEnrollment?.device_bound_to_current === true
595
599
  const dmChatSettingAvailable = typeof selectedEnrollment?.dm_chat_enabled === 'boolean'
596
600
  const dmChatEnabled = selectedEnrollment?.dm_chat_enabled !== false
@@ -606,9 +610,12 @@ window.__ModuleLoader__.load({
606
610
  ? '允许此机器人接收私聊;任务仍按已开放的规则执行。'
607
611
  : '新私聊消息不会交给 DSH;已提交任务仍会在原私聊回复。已开放能力不撤销,重新开启后未过期能力恢复触发。'
608
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
+ }
609
618
  const groupChatLoadFailed = loadFailures.includes('groups')
610
- const groupSummary = (group) => group?.group_name
611
- ? `${group.group_name} · ${group.group_code.slice(-2)}` : group?.group_code || ''
612
619
  const activeCards = cards.filter((card) => !card.revoked_at && card.expires_at * 1000 > Date.now())
613
620
  const connectedBots = bots.filter((bot) => bot.available && bot.connected)
614
621
  const authorizationLoadError = loadFailures.includes('cards')
@@ -616,8 +623,6 @@ window.__ModuleLoader__.load({
616
623
  const enrolledCount = enrolled.filter((item) => item.device_bound_to_current
617
624
  && connectedBots.some((bot) => bot.fingerprint === item.bot_fingerprint)).length
618
625
  const selectedTaskCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint).length
619
- const legacyTriggerCardCount = activeCards.filter((card) => card.bot_fingerprint === selectedBot?.fingerprint
620
- && (!Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length)).length
621
626
  const collaborationStatus = loading ? '正在读取机器人与任务状态…'
622
627
  : botLoadError ? '暂时无法读取机器人状态。请刷新后再试。'
623
628
  : connectedBots.length === 0 ? '尚未发现已连接的机器人。请先在 DSH-IM 中连接飞书机器人。'
@@ -638,47 +643,42 @@ window.__ModuleLoader__.load({
638
643
  const hasPrParameter = parameters.some((item) => item.kind === 'gitea_pr')
639
644
  const hasSelectedPrRule = activeCards.some((card) => card.bot_fingerprint === selectedBot?.fingerprint
640
645
  && Array.isArray(card.parameters) && card.parameters.some((rule) => rule.kind === 'gitea_pr'))
641
- const showPrSecuritySettings = Boolean(selectedBot && (hasPrParameter || hasSelectedPrRule))
642
- const selectedGroupSummary = groupSummary(availableGroups.find((group) => group.group_id === groupId)) || groupId
643
- 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)
644
650
  const claudeCodeAvailable = executors.some((item) => item.id === 'claude-code' && item.available)
645
651
  const dshAvailable = executors.some((item) => item.id === 'dsh' && item.available)
652
+ const nativePrUnavailableReason = executors.find((item) => item.reason)?.reason || null
646
653
  const reviewerLabel = (id) => ({ dsh: 'DSH', codex: 'Codex', 'claude-code': 'Claude Code' })[id] || id
647
- const executorReason = (id) => {
648
- if (loadFailures.includes('executors')) return '无法读取这台 DSH 的执行方状态,请刷新后重试。'
649
- const reasons = []
650
- if (['codex', 'claude-code'].includes(id) && !hasPrParameter) {
651
- reasons.push('请在任务描述中写明 Gitea PR 链接或编号并重新识别')
652
- }
653
- const executorId = id === 'codex' ? 'codex-readonly' : id
654
- const executor = executors.find((item) => item.id === executorId)
655
- if (!executor?.available) reasons.push(executor?.reason || `这台 DSH 尚未启用 ${reviewerLabel(id)}`)
656
- return reasons.join(';')
657
- }
658
654
  const cardReviewerSummary = (card) => {
659
655
  const selected = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
660
- return selected.length > 1
661
- ? card.cross_review_always === true
662
- ? `每次交叉审查:${selected.map(reviewerLabel).join('、')}`
663
- : `交叉可用:${selected.map(reviewerLabel).join('、')};默认 ${reviewerLabel(card.runner)}`
664
- : card.runner === 'codex' ? 'Codex PR 快照审查'
665
- : card.runner === 'claude-code' ? 'Claude Code PR 快照审查'
666
- : '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'
667
661
  }
668
662
  const requesterSummary = senderScope === 'allowlist'
669
663
  ? `仅指定成员:${senderIds.trim() || '尚未填写成员 ID'}`
670
664
  : conversationKind === 'group' ? '群内所有成员' : '任何能私聊此机器人的人'
671
665
  const resultRouteSummary = conversationKind === 'group' ? '回复到原群(群内可见)' : '回复到发起任务的私聊'
672
- const triggerPhraseEntries = triggerPhrases ? triggerPhrases.split('\n') : ['']
673
- const ruleTriggerSummary = triggerPhraseEntries.map((item) => item.trim()).filter(Boolean)
674
- const ruleSummaryReady = Boolean(title.trim() && intent.trim() && taskPrompt.trim())
675
- const ruleDescriptionNeedsRecognition = recognizedDescriptionRef.current !== null
676
- && recognizedDescriptionRef.current !== ruleDescription.trim()
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
+ ])]
677
675
  const parameterSummary = (rule) => {
678
676
  const parts = [rule.required === false ? '可选' : '必填']
679
677
  const choices = Array.isArray(rule.choices) ? rule.choices
680
678
  : typeof rule.choices === 'string' ? rule.choices.split(/[,,\n]/u).map((item) => item.trim()).filter(Boolean) : []
681
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')
682
682
  else if (rule.kind === 'choice' && choices.length) parts.push(`选项 ${choices.join('、')}`)
683
683
  else if (rule.kind === 'integer') {
684
684
  const minValue = rule.min == null || rule.min === '' ? null : Number(rule.min)
@@ -698,103 +698,56 @@ window.__ModuleLoader__.load({
698
698
  ? `请求时还需提供:${parameters.map(parameterSummary).join(';')}`
699
699
  : '无需额外参数,按请求中的内容处理'
700
700
  const ruleSummaryNarrative = ruleSummaryReady
701
- ? intent.trim() || title.trim()
701
+ ? `${intent.trim() || title.trim()}${taskPrompt.trim() ? `;${taskPrompt.trim()}` : ''};${recognizedInputsSummary}`
702
702
  : '识别后会用一段摘要呈现系统理解的任务。'
703
- const reviewModeSummary = multiAgentMode || reviewers.length > 1
704
- ? reviewers.length < 2 ? '交叉审查尚未配置完成,至少再选择一位可用 Agent'
705
- : crossReviewAlways
706
- ? `每次任务请求都并行运行 ${reviewers.map(reviewerLabel).join('、')}(会增加调用量)`
707
- : `普通请求由 ${reviewerLabel(runner)} 执行;请求人明确提出“交叉 review”时并行运行 ${reviewers.map(reviewerLabel).join('、')}`
708
- : `${reviewerLabel(runner)} 单 Agent 执行`
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('、')
709
706
  const cardBlockReason = enrollmentLoadError ? '暂时无法核对机器人是否已启用协作,请刷新状态后再试。'
710
707
  : !botConnected || !selectedEnrollmentBound ? '先在上方把机器人绑定到这台 DSH。'
711
- : loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 2
712
- ? '团队服务端尚未支持触发规则的持久化与执行前核验;请先升级 ANT 服务端。'
708
+ : loadFailures.includes('settings') || defaults?.task_rule_schema_version !== 3
709
+ ? '团队服务端尚未支持 AI 任务 Harness 与运行时授权核验;请先升级 ANT 服务端。'
713
710
  : !ruleDescription.trim() ? '请先用自然语言描述要做的任务。'
714
- : ruleDescriptionNeedsRecognition
715
- ? '描述已修改,请重新识别,让执行规则与描述保持一致。'
716
- : !title.trim() || !intent.trim() || !taskPrompt.trim() ? '先识别任务描述并检查摘要,再开放任务。'
717
- : !triggerPhrases.trim() ? '请填写至少一个触发短语。'
718
- : multiAgentMode && reviewers.length < 2 ? '交叉审查至少选择两位可用 Agent。'
719
- : crossReviewAlways && defaults?.cross_review_policy_version !== 1
720
- ? '当前团队服务端尚不支持“每次都交叉审查”,请先升级 ANT 服务端。'
711
+ : recognizedDescriptionRef.current !== ruleDescription.trim()
712
+ ? 'AI 正在根据最新描述检查任务 Harness;分析完成后再开放。'
713
+ : !title.trim() || !intent.trim() || !taskPrompt.trim() ? '等待 AI 生成任务理解和处理要求。'
714
+ : missingHarnessInputs.length ? `任务范围还有必需信息未明确:${missingHarnessInputs.join('、')}。请补充描述。`
721
715
  : conversationKind === 'group' && loadFailures.includes('groups') ? '暂时无法核对目标群,请刷新状态后再试。'
722
716
  : conversationKind === 'group' && !groupId ? '请在“请求范围”中选择目标群;若列表为空,让群成员先 @机器人,再刷新群列表。'
723
717
  : senderScope === 'allowlist' && !senderIds.trim() ? '请填写可请求成员的 IM ID。'
718
+ : reviewers.some((item) => item !== 'dsh')
719
+ ? '当前只允许 DSH 执行协作任务。请将默认执行方改为 DSH,并移除未获准的外部 Agent。'
724
720
  : parameters.some((rule) => rule.kind === 'gitea_pr') && !tokenConfigured ? '请先在“本机与任务”中保存本机 Gitea 只读令牌。'
725
- : parameters.some((rule) => rule.kind === 'gitea_pr') && !selectedEnrollment?.gitea_origin
726
- ? 'PR 来源尚未信任。请到“本机与任务”设置可信 Gitea 来源。'
727
- : parameters.some((rule) => rule.kind === 'gitea_pr')
728
- && normalizeServiceUrl(trustedOrigin) !== selectedEnrollment?.gitea_origin
729
- ? '请先在“本机与任务”保存可信 Gitea 来源;任务描述中的 PR 链接会使用这个来源。'
730
- : nativeReviewerSelected && (parameters.length !== 1 || parameters[0].kind !== 'gitea_pr')
731
- ? 'Codex 和 Claude Code 目前只支持单个 Gitea PR 审查任务。'
732
- : nativeReviewerSelected && !parameters[0].required
733
- ? '本机代码审查的 Gitea PR 编号必须设为必填。'
734
- : reviewers.includes('dsh') && !dshAvailable
735
- ? '这台 DSH 尚未准备好文字审查执行器。'
736
- : reviewers.includes('codex') && !codexAvailable
737
- ? '这台 DSH 尚未找到可用的 Codex CLI,请先安装或检查 DSH 的 PATH。'
738
- : reviewers.includes('claude-code') && !claudeCodeAvailable
739
- ? '这台 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 尚未准备好任务执行器。'
740
736
  : ''
741
737
  const loadNames = { bots: '机器人', 'enrolled-bots': '协作启用状态', groups: '群聊列表',
742
738
  cards: '任务授权', settings: '团队默认值', executors: '本机执行器',
743
739
  'local-settings': '本机设置', status: '最近任务' }
744
- const reportFormError = (field, text) => {
745
- setFormError({ field, text })
746
- if (typeof document !== 'undefined') setTimeout(() => {
747
- const target = document.getElementById(field)
748
- target?.scrollIntoView?.({ block: 'center', behavior: 'smooth' })
749
- target?.focus?.()
750
- }, 0)
751
- }
752
- const clearFormError = (field) => setFormError((current) => current?.field === field ? null : current)
753
- const setTriggerPhraseAt = (index, value) => {
754
- const next = [...triggerPhraseEntries]
755
- next[index] = value
756
- triggerPhrasesEditedRef.current = true
757
- setTriggerPhrases(next.join('\n'))
758
- clearFormError('ant-trigger-phrases')
759
- }
760
- const addTriggerPhrase = () => {
761
- triggerPhrasesEditedRef.current = true
762
- setTriggerPhrases([...triggerPhraseEntries, ''].join('\n'))
763
- clearFormError('ant-trigger-phrases')
764
- }
765
- const removeTriggerPhrase = (index) => {
766
- triggerPhrasesEditedRef.current = true
767
- setTriggerPhrases(triggerPhraseEntries.filter((_item, itemIndex) => itemIndex !== index).join('\n'))
768
- clearFormError('ant-trigger-phrases')
769
- }
770
740
  const changeReviewer = (id, enabled) => {
771
741
  const next = enabled ? [...reviewers, id] : reviewers.filter((item) => item !== id)
772
- const unique = [...new Set(next)]
773
- setReviewers(unique)
774
- if (unique.length && !unique.includes(runner)) setRunner(unique[0])
775
- if (multiAgentMode && unique.length < 2) {
776
- reportFormError('ant-reviewer-list', '交叉审查至少选择两位可用 Agent。')
777
- setCrossReviewAlways(false)
778
- } else clearFormError('ant-reviewer-list')
742
+ if (!next.length) { setMessage('至少保留一位任务执行 Agent。'); return }
743
+ setReviewers([...new Set(next)])
744
+ if (!next.includes(runner)) setRunner(next[0])
779
745
  }
780
746
  const selectRunner = (id) => {
781
747
  setRunner(id)
782
- setReviewers((current) => multiAgentMode
783
- ? current.includes(id) ? current : [...current, id]
748
+ setReviewers((current) => current.length > 1
749
+ ? [...new Set([...current, id])]
784
750
  : [id])
785
- clearFormError('ant-agent-mode')
786
- }
787
- const selectExecutionMode = (mode) => {
788
- if (mode === 'single') {
789
- setMultiAgentMode(false)
790
- setReviewers([runner])
791
- setCrossReviewAlways(false)
792
- } else {
793
- setMultiAgentMode(true)
794
- if (reviewers.length < 2) setCrossReviewAlways(false)
795
- }
796
- clearFormError('ant-agent-mode')
797
- clearFormError('ant-reviewer-list')
798
751
  }
799
752
  const startNewCard = (context = {}) => {
800
753
  setEditingCardId(null); setEditingExpiresAt(null)
@@ -802,53 +755,57 @@ window.__ModuleLoader__.load({
802
755
  setRecognitionMessage('')
803
756
  recognizedBaselineRef.current = null
804
757
  recognizedDescriptionRef.current = null
805
- triggerPhrasesEditedRef.current = false
806
- setFormError(null)
758
+ autoAnalyzeDescriptionRef.current = null
759
+ recognitionRequestRef.current += 1
807
760
  setRuleDescription('')
808
- setTriggerPhrases('')
761
+ setMissingRequired([]); setSuggestions([]); setRuleExamples([])
809
762
  setTitle(''); setIntent(''); setTaskPrompt(''); setRunner('dsh')
810
- setReviewers(['dsh']); setCrossReviewAlways(false); setMultiAgentMode(false)
763
+ setReviewers(['dsh']); setParallelAgentsOpen(false)
811
764
  setParameters([]); setConversationKind(context.conversationKind || 'dm')
812
765
  setGroupId(context.groupId || '')
813
766
  setSenderScope('any'); setSenderIds(''); setDays(7); setCreateOpen(true)
814
767
  if (context.botId) setBotId(context.botId)
815
768
  }
816
- const taskRuleDraftFromCard = (card, title) => ({
817
- title: title || '', intent: card.intent || '', task_prompt: card.task_prompt || '',
818
- trigger_phrases: Array.isArray(card.trigger_phrases) ? card.trigger_phrases.map((item) => item.trim()).filter(Boolean) : [],
819
- parameters: (card.parameters || []).map((rule) => ({
769
+ const taskRuleDraftFromCard = (card, title) => {
770
+ const rules = (card.parameters || []).map((rule) => ({
820
771
  name: rule.name, kind: rule.kind, required: rule.required,
821
772
  description: rule.description || '', min: rule.min == null ? '' : String(rule.min),
822
773
  max: rule.max == null ? '' : String(rule.max),
823
774
  choices: Array.isArray(rule.choices) ? rule.choices.join(', ') : String(rule.choices ?? ''),
824
775
  gitea_origin: rule.gitea_origin || '', repository: rule.repository || '',
825
- })),
826
- })
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
+ }
827
789
  const editCard = (card, copy = false) => {
828
790
  setBotId(card.bot_id)
829
791
  setRecognitionMessage('')
830
- setFormError(null)
831
- triggerPhrasesEditedRef.current = false
832
- setTrustedOrigin(enrolled.find((bot) => bot.bot_id === card.bot_id)?.gitea_origin || '')
792
+ autoAnalyzeDescriptionRef.current = null
793
+ recognitionRequestRef.current += 1
833
794
  setEditingCardId(copy ? null : card.id); setEditingExpiresAt(copy ? null : card.expires_at)
834
795
  const taskDraft = taskRuleDraftFromCard(card, copy ? `${card.title.slice(0, 94)}(副本)` : card.title)
835
796
  recognizedBaselineRef.current = taskDraft
836
- const existingDescription = card.rule_description || card.intent || ''
797
+ const existingDescription = card.rule_description || ''
837
798
  recognizedDescriptionRef.current = existingDescription.trim() || null
838
- const legacyTriggerRule = !Array.isArray(card.trigger_phrases) || !card.trigger_phrases.length
839
- setDraftNotice(legacyTriggerRule
840
- ? '这项旧任务没有明确触发方式;新版插件不会按它启动任务。请升级 ANT 服务端后重新描述、识别并开放规则。'
841
- : '')
799
+ setDraftNotice(existingDescription.trim() ? ''
800
+ : '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
842
801
  setRuleDescription(existingDescription)
843
- setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
802
+ setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
844
803
  setTitle(taskDraft.title)
845
804
  if (copy) setDays(7)
846
805
  setIntent(taskDraft.intent)
847
806
  setTaskPrompt(taskDraft.task_prompt); setRunner(card.runner)
848
- const selectedReviewers = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner]
849
- setReviewers(selectedReviewers)
850
- setMultiAgentMode(selectedReviewers.length > 1)
851
- setCrossReviewAlways(card.cross_review_always === true)
807
+ setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner])
808
+ setParallelAgentsOpen(false)
852
809
  setParameters(taskDraft.parameters)
853
810
  setConversationKind(card.conversation_kind); setGroupId(card.conversation_id || '')
854
811
  setSenderScope(card.sender_scope); setSenderIds((card.sender_ids || []).join(', '))
@@ -857,28 +814,35 @@ window.__ModuleLoader__.load({
857
814
  const applyRuleDraft = (draft) => {
858
815
  setTitle(draft.title)
859
816
  setIntent(draft.intent)
860
- if (!triggerPhrasesEditedRef.current) setTriggerPhrases(draft.trigger_phrases.join('\n'))
861
817
  setTaskPrompt(draft.task_prompt)
862
818
  setParameters(draft.parameters)
819
+ setMissingRequired(draft.missing_required || [])
820
+ setSuggestions(draft.suggestions || [])
821
+ setRuleExamples(draft.examples || [])
863
822
  }
864
- const identifyRule = async () => {
865
- if (!ruleDescription.trim()) {
823
+ const identifyRule = async (description = ruleDescription) => {
824
+ const requestedDescription = description.trim()
825
+ if (!requestedDescription) {
866
826
  setRecognitionMessage('先描述这项任务何时触发、要做什么以及涉及哪些资料。')
867
827
  return
868
828
  }
869
- setRecognitionBusy(true); setFormError(null); setRecognitionMessage('正在调用团队模型识别规则…')
829
+ if (recognitionTimerRef.current) clearTimeout(recognitionTimerRef.current)
830
+ const requestNumber = ++recognitionRequestRef.current
831
+ setRecognitionBusy(true); setMessage(''); setRecognitionMessage('AI 正在分析必需范围、可选条件和执行方式…')
870
832
  try {
871
833
  const response = await fetch('/api/ant-teams/delegation/recognize', {
872
834
  method: 'POST', headers: { 'content-type': 'application/json' },
873
- body: JSON.stringify({ description: ruleDescription }),
835
+ body: JSON.stringify({ description: requestedDescription }),
874
836
  })
875
837
  const result = await readLocalJsonResponse(response)
876
838
  if (!response.ok) throw new Error(result.message || '规则识别失败。')
877
839
  const candidate = {
878
840
  title: typeof result.title === 'string' ? result.title : '',
879
841
  intent: typeof result.intent === 'string' ? result.intent : '',
880
- trigger_phrases: Array.isArray(result.trigger_phrases) ? result.trigger_phrases : [],
881
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 : [],
882
846
  parameters: Array.isArray(result.parameters) ? result.parameters.map((rule) => ({
883
847
  name: typeof rule?.name === 'string' ? rule.name : '',
884
848
  kind: typeof rule?.kind === 'string' ? rule.kind : 'text',
@@ -892,21 +856,45 @@ window.__ModuleLoader__.load({
892
856
  repository: typeof rule?.repository === 'string' ? rule.repository : '',
893
857
  })) : [],
894
858
  }
895
- if (!candidate.title || !candidate.intent || !candidate.task_prompt || !candidate.trigger_phrases.length) {
896
- 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 返回的任务理解不完整。请补充描述后重试。')
897
876
  }
898
877
  const previous = recognizedBaselineRef.current
899
878
  applyRuleDraft(candidate)
900
879
  recognizedBaselineRef.current = candidate
901
- recognizedDescriptionRef.current = ruleDescription.trim()
902
- setRecognitionMessage(triggerPhrasesEditedRef.current
903
- ? '已更新任务草稿;保留了手动调整的触发短语,请核对它仍符合新描述。'
904
- : previous
905
- ? '已根据新描述更新草稿,请检查摘要和授权范围。'
906
- : '已识别为任务草稿。检查摘要和授权范围;此操作不会开放任务。')
907
- } catch (error) { setRecognitionMessage(error.message) }
908
- finally { setRecognitionBusy(false) }
880
+ recognizedDescriptionRef.current = requestedDescription
881
+ setRecognitionMessage(previous
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
+ }
909
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])
910
898
  const saveGiteaToken = async () => {
911
899
  if (!giteaToken.trim()) return
912
900
  setBusy(true)
@@ -920,12 +908,7 @@ window.__ModuleLoader__.load({
920
908
  }
921
909
  const enrollBot = async () => {
922
910
  if (!selectedBot) return
923
- const requestedOrigin = trustedOrigin
924
- const origin = requestedOrigin?.trim() ? normalizeServiceUrl(requestedOrigin) : ''
925
- if (requestedOrigin?.trim() && !origin) {
926
- setMessage('请填写有效的 HTTPS Gitea 地址后再启用协作。')
927
- return
928
- }
911
+ const origin = selectedEnrollment?.gitea_origin || ''
929
912
  setBusy(true)
930
913
  try {
931
914
  const response = await fetch('/api/ant-teams/delegation/enrolled-bots', {
@@ -936,29 +919,10 @@ window.__ModuleLoader__.load({
936
919
  const result = await readLocalJsonResponse(response)
937
920
  if (!response.ok) throw new Error(result.message || '协作机器人启用失败。')
938
921
  await load()
939
- if (origin) setTrustedOrigin(origin)
940
922
  setMessage('协作机器人已启用。你可以分别设置私聊和群聊,再开放需要的能力。')
941
923
  } catch (error) { setMessage(error.message) }
942
924
  finally { setBusy(false) }
943
925
  }
944
- const saveTrustedOrigin = async () => {
945
- if (!selectedBot || !selectedEnrollmentBound) return
946
- const origin = normalizeServiceUrl(trustedOrigin)
947
- if (!origin) { setMessage('请填写有效的 HTTPS Gitea 地址。'); return }
948
- if (!window.confirm(`将这个机器人的可信 Gitea 地址设为 ${origin}?已开放能力中的 PR 参数需要重新核对。`)) return
949
- setBusy(true)
950
- try {
951
- const response = await fetch('/api/ant-teams/delegation/enrolled-bots', {
952
- method: 'POST', headers: { 'content-type': 'application/json' },
953
- body: JSON.stringify({ bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
954
- gitea_origin: origin }),
955
- })
956
- const result = await readLocalJsonResponse(response)
957
- if (!response.ok) throw new Error(result.message || '可信地址保存失败。')
958
- await load(); setTrustedOrigin(origin); setMessage('可信 Gitea 地址已保存。')
959
- } catch (error) { setMessage(error.message) }
960
- finally { setBusy(false) }
961
- }
962
926
  const saveGroupChat = async (enabled, targetGroupId) => {
963
927
  if (!selectedBot || !selectedEnrollmentBound || !targetGroupId.trim()) return
964
928
  const targetHasTasks = activeCards.some((card) => card.bot_fingerprint === selectedBot.fingerprint
@@ -1020,7 +984,7 @@ window.__ModuleLoader__.load({
1020
984
  if (!response.ok) throw new Error(result.message || '本机处理设置保存失败。')
1021
985
  setLocalConcurrency(result.max_concurrency)
1022
986
  setRepositoryCacheLimitGib(result.repository_cache_limit_gib)
1023
- setMessage('本机并发与仓库缓存设置已保存。')
987
+ setMessage('本机并发设置已保存;仓库缓存上限保留原值。')
1024
988
  } catch (error) { setMessage(error.message) }
1025
989
  finally { setBusy(false) }
1026
990
  }
@@ -1044,7 +1008,7 @@ window.__ModuleLoader__.load({
1044
1008
  return focusAfterTabChange('collaboration', 'ant-new-task-summary')
1045
1009
  }
1046
1010
  setCreateOpen(true)
1047
- focusAfterTabChange('collaboration', 'ant-open-confirm')
1011
+ focusAfterTabChange('collaboration', 'ant-authorization-review')
1048
1012
  }
1049
1013
  const collaborationGridStyle = {
1050
1014
  display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 12,
@@ -1089,28 +1053,20 @@ window.__ModuleLoader__.load({
1089
1053
  const createCard = async (event) => {
1090
1054
  event.preventDefault()
1091
1055
  setDraftNotice('')
1092
- setFormError(null)
1093
- if (!selectedBot || !botConnected || !selectedEnrollmentBound) {
1094
- reportFormError('ant-bot-selector', '请先在这台 DSH 绑定选中的协作机器人。'); return
1095
- }
1096
- if (crossReviewAlways && defaults?.cross_review_policy_version !== 1) {
1097
- reportFormError('ant-open-confirm', '当前团队服务端尚不支持“每次都交叉审查”,请先升级 ANT 服务端。'); return
1056
+ if (!selectedBot || !botConnected || !selectedEnrollmentBound) { setMessage('请先在这台 DSH 绑定选中的协作机器人。'); return }
1057
+ if (reviewers.some((item) => item !== 'dsh')) {
1058
+ setMessage('当前无法核验外部子 Agent 的逐任务文件、Git 和网络权限;请将任务执行方设为 DSH。'); return
1098
1059
  }
1099
1060
  if (editingCardId && (!editingExpiresAt || editingExpiresAt <= Math.floor(Date.now() / 1000))) {
1100
- reportFormError('ant-open-confirm', '这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
1061
+ setMessage('这项能力已到期,无法继续编辑;请返回后重新开放一项能力。'); return
1101
1062
  }
1102
1063
  if (!ruleDescription.trim() || !title.trim() || !intent.trim() || !taskPrompt.trim()) {
1103
- reportFormError('ant-rule-description', '请填写任务描述并点击“识别规则”,检查摘要后再开放。'); return
1064
+ setMessage('请填写任务描述并点击“识别规则”,检查摘要后再开放。'); return
1104
1065
  }
1105
1066
  const bytes = (value) => new TextEncoder().encode(value).length
1106
1067
  if (bytes(ruleDescription.trim()) > 4000 || bytes(title.trim()) > 100
1107
1068
  || bytes(intent.trim()) > 1000 || bytes(taskPrompt.trim()) > 4000) {
1108
- reportFormError('ant-rule-description', '任务描述或识别摘要过长,请缩短描述并重新识别。'); return
1109
- }
1110
- const triggerLines = triggerPhrases.split('\n').map((item) => item.normalize('NFKC').trim()).filter(Boolean)
1111
- if (!triggerLines.length || triggerLines.length > 8 || triggerLines.some((item) => bytes(item) > 120)
1112
- || new Set(triggerLines.map((item) => item.toLowerCase())).size !== triggerLines.length) {
1113
- reportFormError('ant-trigger-phrases', '触发短语需填写 1–8 条,每条不超过 120 字节且不能重复。'); return
1069
+ setMessage('任务描述或识别摘要过长,请缩短描述并重新识别。'); return
1114
1070
  }
1115
1071
  const normalized = parameters.map((rule) => ({ name: rule.name.trim(), kind: rule.kind,
1116
1072
  description: rule.description.trim() || null,
@@ -1118,21 +1074,25 @@ window.__ModuleLoader__.load({
1118
1074
  max: rule.max == null || rule.max === '' ? null : Number(rule.max),
1119
1075
  choices: rule.kind === 'choice' ? (Array.isArray(rule.choices) ? rule.choices
1120
1076
  : String(rule.choices ?? '').split(/[,,\n]/u)).map((item) => item.trim()).filter(Boolean) : null,
1121
- 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,
1122
1079
  repository: rule.kind === 'gitea_pr' ? rule.repository.trim().toLowerCase() : null }))
1123
1080
  if (normalized.some((rule) => !/^[A-Za-z][A-Za-z0-9_]{0,31}$/u.test(rule.name))) {
1124
- reportFormError('ant-rule-description', '没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
1081
+ setMessage('没能从描述中整理出有效的请求信息。请说明同事需要提供什么,再重新识别。'); return
1125
1082
  }
1126
1083
  if (new Set(normalized.map((rule) => rule.name)).size !== normalized.length) {
1127
- reportFormError('ant-rule-description', '请求信息识别有重复项。请修改任务描述并重新识别。'); return
1084
+ setMessage('请求信息识别有重复项。请修改任务描述并重新识别。'); return
1085
+ }
1086
+ if (normalized.some((rule) => rule.name === 'head_sha' && rule.kind !== 'git_sha')) {
1087
+ setMessage('head_sha 是 PR 提交版本的保留字段;请在自然语言描述中使用 PR Head SHA。'); return
1128
1088
  }
1129
1089
  if (normalized.some((rule) => rule.description && bytes(rule.description) > 100)) {
1130
- reportFormError('ant-rule-description', '请求信息说明过长。请缩短任务描述并重新识别。'); return
1090
+ setMessage('请求信息说明过长。请缩短任务描述并重新识别。'); return
1131
1091
  }
1132
1092
  if (normalized.some((rule) => rule.kind === 'choice' && (!rule.choices?.length || rule.choices.length > 32
1133
1093
  || rule.choices.some((choice) => bytes(choice) > 100)
1134
1094
  || new Set(rule.choices).size !== rule.choices.length))) {
1135
- reportFormError('ant-rule-description', '描述中的可选范围不清楚或重复。请修改描述并重新识别。'); return
1095
+ setMessage('描述中的可选范围不清楚或重复。请修改描述并重新识别。'); return
1136
1096
  }
1137
1097
  if (normalized.some((rule) => (rule.min != null && !Number.isInteger(rule.min))
1138
1098
  || (rule.max != null && !Number.isInteger(rule.max))
@@ -1140,10 +1100,20 @@ window.__ModuleLoader__.load({
1140
1100
  || (rule.kind === 'text' && rule.max != null && (rule.max < 1 || rule.max > 4096))
1141
1101
  || (rule.kind === 'gitea_pr' && ((rule.min != null && rule.min < 1)
1142
1102
  || (rule.max != null && rule.max > 1_000_000_000))))) {
1143
- reportFormError('ant-rule-description', '描述中的请求范围无效。请明确范围或提供完整 PR 链接,再重新识别。'); return
1103
+ setMessage('描述中的请求范围无效。请明确范围或提供完整 PR 链接,再重新识别。'); return
1144
1104
  }
1145
1105
  if (normalized.some((rule) => rule.kind === 'gitea_pr' && (!rule.gitea_origin || !rule.repository))) {
1146
- reportFormError('ant-rule-description', '没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
1106
+ setMessage('没能从描述中识别 PR 来源或仓库。请补充完整 PR 链接或仓库名,再重新识别。'); return
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
1147
1117
  }
1148
1118
  const validRepository = (value) => {
1149
1119
  const parts = value.split('/')
@@ -1151,30 +1121,30 @@ window.__ModuleLoader__.load({
1151
1121
  && /^[A-Za-z0-9_.-]{1,100}$/u.test(part))
1152
1122
  }
1153
1123
  if (normalized.some((rule) => rule.kind === 'gitea_pr' && !validRepository(rule.repository))) {
1154
- reportFormError('ant-rule-description', '仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
1124
+ setMessage('仓库名无法识别。请在描述中写明“团队名/仓库名”或粘贴完整 PR 链接,再重新识别。'); return
1155
1125
  }
1156
- if (!reviewers.length || !reviewers.includes(runner)
1157
- || (multiAgentMode && reviewers.length < 2)
1158
- || (!multiAgentMode && reviewers.length !== 1)) {
1159
- reportFormError('ant-agent-mode', multiAgentMode
1160
- ? '交叉审查至少选择两位可用 Agent。' : '单 Agent 模式请选择一位执行 Agent。'); return
1126
+ if (!reviewers.length || !reviewers.includes(runner)) {
1127
+ setMessage('请至少选择一位任务执行 Agent,并设置普通请求的默认执行方。'); return
1161
1128
  }
1162
- if (nativeReviewerSelected && (normalized.length !== 1
1163
- || normalized[0].kind !== 'gitea_pr' || !normalized[0].required)) {
1164
- reportFormError('ant-rule-description', '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
1165
1134
  }
1166
1135
  const scope = conversationKind === 'group' ? `群 ${groupId}` : '私聊'
1167
1136
  const resultDestination = conversationKind === 'group' ? '原群(群内可见)' : '发起任务的私聊'
1168
1137
  const executionDetails = {
1169
- dsh: 'DSH 只根据已提供的 PR diff 审查,不读取完整仓库。',
1170
- codex: 'Codex 使用已核验 PR Head 的临时代码快照,并启用本机固定只读权限;不接收 Gitea 令牌。',
1171
- 'claude-code': 'Claude Code 使用同一份临时代码快照,沿用这台 DSH 的本机权限设置;请确认该权限适合由获准成员请求审查。',
1138
+ dsh: 'DSH 使用获准的团队模型处理对话内容和 ANT 已核验后提供的资源。',
1139
+ codex: 'Codex 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
1140
+ 'claude-code': 'Claude Code 当前不能用于协作任务,因为 ANT 无法核验其逐任务文件、Git 和网络权限。',
1172
1141
  }
1173
- const executionInfo = `${reviewModeSummary}。${reviewers.map((item) => executionDetails[item]).join(' ')}`
1174
- const triggerSummary = `消息以“${triggerLines.join('”或“')}”开头时触发。`
1142
+ const executionInfo = reviewers.length > 1
1143
+ ? `普通请求默认由 ${reviewerLabel(runner)} 执行;请求人明确要求多 Agent 分别处理时,会并行启动 ${reviewers.map(reviewerLabel).join('、')},分别返回结果。${reviewers.map((item) => executionDetails[item]).join(' ')}`
1144
+ : executionDetails[reviewers[0]]
1175
1145
  const actionDescription = editingCardId
1176
- ? `更新“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求。${triggerSummary}旧待确认请求将失效,已提交任务继续使用提交时的配置。结果${resultDestination}。到期时间保持为 ${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。`
1177
- : `开放“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求。${triggerSummary}有效期 ${days} 天;结果${resultDestination}。`
1146
+ ? `更新“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求。旧待确认请求将失效,已提交任务继续使用提交时的配置。结果${resultDestination}。到期时间保持为 ${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}。`
1147
+ : `开放“${title}”后,${requesterSummary}可在${scope}按任务描述中的规则发起请求,有效期 ${days} 天;结果${resultDestination}。`
1178
1148
  if (!window.confirm(`${actionDescription}${executionInfo}请确认。`)) return
1179
1149
  setBusy(true)
1180
1150
  try {
@@ -1182,10 +1152,9 @@ window.__ModuleLoader__.load({
1182
1152
  method: editingCardId ? 'PUT' : 'POST', headers: { 'content-type': 'application/json' },
1183
1153
  body: JSON.stringify({ ...(editingCardId ? { id: editingCardId } : {}),
1184
1154
  bot_id: selectedBot.bot_id, bot_fingerprint: selectedBot.fingerprint,
1185
- title, rule_description: ruleDescription.trim(), trigger_phrases: triggerLines,
1186
- intent, examples: [],
1187
- parameters: normalized, task_prompt: taskPrompt, runner, reviewers,
1188
- 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,
1189
1158
  sender_scope: senderScope, sender_ids: senderScope === 'allowlist'
1190
1159
  ? senderIds.split(/[\s,,]+/u).filter(Boolean) : [], conversation_kind: conversationKind,
1191
1160
  ...(conversationKind === 'group' ? { conversation_id: groupId } : {}),
@@ -1193,7 +1162,7 @@ window.__ModuleLoader__.load({
1193
1162
  })
1194
1163
  const result = await readLocalJsonResponse(response)
1195
1164
  if (!response.ok && result.code === 'IM_TASK_RULE_REQUIRED') {
1196
- throw new Error('当前 ANT 插件版本不支持任务触发规则,请升级插件后再开放任务。')
1165
+ throw new Error('当前 ANT 服务端不支持自然语言任务 Harness,请升级 ANT 服务端后再开放任务。')
1197
1166
  }
1198
1167
  if (!response.ok && reviewers.includes('claude-code') && result.code === 'INVALID_REQUEST') {
1199
1168
  throw new Error('当前团队服务端可能尚不支持 Claude Code 审查配置。请管理员升级服务端后刷新重试。')
@@ -1201,8 +1170,7 @@ window.__ModuleLoader__.load({
1201
1170
  if (!response.ok) throw new Error(result.message || '能力保存失败。')
1202
1171
  const savedReviewers = Array.isArray(result.reviewers) && result.reviewers.length
1203
1172
  ? result.reviewers : [result.runner]
1204
- if ([...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')
1205
- || (result.cross_review_always === true) !== crossReviewAlways) {
1173
+ if ([...savedReviewers].sort().join('\0') !== [...reviewers].sort().join('\0')) {
1206
1174
  if (!editingCardId && typeof result.id === 'string') {
1207
1175
  const cleanup = await fetch('/api/ant-teams/delegation/cards', {
1208
1176
  method: 'DELETE', headers: { 'content-type': 'application/json' },
@@ -1210,25 +1178,25 @@ window.__ModuleLoader__.load({
1210
1178
  }).catch(() => null)
1211
1179
  if (!cleanup?.ok) {
1212
1180
  await load().catch(() => undefined)
1213
- throw new Error('团队服务端尚不支持完整的多 Agent 审查策略;旧服务端可能已保存不匹配的授权,请刷新列表并检查,再升级 ANT 服务端。')
1181
+ throw new Error('团队服务端尚不支持多 Agent 审查配置;旧服务端可能已保存单 Agent 授权,请刷新列表并停止它,再升级 ANT 服务端。')
1214
1182
  }
1215
1183
  }
1216
- throw new Error('当前团队服务端尚不支持完整的多 Agent 审查策略;请管理员升级 ANT 服务端后重试。')
1184
+ throw new Error('当前团队服务端尚不支持多 Agent 审查配置;请管理员升级 ANT 服务端后重试。')
1217
1185
  }
1218
1186
  recognizedBaselineRef.current = null
1219
1187
  recognizedDescriptionRef.current = null
1220
- triggerPhrasesEditedRef.current = false
1221
- setRuleDescription(''); setTriggerPhrases('')
1188
+ autoAnalyzeDescriptionRef.current = null
1189
+ recognitionRequestRef.current += 1
1190
+ setRuleDescription(''); setMissingRequired([]); setSuggestions([]); setRuleExamples([])
1222
1191
  setTitle(''); setIntent(''); setTaskPrompt(''); setParameters([]); setRunner('dsh'); setReviewers(['dsh'])
1223
- setCrossReviewAlways(false)
1224
- setMultiAgentMode(false)
1192
+ setParallelAgentsOpen(false)
1225
1193
  setEditingCardId(null); setEditingExpiresAt(null)
1226
1194
  await load()
1227
1195
  setCreateOpen(false)
1228
1196
  setMessage(editingCardId
1229
1197
  ? '能力配置已更新。新请求将使用新配置,已提交任务保留原配置。'
1230
1198
  : '任务已开放。机器人会按已确认的描述判断请求;缺少必要信息时会向请求人追问。')
1231
- } catch (error) { reportFormError('ant-open-confirm', error.message) }
1199
+ } catch (error) { setMessage(error.message) }
1232
1200
  finally { setBusy(false) }
1233
1201
  }
1234
1202
  const revokeCard = async (card) => {
@@ -1245,31 +1213,27 @@ window.__ModuleLoader__.load({
1245
1213
  }
1246
1214
  const copyCardToDraft = (card) => {
1247
1215
  setEditingCardId(null); setEditingExpiresAt(null)
1248
- setFormError(null); triggerPhrasesEditedRef.current = false
1249
- const hasTriggerRule = Array.isArray(card.trigger_phrases) && card.trigger_phrases.length > 0
1250
- const copiedDescription = card.rule_description || card.intent || ''
1251
- recognizedDescriptionRef.current = hasTriggerRule ? copiedDescription.trim() || null : null
1252
- 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()
1253
1222
  ? '已复制到新草稿。复制不保留原有效期;请检查任务描述和请求范围。'
1254
- : '复制的旧任务没有明确触发方式。请修改描述并重新识别,再在支持触发规则的服务端开放;副本不保留原有效期。')
1223
+ : '这项旧任务没有自然语言描述。请补充任务描述,让 AI 生成 Harness 后再开放。')
1255
1224
  const cardBot = bots.find((bot) => bot.fingerprint === card.bot_fingerprint)
1256
- const cardEnrollment = enrolled.find((bot) => bot.bot_fingerprint === card.bot_fingerprint)
1257
1225
  const titleChars = Array.from(`复制:${card.title || '未命名能力'}`)
1258
1226
  while (new TextEncoder().encode(titleChars.join('')).length > 100) titleChars.pop()
1259
1227
  const taskDraft = taskRuleDraftFromCard(card, titleChars.join(''))
1260
- recognizedBaselineRef.current = hasTriggerRule ? taskDraft : null
1228
+ recognizedBaselineRef.current = taskDraft
1261
1229
  setBotId(card.bot_id || cardBot?.bot_id || '')
1262
- setTrustedOrigin(cardEnrollment?.gitea_origin || '')
1263
1230
  setRuleDescription(copiedDescription)
1264
- setTriggerPhrases(taskDraft.trigger_phrases.join('\n'))
1231
+ setMissingRequired([]); setSuggestions(taskDraft.suggestions); setRuleExamples(taskDraft.examples)
1265
1232
  setTitle(taskDraft.title)
1266
1233
  setIntent(taskDraft.intent)
1267
1234
  setTaskPrompt(taskDraft.task_prompt)
1268
1235
  setRunner(['dsh', 'codex', 'claude-code'].includes(card.runner) ? card.runner : 'dsh')
1269
- const copiedReviewers = Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh']
1270
- setReviewers(copiedReviewers)
1271
- setMultiAgentMode(copiedReviewers.length > 1)
1272
- setCrossReviewAlways(card.cross_review_always === true)
1236
+ setReviewers(Array.isArray(card.reviewers) && card.reviewers.length ? card.reviewers : [card.runner || 'dsh'])
1273
1237
  setParameters(taskDraft.parameters)
1274
1238
  setConversationKind(card.conversation_kind === 'group' ? 'group' : 'dm')
1275
1239
  setGroupId(card.conversation_id || '')
@@ -1299,8 +1263,7 @@ window.__ModuleLoader__.load({
1299
1263
  loading ? '读取中…' : '刷新状态')),
1300
1264
  h('p', { role: 'status', style: mutedTextStyle }, collaborationStatus),
1301
1265
  bots.length ? h('label', null, '我的机器人', h('select', { id: 'ant-bot-selector', value: botId,
1302
- onChange: (event) => { setBotId(event.target.value); setGroupId('');
1303
- setTrustedOrigin(enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
1266
+ onChange: (event) => { setBotId(event.target.value); setGroupId('') }, style: inputStyle },
1304
1267
  h('option', { value: '' }, '请选择机器人'),
1305
1268
  ...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
1306
1269
  disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))) : null,
@@ -1312,23 +1275,6 @@ window.__ModuleLoader__.load({
1312
1275
  selectedBot && !selectedEnrollmentBound ? h('button', { type: 'button', disabled: busy || !botConnected || enrollmentLoadError,
1313
1276
  onClick: () => void enrollBot(), style: primaryButtonStyle },
1314
1277
  selectedEnrollment ? '绑定原协作配置到这台 DSH' : '启用并绑定到这台 DSH') : null)
1315
- const trustedOriginCard = showPrSecuritySettings ? h('section', { style: collaborationCardStyle },
1316
- h('strong', null, 'PR 读取来源(可选)'),
1317
- h('p', { style: mutedTextStyle }, '只有任务需要读取 Gitea PR 时才需设置。PR 链接、仓库和编号写在任务描述里;这里仅登记本机允许读取的 Gitea 来源。'),
1318
- h('label', null, '机器人', h('select', { 'aria-label': 'Gitea 来源对应机器人', value: botId,
1319
- onChange: (event) => { setBotId(event.target.value); setTrustedOrigin(
1320
- enrolled.find((bot) => bot.bot_id === event.target.value)?.gitea_origin || '') }, style: inputStyle },
1321
- h('option', { value: '' }, '请选择机器人'),
1322
- ...bots.map((bot) => h('option', { key: bot.bot_id, value: bot.bot_id,
1323
- disabled: !bot.available || !bot.connected }, `${bot.name}${bot.available && bot.connected ? '' : '(未连接)'}`)))),
1324
- selectedBot ? h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
1325
- h('input', { 'aria-label': '可信 Gitea 地址', type: 'url', value: trustedOrigin,
1326
- onChange: (event) => setTrustedOrigin(event.target.value), placeholder: 'https://git.example.com',
1327
- style: { ...inputStyle, flex: '1 1 220px' } }),
1328
- h('button', { type: 'button', disabled: busy || !trustedOrigin.trim() || (!selectedEnrollmentBound && !botConnected),
1329
- onClick: () => void (selectedEnrollmentBound ? saveTrustedOrigin() : enrollBot()), style: secondaryButtonStyle },
1330
- selectedEnrollmentBound ? '保存可信来源' : botConnected ? '启用并保存来源' : '先连接机器人'))
1331
- : null) : null
1332
1278
  const groupTaskStatusPanel = selectedEnrollmentBound && botConnected ? h('section', {
1333
1279
  id: 'ant-group-task-status', role: 'region', 'aria-label': '各群聊天与任务状态', tabIndex: -1,
1334
1280
  style: { ...collaborationCardStyle, gap: 10 } },
@@ -1339,7 +1285,7 @@ window.__ModuleLoader__.load({
1339
1285
  loading ? '读取中…' : '刷新群列表')),
1340
1286
  h('p', { style: mutedTextStyle }, groupChatLoadFailed
1341
1287
  ? '群列表暂不可用,请刷新状态后重试。'
1342
- : availableGroups.length ? '每个群分别控制普通聊天和任务;群名称取自 DSH-IM 已保存目标,识别码可用于核对。'
1288
+ : availableGroups.length ? '每个群分别控制普通聊天和任务;名称取自 DSH-IM 已保存目标,括号内为识别码末两位。未匹配名称时显示完整识别码。'
1343
1289
  : '先在目标群 @机器人,再刷新状态发现群。'),
1344
1290
  availableGroups.length ? h('div', { id: 'ant-group-card-grid', style: collaborationGridStyle },
1345
1291
  ...availableGroups.map((group) => {
@@ -1347,16 +1293,13 @@ window.__ModuleLoader__.load({
1347
1293
  && card.conversation_kind === 'group' && card.conversation_id === group.group_id).length
1348
1294
  return h('article', { key: group.group_id, style: { ...collaborationCardStyle, padding: 10, gap: 8 } },
1349
1295
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
1350
- h('div', { style: { display: 'grid', gap: 2, minWidth: 0 } },
1351
- h('strong', { style: { overflowWrap: 'anywhere' } }, group.group_name || group.group_code),
1352
- group.group_name ? h('small', { style: mutedTextStyle, title: `完整识别码 ${group.group_code}` },
1353
- `识别码尾号 ${group.group_code.slice(-2)}`) : null),
1296
+ h('strong', { title: `完整识别码 ${group.group_code}` }, groupLabel(group)),
1354
1297
  h('span', { style: mutedTextStyle }, `已开放任务 ${taskCount} 项`)),
1355
1298
  h('label', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
1356
1299
  h('input', { type: 'checkbox', checked: group.chat_enabled,
1357
1300
  disabled: busy || groupChatLoadFailed,
1358
1301
  onChange: (event) => void saveGroupChat(event.target.checked, group.group_id),
1359
- 'aria-label': `允许群 ${group.group_name ? `${group.group_name},完整识别码 ${group.group_code}` : group.group_code} 普通聊天` }), '普通聊天'),
1302
+ 'aria-label': '允许群 ' + groupLabel(group) + ' 普通聊天' }), '普通聊天'),
1360
1303
  h('button', { type: 'button', disabled: busy, style: secondaryButtonStyle,
1361
1304
  onClick: () => { startNewCard({ conversationKind: 'group', groupId: group.group_id }); focusAfterTabChange('collaboration', 'ant-new-task-summary') } },
1362
1305
  '为此群开放任务'))
@@ -1375,9 +1318,6 @@ window.__ModuleLoader__.load({
1375
1318
  h('p', { style: mutedTextStyle }, '选择机器人,设置聊天入口,再按需要开放任务规则。聊天和任务授权分别控制。')),
1376
1319
  h('span', { style: { ...mutedTextStyle, padding: '4px 9px', borderRadius: 999,
1377
1320
  backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, selectedBot ? selectedBot.name : '尚未选择机器人')),
1378
- legacyTriggerCardCount ? h('p', { role: 'status', style: { ...mutedTextStyle,
1379
- color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1380
- '当前有 ' + legacyTriggerCardCount + ' 项旧任务规则没有明确触发方式;新版插件不会按这些旧规则触发任务。升级 ANT 服务端后,再重新描述并开放需要的规则。') : null,
1381
1321
  loadFailures.length ? h('p', { role: 'status', style: mutedTextStyle },
1382
1322
  `部分协作信息暂时无法读取:${loadFailures.map((path) => loadNames[path]).join('、')}。已读取的信息仍显示,请稍后刷新状态。`) : null,
1383
1323
  h('div', { id: 'ant-collaboration-settings-grid', style: collaborationSettingsGridStyle },
@@ -1405,9 +1345,11 @@ window.__ModuleLoader__.load({
1405
1345
  const remainingDays = Math.max(1, Math.ceil((expiresAt - Date.now()) / 86_400_000))
1406
1346
  const expiry = remainingDays <= 7 ? `${remainingDays} 天后到期` : `至 ${new Date(expiresAt).toLocaleDateString('zh-CN')}`
1407
1347
  const conversation = card.conversation_kind === 'group'
1408
- ? `群 ${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}` : '私聊'
1409
1350
  const requester = card.sender_scope === 'allowlist' ? '指定成员可请求'
1410
1351
  : card.conversation_kind === 'group' ? '群成员可请求' : '可私聊者可请求'
1352
+ const blockedExternalExecutor = card.runner !== 'dsh' || card.cross_review_always === true
1411
1353
  const examplesText = Array.isArray(card.examples) ? card.examples.join('、') : ''
1412
1354
  const requesterIds = card.sender_scope === 'allowlist' && Array.isArray(card.sender_ids) ? card.sender_ids.join('、') : ''
1413
1355
  const remainingDaysLabel = remainingDays <= 7 ? 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))'
@@ -1419,12 +1361,13 @@ window.__ModuleLoader__.load({
1419
1361
  h('span', { style: { ...mutedTextStyle, padding: '2px 8px', borderRadius: 999,
1420
1362
  backgroundColor: 'var(--dsw-alias-bg-layer-2)', color: remainingDaysLabel, whiteSpace: 'nowrap' } }, expiry)),
1421
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,
1422
1367
  card.rule_description
1423
1368
  ? h('span', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, card.rule_description)
1424
1369
  : h('span', { role: 'status', style: { ...mutedTextStyle, color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } },
1425
- '旧规则没有明确触发方式;新版插件不会按它启动任务。升级 ANT 服务端后需重新描述并开放。'),
1426
- Array.isArray(card.trigger_phrases) && card.trigger_phrases.length
1427
- ? h('span', { style: { overflowWrap: 'anywhere' } }, '触发短语:' + card.trigger_phrases.join('、')) : null,
1370
+ '旧任务卡没有自然语言描述。请编辑并补充描述,让 AI 生成 Harness 后再开放。'),
1428
1371
  h('details', null,
1429
1372
  h('summary', { style: { cursor: 'pointer', color: 'var(--dsw-alias-link)', fontSize: 13 } }, '查看规则说明'),
1430
1373
  h('div', { style: { display: 'grid', gap: 4, paddingTop: 6 } },
@@ -1484,183 +1427,138 @@ window.__ModuleLoader__.load({
1484
1427
  h('p', { style: mutedTextStyle }, selectedBot
1485
1428
  ? `当前机器人:${selectedBot.name}${selectedEnrollmentBound ? ' · 已绑定此设备' : ' · 请先绑定到此设备'}`
1486
1429
  : '请先在上方选择机器人并启用协作。'),
1487
- formError?.field === 'ant-bot-selector' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1488
1430
  draftNotice ? h('p', { role: 'status', style: { ...mutedTextStyle,
1489
1431
  color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, draftNotice) : null,
1490
1432
  selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
1491
1433
  '机器人及其任务能力只在这台 DSH 设备接收和执行;同一账号的其他设备不会继承本机授权。') : null,
1492
1434
  selectedEnrollment && !selectedEnrollmentBound ? h('p', { style: mutedTextStyle },
1493
- '这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;Gitea 地址和配置记录会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
1435
+ '这是尚未绑定到设备的机器人。请在要继续使用它的电脑重新启用;原协作配置会保留。若旧设备访问曾被撤销,原任务权限不会恢复,需要重新开放。') : null,
1494
1436
  selectedBot && conversationKind === 'group' && !selectedBot.history_available ? h('p', { role: 'status', style: mutedTextStyle },
1495
1437
  '这个机器人当前不能读取平台群历史;群聊只能参考实际送达的消息和本机近期记录。请检查 DSH-IM 权限。') : null,
1496
- h('section', { id: 'ant-task-context', role: 'region', 'aria-label': '当前任务内容',
1497
- style: { display: 'grid', gap: 8, padding: 12, borderLeft: '4px solid var(--dsw-alias-state-business-primary)',
1498
- backgroundColor: 'var(--dsw-alias-bg-layer-1)', position: 'sticky', top: 0, zIndex: 2 } },
1499
- h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' } },
1500
- h('strong', { style: { fontSize: 17 } }, title || (ruleSummaryReady ? '识别后的任务' : '当前任务')),
1501
- h('span', { style: ruleDescriptionNeedsRecognition ? { ...mutedTextStyle,
1502
- color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } : mutedTextStyle },
1503
- ruleDescriptionNeedsRecognition ? '描述已修改 · 需重新识别'
1504
- : ruleSummaryReady ? '草稿 · 尚未开放' : '填写描述并识别')),
1505
- h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere', display: '-webkit-box',
1506
- WebkitBoxOrient: 'vertical', WebkitLineClamp: 2, overflow: 'hidden' } }, ruleSummaryNarrative)),
1507
- h('div', { style: { display: 'grid', gap: 4, padding: '0 4px' } },
1508
- h('strong', null, '识别详情'),
1509
- h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, taskPrompt.trim() || '执行要求会在识别后显示。'),
1510
- h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } }, recognizedInputsSummary)),
1511
- h('section', { style: { display: 'grid', gap: 6, padding: 10, borderRadius: 8,
1512
- border: '1px solid var(--dsw-alias-border-l2)', backgroundColor: 'var(--dsw-alias-bg-layer-1)' } },
1513
- h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 8, flexWrap: 'wrap' } },
1514
- h('strong', null, '触发短语(必填)'), h('span', { style: mutedTextStyle }, '模型建议 · 可修改')),
1515
- ...triggerPhraseEntries.map((phrase, index) => h('div', {
1516
- key: 'trigger-' + index, style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
1517
- h('input', { id: index === 0 ? 'ant-trigger-phrases' : undefined,
1518
- type: 'text', 'aria-label': '触发短语 ' + (index + 1), value: phrase,
1519
- 'aria-invalid': formError?.field === 'ant-trigger-phrases' ? 'true' : undefined,
1520
- maxLength: 120, placeholder: '填写触发短语', style: { ...inputStyle, flex: '1 1 220px' },
1521
- onChange: (event) => setTriggerPhraseAt(index, event.target.value) }),
1522
- triggerPhraseEntries.length > 1 ? h('button', { type: 'button', disabled: busy,
1523
- onClick: () => removeTriggerPhrase(index), style: secondaryButtonStyle }, '移除') : null)),
1524
- h('button', { type: 'button', disabled: busy || triggerPhraseEntries.length >= 8,
1525
- onClick: addTriggerPhrase, style: { ...secondaryButtonStyle, width: 'fit-content' } }, '添加触发短语'),
1526
- formError?.field === 'ant-trigger-phrases' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1527
- h('p', { style: mutedTextStyle }, '只有消息以这里的短语开头才会触发任务。模型会根据描述给出建议,也可以直接调整。')),
1528
- h('div', { style: { padding: 10, borderRadius: 8, backgroundColor: 'var(--dsw-alias-bg-layer-2)' } },
1529
- h('strong', null, '描述提示:'),
1530
- h('span', null, '写清触发消息以什么开头、要做什么、需要哪些资料和结果怎样返回。识别只整理草稿,开放前请核对。')),
1531
- h('label', { htmlFor: 'ant-rule-description' }, '描述规则', h('textarea', {
1532
- id: 'ant-rule-description', required: true, maxLength: 4000, rows: 4, value: ruleDescription,
1533
- 'aria-invalid': formError?.field === 'ant-rule-description' ? 'true' : undefined,
1534
- onChange: (event) => { setRuleDescription(event.target.value); setRecognitionMessage(''); clearFormError('ant-rule-description') },
1535
- placeholder: '用日常的话说明什么时候触发、任务要做什么、需要哪些资料以及结果怎么返回。', style: inputStyle })),
1536
- formError?.field === 'ant-rule-description' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : 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 } },
1441
+ h('label', null, '描述规则', h('textarea', { required: true, maxLength: 4000, rows: 4, value: ruleDescription,
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 会在输入停顿后分析必需范围、缺失信息和可选条件。执行方、请求人、群范围和有效期由下方通用授权控制。'),
1537
1451
  h('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
1538
1452
  h('button', { type: 'button', disabled: recognitionBusy || busy || !ruleDescription.trim(),
1539
- onClick: () => void identifyRule(), style: secondaryButtonStyle }, recognitionBusy ? '正在识别…' : ruleSummaryReady ? '重新识别' : '识别规则'),
1540
- h('span', { style: mutedTextStyle }, '每次点击调用一次团队模型并计入用量;只整理草稿,不会开放任务。改完描述后可重新识别。')),
1541
- recognitionMessage ? h('p', { role: 'status', 'aria-live': 'polite', style: feedbackNoticeStyle }, recognitionMessage) : null,
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 } },
1457
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 } },
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,
1468
+ h('p', { style: mutedTextStyle }, ruleSummaryReady
1469
+ ? '草稿未开放。检查任务理解、必需范围和建议;有误就改描述,AI 会重新分析。'
1470
+ : ruleSummaryPending ? '分析完成前不能开放任务。' : 'AI 会整理任务理解;草稿不会自动开放。'))),
1542
1471
  h('section', { style: { ...panelStyle, padding: 10, gap: 9, display: 'grid',
1543
1472
  gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 280px), 1fr))' } },
1544
- h('strong', { style: { gridColumn: '1 / -1' } }, 'Agent 运行方式'),
1473
+ h('strong', { style: { gridColumn: '1 / -1' } }, '通用授权'),
1545
1474
  h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1546
- '选择一位 Agent 处理任务,或选择多位 Agent 按需/每次并行审查。'),
1547
- h('div', { id: 'ant-agent-mode', role: 'group', 'aria-label': 'Agent 运行方式', tabIndex: -1,
1548
- style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
1549
- h('button', { type: 'button', 'aria-pressed': !multiAgentMode,
1550
- onClick: () => selectExecutionMode('single'),
1551
- style: !multiAgentMode ? primaryButtonStyle : secondaryButtonStyle }, '单 Agent'),
1552
- h('button', { type: 'button', 'aria-pressed': multiAgentMode,
1553
- onClick: () => selectExecutionMode('multi'),
1554
- style: multiAgentMode ? primaryButtonStyle : secondaryButtonStyle }, '多 Agent 交叉审查')),
1555
- formError?.field === 'ant-agent-mode' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1556
- multiAgentMode && crossReviewAlways ? null : h('label', null,
1557
- multiAgentMode ? '普通请求默认由' : '执行 Agent', h('select', { value: runner, onChange: (event) => selectRunner(event.target.value),
1475
+ '上方描述规则任务要做什么;这里限定谁能请求、在哪里请求、由谁执行和授权多久。'),
1476
+ h('label', null, '执行方', h('select', { value: runner, onChange: (event) => selectRunner(event.target.value),
1558
1477
  style: inputStyle },
1559
1478
  h('option', { value: 'dsh', disabled: !dshAvailable && runner !== 'dsh' }, 'DSH · 当前团队模型'),
1560
- h('option', { value: 'codex', disabled: (!codexAvailable || !hasPrParameter) && runner !== 'codex' }, 'Codex · PR 快照只读'),
1561
- 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')),
1562
1481
  h('span', { style: mutedTextStyle }, runner === 'codex'
1563
- ? 'Codex 使用本机登录,仅读取已核验的 PR 快照。'
1564
- : runner === 'claude-code' ? 'Claude Code 沿用这台 DSH 的权限设置。'
1565
- : 'DSH 使用获准团队模型处理会话中可见或已提供的内容。')),
1566
- !multiAgentMode && !dshAvailable ? h('p', { role: 'status', style: mutedTextStyle }, executorReason('dsh')) : null,
1567
- !multiAgentMode && (!codexAvailable || !hasPrParameter) ? h('p', { role: 'status', style: mutedTextStyle },
1568
- `Codex 暂不可选:${executorReason('codex')}`) : null,
1569
- !multiAgentMode && (!claudeCodeAvailable || !hasPrParameter) ? h('p', { role: 'status', style: mutedTextStyle },
1570
- `Claude Code 暂不可选:${executorReason('claude-code')}`) : null,
1571
- h('div', { id: 'ant-reviewer-list', role: 'group', 'aria-label': '交叉审查 Agent', tabIndex: -1,
1572
- style: { gridColumn: '1 / -1', display: multiAgentMode ? 'grid' : 'none', gap: 8 } },
1573
- h('strong', null, '参与交叉审查的 Agent(至少选择两位)'),
1574
- h('p', { style: mutedTextStyle }, '请求人需要在触发短语开头的任务消息中明确提出“交叉 review”,按需策略才会并行运行。'),
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),
1487
+ style: { gridColumn: '1 / -1', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 10 } },
1488
+ h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, parallelAgentsSummary
1489
+ ? `并行执行:${parallelAgentsSummary}` : '多 Agent 并行(可选)'),
1490
+ h('p', { style: mutedTextStyle }, '当前只开放 DSH。外部 Agent 的逐任务文件、Git 和网络权限无法由 ANT 核验;编辑旧任务时可取消其选择并改用 DSH。'),
1575
1491
  h('div', { style: { display: 'flex', gap: 14, flexWrap: 'wrap' } },
1576
- 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'),
1577
1493
  disabled: !dshAvailable && !reviewers.includes('dsh'),
1578
1494
  onChange: (event) => changeReviewer('dsh', event.target.checked) }), ' DSH'),
1579
- h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Codex', checked: reviewers.includes('codex'),
1580
- 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'),
1581
1497
  onChange: (event) => changeReviewer('codex', event.target.checked) }), ' Codex'),
1582
- h('label', null, h('input', { type: 'checkbox', 'aria-label': '审查 Agent Claude Code', checked: reviewers.includes('claude-code'),
1583
- 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'),
1584
1500
  onChange: (event) => changeReviewer('claude-code', event.target.checked) }), ' Claude Code')),
1585
- !dshAvailable ? h('p', { role: 'status', style: mutedTextStyle }, executorReason('dsh')) : null,
1586
- !codexAvailable || !hasPrParameter ? h('p', { role: 'status', style: mutedTextStyle },
1587
- `Codex 暂不可选:${executorReason('codex')}`) : null,
1588
- !claudeCodeAvailable || !hasPrParameter ? h('p', { role: 'status', style: mutedTextStyle },
1589
- `Claude Code 暂不可选:${executorReason('claude-code')}`) : null,
1590
- multiAgentMode && reviewers.length < 2 && formError?.field !== 'ant-reviewer-list'
1591
- ? h('p', { role: 'alert', style: feedbackNoticeStyle },
1592
- '交叉审查至少选择两位可用 Agent。') : null,
1593
- formError?.field === 'ant-reviewer-list' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1594
- reviewers.length > 1 && defaults?.cross_review_policy_version === 1
1595
- ? h('fieldset', { style: { margin: 0, padding: '8px 10px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8 } },
1596
- h('legend', { style: mutedTextStyle }, '运行策略'),
1597
- h('label', { style: { display: 'flex', gap: 8, alignItems: 'start', marginBottom: 6 } },
1598
- h('input', { type: 'radio', name: 'ant-cross-review-policy', checked: !crossReviewAlways,
1599
- onChange: () => setCrossReviewAlways(false) }),
1600
- h('span', null, `默认由 ${reviewerLabel(runner)} 执行;请求人可逐次要求交叉审查。`)),
1601
- h('label', { style: { display: 'flex', gap: 8, alignItems: 'start' } },
1602
- h('input', { type: 'radio', name: 'ant-cross-review-policy', checked: crossReviewAlways,
1603
- onChange: () => setCrossReviewAlways(true) }),
1604
- h('span', null, '每次任务都并行运行全部已选 Agent;请求人无需额外提出交叉审查。')))
1605
- : reviewers.length > 1 ? h('p', { style: mutedTextStyle }, '每次并行模式需升级 ANT 服务端;当前仍可由请求人逐次提出交叉审查。') : null,
1606
- !hasPrParameter ? h('p', { style: mutedTextStyle }, '从描述识别出 PR 任务后,才能使用 Codex 或 Claude Code。') : null),
1607
- h('strong', { style: { gridColumn: '1 / -1', paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } }, '请求范围'),
1501
+ null),
1502
+ nativePrUnavailableReason ? h('p', { role: 'status', style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1503
+ `${nativePrUnavailableReason}。当前仍可使用 DSH 团队模型处理任务。`) : null,
1608
1504
  h('label', null, '使用场景', h('select', { value: conversationKind,
1609
1505
  onChange: (event) => { setConversationKind(event.target.value); setGroupId('') }, style: inputStyle },
1610
1506
  h('option', { value: 'dm' }, '私聊'), h('option', { value: 'group' }, '指定群聊'))),
1611
- conversationKind === 'group' ? h('div', { style: { gridColumn: '1 / -1', display: 'grid',
1612
- gridTemplateColumns: 'minmax(0, 1fr) auto', gap: 8, alignItems: 'end' } },
1613
- h('label', null, '目标群', h('select', { id: 'ant-group-selector', required: true, value: groupId,
1614
- onChange: (event) => { setGroupId(event.target.value); clearFormError('ant-group-selector') }, style: inputStyle },
1507
+ conversationKind === 'group' ? h('div', { style: { display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) auto',
1508
+ gap: 8, alignItems: 'end' } },
1509
+ h('label', null, '群聊', h('select', { required: true, value: groupId,
1510
+ onChange: (event) => setGroupId(event.target.value), style: inputStyle },
1615
1511
  h('option', { value: '' }, '让群成员先 @ 机器人,再刷新列表'),
1616
- ...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id, title: group.group_code },
1617
- groupSummary(group))))),
1512
+ ...availableGroups.map((group) => h('option', { key: group.group_id, value: group.group_id },
1513
+ groupLabel(group))))),
1618
1514
  h('button', { type: 'button', disabled: busy,
1619
- onClick: () => void load().catch((error) => reportFormError('ant-group-selector', error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
1620
- conversationKind === 'group' && !groupId ? h('p', { role: 'status', style: mutedTextStyle },
1621
- '请选择目标群;列表为空时,让群成员先 @ 机器人,再刷新群列表。') : null,
1622
- formError?.field === 'ant-group-selector' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1515
+ onClick: () => void load().catch((error) => setMessage(error.message)), style: secondaryButtonStyle }, '刷新群列表')) : null,
1623
1516
  h('label', null, '谁可以请求', h('select', { value: senderScope,
1624
- onChange: (event) => { setSenderScope(event.target.value); clearFormError('ant-sender-ids') }, style: inputStyle },
1517
+ onChange: (event) => setSenderScope(event.target.value), style: inputStyle },
1625
1518
  h('option', { value: 'any' }, conversationKind === 'group' ? '此群任何成员' : '任何能私聊此机器人者'),
1626
1519
  h('option', { value: 'allowlist' }, '指定 IM 用户 ID'))),
1627
- senderScope === 'allowlist' ? h('label', null, '可请求成员 ID', h('input', { id: 'ant-sender-ids', 'aria-label': '可请求的 IM 用户 ID',
1628
- required: true, value: senderIds, onChange: (event) => { setSenderIds(event.target.value); clearFormError('ant-sender-ids') },
1520
+ senderScope === 'allowlist' ? h('label', null, '可请求成员 ID', h('input', { 'aria-label': '可请求的 IM 用户 ID',
1521
+ required: true, value: senderIds, onChange: (event) => setSenderIds(event.target.value),
1629
1522
  placeholder: '多个 ID 用逗号分隔', style: inputStyle })) : null,
1630
- senderScope === 'allowlist' && !senderIds.trim() ? h('p', { role: 'status', style: mutedTextStyle },
1631
- '填写至少一个可请求成员的 IM 用户 ID。') : null,
1632
- formError?.field === 'ant-sender-ids' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1633
1523
  editingCardId ? h('p', { style: { ...mutedTextStyle, alignSelf: 'end' } },
1634
1524
  `有效期:${new Date(editingExpiresAt * 1000).toLocaleString('zh-CN')}(编辑时保持不变)`)
1635
1525
  : h('label', null, '有效期', h('select', { value: days, onChange: (event) => setDays(Number(event.target.value)), style: inputStyle },
1636
- h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'),
1637
- h('option', { value: 30 }, '30 天'), h('option', { value: 90 }, '90 天'))),
1638
- nativeReviewerSelected ? h('p', { style: { ...mutedTextStyle, gridColumn: '1 / -1' } },
1639
- '本机 PR 审查需要 Gitea 只读权限 read:repository 和 read:user;ANT 不会把令牌交给执行方。') : null),
1640
- ),
1641
- h('aside', { id: 'ant-open-confirm', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
1526
+ h('option', { value: 1 }, '1 天'), h('option', { value: 7 }, '7 天'), h('option', { value: 30 }, '30 天'))),
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),
1531
+ h('aside', { id: 'ant-authorization-review', role: 'region', 'aria-label': '开放前复核', tabIndex: -1,
1642
1532
  style: { ...settingsPanelStyle, padding: 14, gap: 10, alignSelf: 'stretch' } },
1643
1533
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 } },
1644
- h('h4', { style: sectionTitleStyle }, '开放前确认'),
1534
+ h('h4', { style: sectionTitleStyle }, '开放前看一眼'),
1645
1535
  h('span', { style: { ...mutedTextStyle, padding: '3px 8px', borderRadius: 999,
1646
1536
  backgroundColor: 'var(--dsw-alias-bg-layer-2)' } }, '草稿 · 未开放')),
1647
- h('p', { style: { ...mutedTextStyle, overflowWrap: 'anywhere' } },
1648
- (title || '未命名任务') + ' · 触发短语“' + (ruleTriggerSummary.join('”或“') || '尚未填写') + '”'),
1649
1537
  h('p', { style: mutedTextStyle }, `这项任务将在${conversationKind === 'group'
1650
- ? `群“${selectedGroupSummary || '尚未选择'}”` : '私聊'}开放给${requesterSummary};${reviewModeSummary};`
1538
+ ? `群“${selectedGroupSummary || '尚未选择'}”` : '私聊'}开放给${requesterSummary},由${executorSummary}执行;`
1651
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,
1652
1548
  cardBlockReason ? h('p', { role: 'status', style: { ...mutedTextStyle,
1653
1549
  color: 'var(--dsw-alias-state-warning-primary, var(--dsw-alias-label-secondary))' } }, cardBlockReason) : null,
1654
- formError?.field === 'ant-open-confirm' ? h('p', { role: 'alert', style: feedbackNoticeStyle }, formError.text) : null,
1655
1550
  h('button', { type: 'submit', form: 'ant-capability-create-form', disabled: busy || Boolean(cardBlockReason),
1656
1551
  style: { ...primaryButtonStyle, width: '100%' } }, editingCardId ? '保存能力修改' : '确认并开放能力'))))),
1657
1552
  h('div', { role: 'region', 'aria-label': '本机处理设置', style: { display: activeTab === 'local' ? 'grid' : 'none', gap: 12 } },
1658
- trustedOriginCard,
1659
1553
  showPrSecuritySettings ? h('section', { style: { ...settingsPanelStyle, padding: 14 } },
1660
1554
  h('strong', null, 'PR 只读凭据(本机)'),
1661
1555
  h('p', { style: mutedTextStyle }, tokenConfigured
1662
- ? '只读访问令牌已保存在这台 DSH 的安全凭据中;不会同步到 ANT 服务端或发送给执行器。'
1663
- : '读取 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 不会把只读令牌发送到任务描述中的地址。'),
1664
1562
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
1665
1563
  h('input', { 'aria-label': 'Gitea 只读访问令牌', type: 'password', autoComplete: 'new-password', value: giteaToken,
1666
1564
  onChange: (event) => setGiteaToken(event.target.value),
@@ -1670,7 +1568,7 @@ window.__ModuleLoader__.load({
1670
1568
  onClick: () => void saveGiteaToken(), style: secondaryButtonStyle }, tokenConfigured ? '替换本机令牌' : '保存本机令牌'))) : null,
1671
1569
  h('details', { style: { border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8, padding: 12 } },
1672
1570
  h('summary', { style: { cursor: 'pointer', fontWeight: 600 } }, '本机处理设置'),
1673
- h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务;PR 仓库缓存上限按这台 DSH 的长期仓库缓存总量计算,不包括任务临时工作目录。'),
1571
+ h('p', { style: mutedTextStyle }, '本机并发上限只影响这台电脑接收的新任务。外部代码执行器已暂停,当前不创建 PR 仓库缓存;缓存上限仅保留用于旧缓存回收,暂不可调整。'),
1674
1572
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' } },
1675
1573
  h('span', { style: mutedTextStyle }, loadFailures.includes('settings') ? '团队默认值暂未读取。'
1676
1574
  : `团队默认:先看 ${defaults?.context_messages ?? 10} 条前文、最多翻阅 ${defaults?.history_max_records ?? 100} 条平台记录、同时处理 ${defaults?.max_concurrency ?? 3} 项任务。`),
@@ -1684,26 +1582,28 @@ window.__ModuleLoader__.load({
1684
1582
  h('option', { value: '' }, '跟随团队默认'),
1685
1583
  ...Array.from({ length: 8 }, (_, index) => h('option', { key: index + 1, value: index + 1 }, `${index + 1} 项任务`)))),
1686
1584
  h('label', { style: { display: 'grid', gap: 5, flex: '0 1 200px', maxWidth: '100%' } },
1687
- '这台电脑 PR 仓库缓存上限(GiB)',
1688
- 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,
1689
1588
  value: repositoryCacheLimitGib,
1690
- onChange: (event) => setRepositoryCacheLimitGib(event.target.value),
1691
1589
  style: { ...inputStyle, width: 128, maxWidth: '100%', boxSizing: 'border-box' } })),
1692
1590
  h('button', { disabled: busy, style: secondaryButtonStyle }, '保存本机处理设置'),
1693
1591
  h('p', { style: { ...mutedTextStyle, flex: '1 1 100%', margin: 0 } },
1694
- '默认 10 GiB,范围 1–100 GiB,按长期仓库文件逻辑大小计算(不含临时工作区和文件系统分配开销)。日常不会淘汰仍启用能力的缓存;停用或到期能力会在刷新时回收。单次拉取若实际越界,为恢复预算会清理这项能力的仓库缓存(旧 PR 可重新下载)。')),
1592
+ '当前不拉取 PR 仓库快照;刷新授权状态时仍会清理已停用或到期能力的旧缓存。')),
1695
1593
  ),
1696
- h('section', { style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
1594
+ h('section', { 'aria-label': '执行方状态与权限边界', style: { ...settingsPanelStyle, padding: 14, gap: 8 } },
1697
1595
  h('strong', null, '执行方状态与权限边界'),
1698
- h('p', { style: mutedTextStyle }, '这里显示本机可用情况;具体哪些执行方能处理某项任务,由“协作”页中的规则决定。'),
1596
+ h('p', { style: mutedTextStyle }, '这里显示 DSH 本机的执行器状态;Codex 或 Claude Code 已注册,不代表它们获准执行协作任务。'),
1699
1597
  loadFailures.includes('executors') ? h('p', { role: 'status', style: mutedTextStyle }, '暂时无法读取执行方状态,请刷新后重试。') : null,
1700
- ...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', {
1701
1599
  key: item.id, style: { display: 'grid', gap: 3, paddingTop: 8, borderTop: '1px solid var(--dsw-alias-border-l2)' } },
1702
- h('strong', null, item.title + ' · ' + (item.available ? '可用' : item.reason || '未启用')),
1703
- h('span', { style: mutedTextStyle }, item.id === 'codex-readonly'
1704
- ? '读取固定到 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 和网络权限。'
1705
1605
  : item.id === 'claude-code'
1706
- ? '读取相同 PR 快照,但沿用本机 DSH 权限设置,不具备 Codex 路径的固定只读隔离。'
1606
+ ? 'ANT 暂不启动 Claude Code 子 Agent,因为当前无法核验逐任务文件、Git 和网络权限。'
1707
1607
  : '处理授权任务提供的内容,不调用本机工具。'))))))
1708
1608
  }
1709
1609