重构: aichat agent 能力系统化(L1元能力+L2/L3后端+list去重)
L1 agent 元能力层(治痛①死循环零交付): - env_profile 环境姿势注入 + shell 默认 PowerShell(防引号地狱) - 断路器:同类工具失败≥3熔断 + guard.reset - detect_environment 主动探测工具(python/node/shell) - 求助协议 AiHelpRequired 事件 + 前端求助卡 L2 统一状态机后端(治痛②,前端批2): - ConvState enum 5态 + 合法转换守卫(conv_state.rs) - GeneratingGuard 接入视图层(guard.rs) L3 事件总线后端骨架(治痛③④⑤,接入批2): - EventBus pub-sub + AiBusEvent 8变体(event_bus.rs) list 工具调用重复治理第一步: - build_system_prompt_with_excluded 去重被@实体 + 清单注明语
This commit is contained in:
@@ -57,6 +57,10 @@
|
||||
</div>
|
||||
<!-- 第五批抽离至 ai/MaxRoundsCard.vue(F-260616-03 达 max_iterations 暂停态操作卡,零行为变更,store 单例 + pendingMaxRounds 模块级 ref 共享,toast 经 emit 转父) -->
|
||||
<MaxRoundsCard @toast="(p) => showToast(p.msg, p.type)" />
|
||||
<!-- L1 求助协议(aichat 体验与 agent 能力系统化重构 §2.3,2026-06-21):
|
||||
断路器熔断 emit AiHelpRequired → pendingHelp(模块级 ref)→ 本卡显 reason + options 按钮供用户选。
|
||||
机制优先 prompt 说教:代码强制熔断 + 结构化求助卡(非教 AI 主动问用户)。 -->
|
||||
<HelpRequiredCard @toast="(p) => showToast(p.msg, p.type)" />
|
||||
<!-- F-260619-03 Phase B: 路径授权弹窗(LLM 访问白名单外目录挂起,三选项 once/always/deny)。
|
||||
path_auth 审批链阶段3b:统一审批开关 df-ai-unified-approval 开(true,默认)时下线——
|
||||
path 挂起归一进 ToolCard 内联审批(单真相源:状态层合);关(false,兜底回退)时挂载
|
||||
@@ -136,6 +140,7 @@ import ChatInput from './ai/ChatInput.vue'
|
||||
import TopBar from './ai/TopBar.vue'
|
||||
import MessageList from './ai/MessageList.vue'
|
||||
import MaxRoundsCard from './ai/MaxRoundsCard.vue'
|
||||
import HelpRequiredCard from './ai/HelpRequiredCard.vue'
|
||||
import DirAuthDialog from './ai/DirAuthDialog.vue'
|
||||
import { useConfirm } from '../composables/useConfirm'
|
||||
import { useProjectStore } from '../stores/project'
|
||||
|
||||
105
src/components/ai/HelpRequiredCard.vue
Normal file
105
src/components/ai/HelpRequiredCard.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<!-- L1 求助协议(aichat 体验与 agent 能力系统化重构 §2.3,2026-06-21):断路器熔断后展示的操作卡。
|
||||
触发:后端断路器连续同类失败达阈值 → emit AiHelpRequired(已 guard.reset,loop 终止)→
|
||||
useAiEvents handleLifecycleEvent 翻 pendingHelp(模块级 ref)→ 本卡渲染。
|
||||
复用 MaxRoundsCard 模式:per-conv 守卫(pendingHelp.conversationId === active)+ 行为已记入本地 ref。
|
||||
显 reason(原因)+ context(最近错误 sample)+ options 按钮供用户选(换思路/授权路径/人工接管)。
|
||||
选 option → 仅关闭求助卡(后端 loop 已终止,用户重新发消息即"换思路/人工接管"入口,
|
||||
路径授权走 DirAuthDialog 既有链路)。机制优先 prompt 说教:代码强制熔断 + 结构化求助,
|
||||
非"教 AI 失败就问用户"(LLM 不可靠)。 -->
|
||||
<div v-if="helpActive" class="ai-help-required">
|
||||
<span class="ai-help-required-text">{{ helpActive.reason }}</span>
|
||||
<span class="ai-help-required-hint">{{ helpActive.context }}</span>
|
||||
<div class="ai-help-required-options">
|
||||
<button
|
||||
v-for="opt in helpActive.options"
|
||||
:key="opt"
|
||||
class="ai-help-required-btn"
|
||||
@click="handleSelectOption(opt)"
|
||||
>{{ opt }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAiStore } from '../../stores/ai'
|
||||
import { pendingHelp } from '../../composables/ai/useAiEvents'
|
||||
|
||||
// toast 经 emit 转父(保持单一 toast 源,与 MaxRoundsCard 一致)
|
||||
const emit = defineEmits<{
|
||||
(e: 'toast', payload: { msg: string; type: 'error' | 'warning' | 'info' }): void
|
||||
}>()
|
||||
|
||||
const store = useAiStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// 当前挂起的求助(有值即后端发过 AiHelpRequired)。pendingHelp 由 useAiEvents 翻:
|
||||
// AiHelpRequired 设值;AiCompleted/AiError 清值(重新发送或错误中断后求助卡消失)。
|
||||
//
|
||||
// helpActive 既做守卫(挂起会话===当前展示会话)又做模板取值源:返回 PendingHelp 非空或 null,
|
||||
// 模板 v-if="helpActive" 取真值后 Vue 类型收窄为非 null,可直接访问 .reason/.context/.options。
|
||||
// 显示守卫:仅当挂起求助属当前展示会话时显(F-09 多会话并发不串台)。
|
||||
// 注:不依赖 streaming——求助事件后端已 guard.reset(generating=false),若依赖 isGenerating
|
||||
// 会导致卡片不显(对齐 MaxRoundsCard F-260620 去 streaming 依赖的根治理)。
|
||||
const helpActive = computed(() => {
|
||||
const p = pendingHelp.value
|
||||
if (p && p.conversationId === store.state.activeConversationId) return p
|
||||
return null
|
||||
})
|
||||
|
||||
/** 选 option:关求助卡即可。后端 loop 已在 AiHelpRequired 时终止(guard.reset + return),
|
||||
* 用户选 option 后的"换思路/授权路径/人工接管"由用户重新发消息/操作触发(无独立 IPC)。
|
||||
* 点"授权路径"提示用户路径授权链路(DirAuthDialog 自动触发,无需此卡代发)。
|
||||
* 选后清 pendingHelp,卡片隐藏(对齐 MaxRoundsCard 点继续/停止后由事件清的语义,此处无续跑事件故立即清)。 */
|
||||
function handleSelectOption(opt: string): void {
|
||||
void opt // option 仅区分按钮文案,后端 loop 已终止无独立 IPC 续跑,选即清卡片
|
||||
pendingHelp.value = null
|
||||
emit('toast', { msg: t('aiChat.helpRequiredAck'), type: 'info' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 复用 MaxRoundsCard 样式基调(warning 色系,求助属"需用户介入"信号,视觉一致) */
|
||||
.ai-help-required {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
margin-bottom: 6px;
|
||||
padding: 6px 8px;
|
||||
background: color-mix(in srgb, var(--df-warning) 10%, transparent);
|
||||
border: 0.5px solid color-mix(in srgb, var(--df-warning) 40%, transparent);
|
||||
border-radius: var(--df-radius);
|
||||
}
|
||||
.ai-help-required-text {
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
color: var(--df-warning);
|
||||
}
|
||||
.ai-help-required-hint {
|
||||
font-size: 10.5px;
|
||||
color: var(--df-text-dim);
|
||||
opacity: 0.9;
|
||||
word-break: break-all;
|
||||
}
|
||||
.ai-help-required-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.ai-help-required-btn {
|
||||
border: 0.5px solid color-mix(in srgb, var(--df-warning) 60%, transparent);
|
||||
background: var(--df-warning);
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 3px 10px;
|
||||
border-radius: calc(var(--df-radius) - 2px);
|
||||
transition: filter 0.15s var(--df-ease);
|
||||
}
|
||||
.ai-help-required-btn:hover { filter: brightness(1.1); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user