新增: 工具工作流补全(diff_files工具 + 技能清单注入 + 工作流进度共享 + human端到端测试 + 知识库MCP工具)

This commit is contained in:
lxy
2026-08-08 21:24:53 +08:00
parent 6ba6daf188
commit 9eb2995a74
14 changed files with 807 additions and 93 deletions
+33 -78
View File
@@ -237,6 +237,7 @@ import { useRoute, useRouter } from 'vue-router'
import { listen } from '@tauri-apps/api/event'
import { taskApi, projectApi, ideaApi } from '@/api'
import { workflowApi } from '@/api'
import { useProjectStore } from '@/stores/project'
import { formatDate } from '@/utils/time'
import { useRendered } from '@/composables/useMarkdown'
import {
@@ -245,14 +246,15 @@ import {
priorityLabel,
priorityClass,
} from '../constants/project'
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types'
import type { TaskRecord, ProjectRecord, IdeaRecord, DfDataChangedPayload } from '@/api/types'
import TaskOutputCard from '@/components/task/TaskOutputCard.vue'
import WorkflowDagDisplay from '@/components/workflow/WorkflowDagDisplay.vue'
import type { NodeStatus } from '@/components/workflow/WorkflowDagDisplay.vue'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
// B-41: 共享 workflow store(liveEvents 全局收集 + workflowProgress 派生进度)
const store = useProjectStore()
const loading = ref(true)
const errorMsg = ref('')
@@ -386,32 +388,47 @@ async function confirmSubtask() {
// F-260616-06 ①-1 / B-41 工作流推进状态(与手动 advance 的 advancing 独立,互不干扰)
// ------------------------------------------------------------
// wfAdvancing:推进中态;wfExecId:当前监听的工作流执行 ID(过滤 workflow-event);
// wfTotalNodes/wfDoneCount/wfRunningNode:轻量进度(听 NodeStarted/NodeCompleted);
// wfTotalNodes/wfDoneCount/wfRunningNode:轻量进度(由共享 store 按 execId 派生);
// wfResult: 'completed' | 'failed' | null —— 终态提示,完成/失败后自动清空(由新一次推进重置)。
const wfAdvancing = ref(false)
const wfExecId = ref<string | null>(null)
const wfTotalNodes = ref(0)
const wfDoneCount = ref(0)
const wfRunningNode = ref<string>('')
const wfResult = ref<'completed' | 'failed' | null>(null)
// SW-260618-21: 终态提示 timer 引用,卸载时清理防写已销毁 ref(对齐 Projects.vue _toastTimer 模式)
let _wfResultTimer: ReturnType<typeof setTimeout> | null = null
// B-41: 共享工作流进度推导。workflow store 已全局收集 liveEvents,此处按 execId
// 派生节点状态/进度,与 ProjectDetail 同源(store.workflowProgress),替代原本地 onEvent 逐事件累加。
const wfProgress = computed(() => store.workflowProgress(wfExecId.value))
const wfNodeStatuses = computed(() => wfProgress.value.nodeStatuses)
const wfDoneCount = computed(() => wfProgress.value.doneCount)
const wfTotalNodes = computed(() => wfProgress.value.totalNodes)
const wfRunningNode = computed(() => wfProgress.value.runningNode)
const wfDoneTotal = computed(() => wfTotalNodes.value)
const wfProgressHint = computed(() => wfResult.value !== null)
const wfCompletedHint = computed(() => wfResult.value === 'completed')
const wfFailedHint = computed(() => wfResult.value === 'failed')
// 工作流 DAG 结构展示
// 终态 → 瞬态提示:派生 result 置位时收 wfAdvancing + 3s 后清 wfResult(仅 UI hint,数据不改)
watch(() => wfProgress.value.result, (r) => {
if (!r) return
wfResult.value = r
wfAdvancing.value = false
if (_wfResultTimer) clearTimeout(_wfResultTimer)
_wfResultTimer = setTimeout(() => {
if (wfResult.value === r) wfResult.value = null
_wfResultTimer = null
}, 3000)
})
// 工作流 DAG 结构展示(执行记录读回,节点状态由共享派生)
const wfDagJson = ref('')
const wfNodeStatuses = ref<Record<string, NodeStatus>>({})
async function refreshWorkflowDag(execId: string) {
wfNodeStatuses.value = {}
try {
const record = await workflowApi.getExecution(execId)
if (record?.dag_json) wfDagJson.value = record.dag_json
} catch { /* 静默 */ }
}
const wfDoneTotal = computed(() => wfTotalNodes.value)
const taskId = computed(() => route.params.id as string)
@@ -568,9 +585,6 @@ async function handleWorkflowAdvance(target: string) {
if (!task.value || wfAdvancing.value || advancing.value) return
wfAdvancing.value = true
wfResult.value = null
wfRunningNode.value = ''
wfDoneCount.value = 0
wfTotalNodes.value = 0
try {
const execId = await workflowApi.run(
`task-advance:${target}`,
@@ -589,64 +603,8 @@ async function handleWorkflowAdvance(target: string) {
}
}
// B-41: 处理单个工作流事件(由 onEvent 回调 dispatch)。按 execution_id 过滤当前推进
function handleWorkflowEvent(payload: WorkflowEventPayload) {
if (!wfExecId.value || payload.execution_id !== wfExecId.value) return
const evt = payload.event
switch (evt?.type) {
case 'node_started': {
// 第一次 NodeStarted 时初始化 total(后端模板节点数,无法前端预知,
// 用「已启动 + 已完成」近似 total,显示已完成/已启动进度)
const node = String(evt.node_id ?? '')
wfRunningNode.value = node
wfNodeStatuses.value = { ...wfNodeStatuses.value, [node]: 'running' }
if (wfTotalNodes.value === 0) wfTotalNodes.value = 1
else wfTotalNodes.value = Math.max(wfTotalNodes.value, wfDoneCount.value + 1)
break
}
case 'node_completed': {
wfDoneCount.value += 1
if (wfRunningNode.value) {
wfNodeStatuses.value = { ...wfNodeStatuses.value, [wfRunningNode.value]: 'completed' }
}
wfRunningNode.value = ''
break
}
case 'workflow_completed': {
wfAdvancing.value = false
wfRunningNode.value = ''
wfResult.value = 'completed'
// 终态提示保留 3s 后清空(任务本体由 df-data-changed 刷新,提示不影响数据)
if (_wfResultTimer) clearTimeout(_wfResultTimer)
_wfResultTimer = setTimeout(() => {
if (wfResult.value === 'completed') wfResult.value = null
_wfResultTimer = null
}, 3000)
break
}
case 'workflow_failed':
case 'node_failed': {
if (evt?.node_id) {
const node = String(evt.node_id)
wfNodeStatuses.value = { ...wfNodeStatuses.value, [node]: 'failed' }
}
if (evt?.type === 'workflow_failed') {
wfAdvancing.value = false
wfRunningNode.value = ''
wfResult.value = 'failed'
if (_wfResultTimer) clearTimeout(_wfResultTimer)
_wfResultTimer = setTimeout(() => {
if (wfResult.value === 'failed') wfResult.value = null
_wfResultTimer = null
}, 3000)
}
// node_failed: 单节点失败,工作流后续会发 workflow_failed,此处不提前改终态
break
}
default:
break
}
}
// B-41: 进度状态已由共享 store 按 execId 派生(store.workflowProgress),不再本地逐事件累加
// 原有 handleWorkflowEvent 已移除 —— 事件仍由 workflow store 全局收集,TaskDetail 只读派生结果。
async function load() {
loading.value = true
@@ -689,8 +647,6 @@ watch(renderedDesc, () => { measureDescHeight() })
// 不享受 store 全局 df-data-changed 监听(该监听只刷 store.tasks 列表,不含本视图的当前 task 单体),
// 故此本地监听 entity=task/project 时重载当前 task。
let _unlistenDataChanged: (() => void) | null = null
// B-41: 工作流推进事件 unlistener(听 NodeStarted/NodeCompleted/WorkflowFailed 显示轻量进度)
let _unlistenWorkflowEvent: (() => void) | null = null
onMounted(async () => {
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat 共享),不阻塞 load
@@ -709,18 +665,17 @@ onMounted(async () => {
} catch (e) {
console.error('[TaskDetail] 启动 df-data-changed 监听失败:', e)
}
// B-41: 工作流推进事件监听(复用 workflowApi.onEvent, 与 workflow store 的全局监听并存无冲突 —
// 多个 listen 各自接收全量事件,本视图按 execution_id 过滤只处理自己发起的推进)
// B-41: 工作流进度由 workflow store 全局 liveEvents 推导(store.workflowProgress),
// 此处只需确保 store 事件监听已启动(幂等,共享单例;按 execution_id 过滤由派生层完成)。
try {
_unlistenWorkflowEvent = await workflowApi.onEvent(handleWorkflowEvent)
await store.startEventListener()
} catch (e) {
console.error('[TaskDetail] 启动 workflow-event 监听失败:', e)
console.error('[TaskDetail] 启动 workflow 事件监听失败:', e)
}
})
onBeforeUnmount(() => {
if (_unlistenDataChanged) { _unlistenDataChanged(); _unlistenDataChanged = null }
if (_unlistenWorkflowEvent) { _unlistenWorkflowEvent(); _unlistenWorkflowEvent = null }
// F-260805:移除子任务快捷菜单关闭监听
document.removeEventListener('click', closeChildMenu)
// SW-260618-21: 清终态提示 timer 防卸载后写已销毁 ref