新增: 技能注入反馈闭环与项目扫描前端接线

技能注入成功/失败 toast 与事件类型监听接线
项目级技能扫描前端传参点就绪
This commit is contained in:
lxy
2026-10-01 19:35:19 +08:00
parent 75c5e23523
commit 6ef15a0940
5 changed files with 33 additions and 2 deletions
+5 -1
View File
@@ -418,6 +418,10 @@ export type AiChatEvent = ({
// AE-2025-04 会话级信任:同会话已批准过同类操作(同工具+同目录)自动放行,前端显示轻量 toast。 // AE-2025-04 会话级信任:同会话已批准过同类操作(同工具+同目录)自动放行,前端显示轻量 toast。
// 不写消息/不动 pending(Started/Completed 仍独立发),仅提示性事件。 // 不写消息/不动 pending(Started/Completed 仍独立发),仅提示性事件。
type: 'AiToolAutoApproved'; id: string; tool: string; dir: string type: 'AiToolAutoApproved'; id: string; tool: string; dir: string
} | {
// 技能注入反馈闭环:/ 技能 resolve 成功(ok=true)/失败(ok=false,未找到或读失败)通知,
// 前端轻 toast(成功「已注入技能《name》」/失败「技能 name 未找到」)。仅提示,不改消息流。
type: 'AiSkillInjected'; name: string; ok: boolean
} | { } | {
// path_auth 审批链阶段3b(统一审批模型):AiApprovalRequired 携带 kind 字段区分 // path_auth 审批链阶段3b(统一审批模型):AiApprovalRequired 携带 kind 字段区分
// 'path'(F-260619-03 Phase B 路径授权挂起,走 once/session/always/deny)vs 'risk'(普通 RiskLevel 审批, // 'path'(F-260619-03 Phase B 路径授权挂起,走 once/session/always/deny)vs 'risk'(普通 RiskLevel 审批,
@@ -772,7 +776,7 @@ export interface SkillInfo {
name: string name: string
description: string description: string
argument_hint?: string argument_hint?: string
/** skill | command | plugin */ /** skill | project | command | plugin(project 为项目级 .claude/skills 扫描来源) */
source: string source: string
/** SKILL.md 绝对路径(后端注入时读全文) */ /** SKILL.md 绝对路径(后端注入时读全文) */
path: string path: string
+13
View File
@@ -195,6 +195,8 @@ let _unlistenToolSlow: (() => void) | null = null
let _unlistenAutoApproved: (() => void) | null = null let _unlistenAutoApproved: (() => void) | null = null
// P1-e-d: 审批失败 toast unlistener(useAiApproval 审批 IPC 未送达经事件总线 emit,同 _unlistenToolSlow 模式) // P1-e-d: 审批失败 toast unlistener(useAiApproval 审批 IPC 未送达经事件总线 emit,同 _unlistenToolSlow 模式)
let _unlistenAiToast: (() => void) | null = null let _unlistenAiToast: (() => void) | null = null
// 技能注入反馈 toast unlistener(后端 AiSkillInjected 经 ai-chat-event 直达,见 onMounted)
let _unlistenSkillInjected: (() => void) | null = null
// 第二批抽离: inputText/inputEl/图片输入/技能/实体联想/handleSend 已迁移至 ChatInput.vue // 第二批抽离: inputText/inputEl/图片输入/技能/实体联想/handleSend 已迁移至 ChatInput.vue
// chatInputRef: 父调 expose(focus/startEditFocus/clearInput/hasUnsentInput/sendExamplePrompt) // chatInputRef: 父调 expose(focus/startEditFocus/clearInput/hasUnsentInput/sendExamplePrompt)
@@ -477,6 +479,7 @@ onBeforeUnmount(() => {
if (_unlistenToolSlow) { _unlistenToolSlow(); _unlistenToolSlow = null } // B-260616-12 if (_unlistenToolSlow) { _unlistenToolSlow(); _unlistenToolSlow = null } // B-260616-12
if (_unlistenAutoApproved) { _unlistenAutoApproved(); _unlistenAutoApproved = null } // AE-2025-04 if (_unlistenAutoApproved) { _unlistenAutoApproved(); _unlistenAutoApproved = null } // AE-2025-04
if (_unlistenAiToast) { _unlistenAiToast(); _unlistenAiToast = null } // P1-e-d if (_unlistenAiToast) { _unlistenAiToast(); _unlistenAiToast = null } // P1-e-d
if (_unlistenSkillInjected) { _unlistenSkillInjected(); _unlistenSkillInjected = null } // 技能注入反馈 toast
// toast timer 清理由 useToast composable 的 onUnmounted 自动管理 // toast timer 清理由 useToast composable 的 onUnmounted 自动管理
}) })
@@ -520,6 +523,16 @@ onMounted(async () => {
_unlistenAiToast = await listen<{ msg: string; type: 'info' | 'error' | 'warning' | 'success' }>('ai-toast', (e) => { _unlistenAiToast = await listen<{ msg: string; type: 'info' | 'error' | 'warning' | 'success' }>('ai-toast', (e) => {
showToast(e.payload.msg, e.payload.type) showToast(e.payload.msg, e.payload.type)
}) })
// 技能注入反馈闭环(注入成功/失败轻 toast):后端 resolve_and_inject 双写 ai-chat-event,
// 此处独立 listen 过滤 AiSkillInjected(不动 useAiEvents 分发器,toast 归组件本地副作用)。
_unlistenSkillInjected = await listen<{ name: string; ok: boolean }>('ai-chat-event', (e) => {
const p = e.payload as { type?: string; name?: string; ok?: boolean }
if (p?.type !== 'AiSkillInjected' || !p.name) return
showToast(
p.ok ? t('aiChat.skillInjectedToast', { name: p.name }) : t('aiChat.skillNotFoundToast', { name: p.name }),
p.ok ? 'success' : 'warning',
)
})
// F-15 阶段2: 注册上下文管理事件监听器(清空/压缩生命周期事件 → toast/刷新) // F-15 阶段2: 注册上下文管理事件监听器(清空/压缩生命周期事件 → toast/刷新)
await initContextEventListeners() await initContextEventListeners()
// 恢复生成中态(刷新后/分离窗口接管): // 恢复生成中态(刷新后/分离窗口接管):
+7 -1
View File
@@ -150,7 +150,13 @@ async function loadProviders() {
} }
} }
/** 拉取本机技能列表(用于 `/` 联想,静默失败) */ /** 拉取本机技能列表(用于 `/` 联想,静默失败)。
*
* 项目级技能接线点(后端+IPC 已就绪):DevFlow 无全局「当前项目」语境(projectsStore 是
* 多项目清单,无 active root),故暂不传 projectPath(仅 home 级扫描)。后续有当前项目
* 上下文时改调 `aiApi.listSkills(projectPath)` 即启用 `<project>/.claude/skills` 扫描,
* 同名覆盖 home 级(source="project")。
*/
async function loadSkills() { async function loadSkills() {
try { try {
state.skills = await aiApi.listSkills() state.skills = await aiApi.listSkills()
+4
View File
@@ -250,6 +250,10 @@ export default {
// Auto-approval toast (same-session already approved same-kind op: same tool + same dir) // Auto-approval toast (same-session already approved same-kind op: same tool + same dir)
autoApprovedToast: 'Auto-approved: {tool}({dir})', autoApprovedToast: 'Auto-approved: {tool}({dir})',
// ── Skill injection feedback (success/failure light toast for /skill resolve) ──
skillInjectedToast: 'Skill "{name}" injected',
skillNotFoundToast: 'Skill {name} not found',
// ── CIStatus.vue (commit CI checks status card) ── // ── CIStatus.vue (commit CI checks status card) ──
ciTitle: 'CI Checks', ciTitle: 'CI Checks',
ciNFailed: '{n} failed', ciNFailed: '{n} failed',
+4
View File
@@ -251,6 +251,10 @@ export default {
// 自动放行 toast(同会话已批准过同类操作:同工具+同目录,轻量 info 非审批气泡) // 自动放行 toast(同会话已批准过同类操作:同工具+同目录,轻量 info 非审批气泡)
autoApprovedToast: '已自动放行: {tool}({dir})', autoApprovedToast: '已自动放行: {tool}({dir})',
// ── 技能注入反馈闭环(/ 技能 resolve 成功/失败轻 toast) ──
skillInjectedToast: '已注入技能《{name}》',
skillNotFoundToast: '技能 {name} 未找到',
// ── CIStatus.vue(代码提交 CI 检查状态卡)── // ── CIStatus.vue(代码提交 CI 检查状态卡)──
ciTitle: 'CI 检查', ciTitle: 'CI 检查',
ciNFailed: '{n} 项失败', ciNFailed: '{n} 项失败',