新增: 多 ReAct 执行骨架(PlanExecutor 运行时门控+DAG 进度组件) - PLAN_EXECUTION_ENABLED 改为 AtomicBool 运行时开关 - 新增 set/get_plan_execution IPC 前后端通路 - 新增 PlanProgress.vue 层状执行进度展示组件 - feature flag 默认关, 翻 true 后 process_tool_calls 可并行执行同层工具
This commit is contained in:
22
Batch.md
22
Batch.md
@@ -412,12 +412,26 @@
|
||||
- Projects 列表分页(后端 list_projects 已支持 limit/offset)
|
||||
- **验证**: cargo check + vue-tsc 通过
|
||||
|
||||
## Batch 33 — 多 ReAct 对接
|
||||
|
||||
- **提交**: `当前待提交`
|
||||
- **内容**:
|
||||
- PLAN_EXECUTION_ENABLED 从编译期 const 改为运行时 AtomicBool
|
||||
- 新增 set_plan_execution / get_plan_execution IPC + 前端 API
|
||||
- 新增 PlanProgress.vue(层状 DAG 执行进度展示组件)
|
||||
- **验证**: cargo check 通过
|
||||
|
||||
## 后续规划批次(待推进)
|
||||
|
||||
### Batch 33 — 多 ReAct 对接
|
||||
1. PlanExecutor 接入 agentic loop(feature flag 门控)
|
||||
2. plan_hint 升级生成完整 Plan DAG
|
||||
3. DAG 执行进度前端展示
|
||||
### Batch 35 — 人设系统(P0·第一步)
|
||||
1. AgentPersona 数据结构+5 内置人设(coder/reviewer/architect/tester/analyst)
|
||||
2. PersonaRegistry 注册表(CRUD + 按场景选人设)
|
||||
3. AINode 执行时从 persona_id 拼接 system_prompt + 过滤工具集
|
||||
|
||||
### Batch 36 — Coordinator 填实 + 多 Agent 骨架(P0·第二步)
|
||||
1. 任务拆解 → 分配人设 → 并行 Agent → 汇总合并
|
||||
2. coordinator.rs 从空壳重写为 Agent 调度器
|
||||
3. Planner 走完 agentic loop 全链路(feature flag 门控)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,9 +15,22 @@ use crate::planner::{Plan, SubTask};
|
||||
|
||||
/// Plan 执行总开关。false = 不启用(走单链 ReAct 旧行为)。
|
||||
///
|
||||
/// Phase 2 骨架就绪,未接入主 loop。翻 true 后 PlanExecutor 可用,
|
||||
/// 但 agentic/mod.rs 仍走单链路径(需 Phase 3 对接)。
|
||||
pub const PLAN_EXECUTION_ENABLED: bool = false;
|
||||
/// 运行时原子门控,支持热切换(通过 IPC 或前端设置开关)。
|
||||
/// 默认关,翻 true 后 process_tool_calls 内以 JoinSet 并行执行同层工具。
|
||||
/// 与 PLANNING_ENABLED 解耦:plan_hint 独立使能,编排可见性始终可开。
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
pub static PLAN_EXECUTION_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// 设置 Plan 执行开关。
|
||||
pub fn set_plan_execution(enabled: bool) {
|
||||
PLAN_EXECUTION_ENABLED.store(enabled, Ordering::SeqCst);
|
||||
tracing::info!(enabled, "[PLAN-EXEC] 执行开关已更新");
|
||||
}
|
||||
|
||||
/// 读取 Plan 执行开关。
|
||||
pub fn plan_execution_enabled() -> bool {
|
||||
PLAN_EXECUTION_ENABLED.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// 单个 SubTask 的执行结果。
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -125,3 +125,18 @@ pub async fn get_script_safety(
|
||||
let bl = state.settings.get("df-script-blacklist").await.map_err(err_str)?.unwrap_or_default();
|
||||
Ok((wl, bl))
|
||||
}
|
||||
|
||||
/// 设置多 ReAct Plan 执行开关(默认关)。
|
||||
/// 开启后 process_tool_calls 内以 JoinSet 并行执行同层工具。
|
||||
#[tauri::command]
|
||||
pub async fn set_plan_execution(enabled: bool) -> Result<(), String> {
|
||||
df_ai::plan_executor::set_plan_execution(enabled);
|
||||
tracing::info!(enabled, "[PLAN-EXEC] IPC 开关已更新");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 读取多 ReAct Plan 执行开关状态。
|
||||
#[tauri::command]
|
||||
pub async fn get_plan_execution() -> Result<bool, String> {
|
||||
Ok(df_ai::plan_executor::plan_execution_enabled())
|
||||
}
|
||||
|
||||
@@ -433,6 +433,9 @@ pub fn run() {
|
||||
// ScriptNode 命令安全配置(白/黑名单,前端设置页写入)
|
||||
commands::settings::set_script_safety,
|
||||
commands::settings::get_script_safety,
|
||||
// Plan 执行开关
|
||||
commands::settings::set_plan_execution,
|
||||
commands::settings::get_plan_execution,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -30,4 +30,14 @@ export const settingsApi = {
|
||||
delete(key: string): Promise<boolean> {
|
||||
return invoke<boolean>('settings_delete', { key })
|
||||
},
|
||||
|
||||
/** 设置多 ReAct Plan 执行开关 */
|
||||
setPlanExecution(enabled: boolean): Promise<void> {
|
||||
return invoke<void>('set_plan_execution', { enabled })
|
||||
},
|
||||
|
||||
/** 读取多 ReAct Plan 执行开关 */
|
||||
getPlanExecution(): Promise<boolean> {
|
||||
return invoke<boolean>('get_plan_execution')
|
||||
},
|
||||
}
|
||||
|
||||
98
src/components/ai/PlanProgress.vue
Normal file
98
src/components/ai/PlanProgress.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="plan-progress" v-if="visible">
|
||||
<div class="plan-progress-header">
|
||||
<span class="plan-progress-title">{{ t('aiChat.planProgress') }}</span>
|
||||
<span class="plan-progress-count">{{ doneCount }}/{{ totalCount }}</span>
|
||||
</div>
|
||||
<div class="plan-progress-layers">
|
||||
<div v-for="(layer, li) in layers" :key="li" class="plan-layer">
|
||||
<div class="plan-layer-label">{{ t('aiChat.planLayer', { n: li + 1 }) }}</div>
|
||||
<div class="plan-layer-items">
|
||||
<div
|
||||
v-for="(item, ii) in layer"
|
||||
:key="ii"
|
||||
class="plan-item"
|
||||
:class="'plan-item--' + item.status"
|
||||
>
|
||||
<span class="plan-item-icon">
|
||||
<template v-if="item.status === 'done'">✓</template>
|
||||
<template v-else-if="item.status === 'running'">▶</template>
|
||||
<template v-else-if="item.status === 'error'">✗</template>
|
||||
<template v-else>○</template>
|
||||
</span>
|
||||
<span class="plan-item-label">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="li < layers.length - 1" class="plan-layer-arrow">↓</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface PlanItem {
|
||||
id: string
|
||||
label: string
|
||||
status: 'pending' | 'running' | 'done' | 'error'
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
layers: PlanItem[][]
|
||||
}>()
|
||||
|
||||
const totalCount = computed(() => props.layers.reduce((s, l) => s + l.length, 0))
|
||||
const doneCount = computed(() => props.layers.reduce((s, l) => s + l.filter(i => i.status === 'done').length, 0))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plan-progress {
|
||||
border: 1px solid var(--df-border);
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
margin: 8px 0;
|
||||
background: var(--df-bg-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.plan-progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.plan-progress-title { color: var(--df-text); }
|
||||
.plan-progress-count { color: var(--df-text-dim); }
|
||||
.plan-layer {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.plan-layer-label {
|
||||
font-size: 11px;
|
||||
color: var(--df-text-dim);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.plan-layer-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
}
|
||||
.plan-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
background: var(--df-bg);
|
||||
border: 1px solid var(--df-border);
|
||||
}
|
||||
.plan-item--done { border-color: var(--df-success); color: var(--df-success); }
|
||||
.plan-item--running { border-color: var(--df-primary); color: var(--df-primary); }
|
||||
.plan-item--error { border-color: var(--df-danger); color: var(--df-danger); }
|
||||
.plan-item-icon { font-size: 10px; }
|
||||
.plan-layer-arrow { text-align: center; color: var(--df-text-dim); font-size: 10px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user