feat: Phase 2 - Orchestrator workflow node + tool-level human approval
2.1 Orchestrator in workflow:
- New run_orchestrator_node() in workflow_integration.py loads agents from DB,
supports route/sequential/debate/pipeline modes
- New 'orchestrator' node type in workflow_engine.py execute_node dispatch
2.2 Tool-level human approval:
- AgentToolConfig extended with require_approval, approval_timeout_ms,
approval_default fields
- New ApprovalManager (approval_manager.py) with asyncio.Event-based
create/wait_for_decision/resolve pattern
- AgentRuntime run() and run_stream() intercept tool execution,
wait for approval decision before executing
- New POST /api/v1/approval/{id}/resolve REST endpoint
- Frontend: approval_required SSE event handling, approval dialog UI
with approve/deny/skip buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,22 @@
|
||||
<el-button @click="showOrchestrateEditor = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 工具审批对话框 -->
|
||||
<el-dialog v-model="showApprovalDialog" title="工具执行审批" width="480px" :close-on-click-modal="false">
|
||||
<div style="margin-bottom: 12px">
|
||||
<el-tag type="warning" size="large">{{ approvalToolName }}</el-tag>
|
||||
<span style="margin-left: 8px; color: #666">需要人工审批才能执行</span>
|
||||
</div>
|
||||
<div v-if="Object.keys(approvalArgs).length > 0" style="background: #f5f7fa; padding: 12px; border-radius: 6px; max-height: 200px; overflow-y: auto">
|
||||
<pre style="margin: 0; font-size: 13px; white-space: pre-wrap; word-break: break-all">{{ JSON.stringify(approvalArgs, null, 2) }}</pre>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="resolveApproval('denied')">拒绝</el-button>
|
||||
<el-button @click="resolveApproval('skip')">跳过</el-button>
|
||||
<el-button type="primary" @click="resolveApproval('approved')">批准执行</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</MainLayout>
|
||||
</template>
|
||||
|
||||
@@ -249,6 +265,18 @@ interface ChatState {
|
||||
orchestrateAgents: OrchestrateAgentForm[]
|
||||
}
|
||||
|
||||
async function resolveApproval(decision: string) {
|
||||
try {
|
||||
await api.post(`/api/v1/approval/${approvalId.value}/resolve`, { decision })
|
||||
} catch {
|
||||
// 审批已超时或已处理,忽略
|
||||
}
|
||||
showApprovalDialog.value = false
|
||||
approvalId.value = ''
|
||||
approvalToolName.value = ''
|
||||
approvalArgs.value = {}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
const state: ChatState = {
|
||||
@@ -285,6 +313,11 @@ const messages = ref<Record<string, ChatMessage[]>>({})
|
||||
const inputMessage = ref('')
|
||||
const loading = ref(false)
|
||||
const streamingActive = ref(false)
|
||||
// 工具审批状态
|
||||
const showApprovalDialog = ref(false)
|
||||
const approvalId = ref('')
|
||||
const approvalToolName = ref('')
|
||||
const approvalArgs = ref<Record<string, any>>({})
|
||||
const messagesRef = ref<HTMLElement | null>(null)
|
||||
const sessionId = ref<Record<string, string>>({})
|
||||
const agent = ref<Agent | null>(null)
|
||||
@@ -486,6 +519,11 @@ async function sendMessage() {
|
||||
tool_name: data.name,
|
||||
tool_result: data.result,
|
||||
})
|
||||
} else if (eventType === 'approval_required') {
|
||||
approvalId.value = data.approval_id || ''
|
||||
approvalToolName.value = data.tool_name || ''
|
||||
approvalArgs.value = data.args || {}
|
||||
showApprovalDialog.value = true
|
||||
} else if (eventType === 'final') {
|
||||
currentMsg.content = data.content || ''
|
||||
currentMsg.iterations = data.iterations_used || 0
|
||||
|
||||
Reference in New Issue
Block a user