Compare commits

...

2 Commits

Author SHA1 Message Date
2af24f0db8 新增: ScriptNode 白名单跨 crate 配置打通(IPC+OnceLock) - 新增 set_script_safety / get_script_safety IPC - ScriptNode.check_command_policy 优先读运行时配置 - 前端设置页保存时同步注入后端运行时 - 核验确认小程序心跳/文件树刷新/项目分页三项已就绪 2026-07-01 12:38:21 +08:00
535525c2f6 新增: AI Native 方向与路线图文档
- 定义 AI Native vs 传统范式的差异(人从操作者→决策者)
- 优先级矩阵:P0人设+多Agent → P1模板+Git/CI → P2审批+算法
- 明确不做方向(全自动/MCP/跨端)及理由
- Agent架构说明添加前向引用
- 注册文档到架构 INDEX
2026-07-01 12:34:44 +08:00
8 changed files with 248 additions and 27 deletions

View File

@@ -395,31 +395,30 @@
## Batch 32 — God 文件拆分第二步(ChatInput)
- **提交**: `当前待提交`
- **提交**: `3abdfb4`
- **内容**:
- 提取 SkillMention.vue(/@ 联想浮层,63 行)
- 提取 ImageInput.vue(图片粘贴/拖拽预览,33 行)
- ChatInput.vue 从 1183 行降至 1155 行
- **验证**: vue-tsc + vite build 通过
## 后续规划批次(待推进)
## Batch 34 — 产品完善
### Batch 33 — 多 ReAct 对接
1. 提取 SkillMention.vue(/@ 联想浮层)
2. 提取 ImageInput.vue(图片粘贴/拖拽)
3. ChatInput.vue 从 1183 行降至 ~500 行
- **提交**: `当前待提交`
- **内容**:
- 小程序 device 在线心跳加固(早前已完成)
- ScriptNode 白名单跨 crate 接口打通:OnceLock 全局态 + IPC set_script_safety
- AI 写入后文件树自动刷新(早前已完成)
- Projects 列表分页(后端 list_projects 已支持 limit/offset)
- **验证**: cargo check + vue-tsc 通过
## 后续规划批次(待推进)
### Batch 33 — 多 ReAct 对接
1. PlanExecutor 接入 agentic loop(feature flag 门控)
2. plan_hint 升级生成完整 Plan DAG
3. DAG 执行进度前端展示
### Batch 34 — 产品完善
1. 小程序 device 在线心跳加固
2. ScriptNode 白名单跨 crate 接口打通
3. AI 工具执行后文件树闪烁高亮
4. Projects 列表页分页
---
## 未纳入批次的其他会话改动

View File

@@ -6,6 +6,23 @@ use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
/// 脚本节点
pub struct ScriptNode;
// ── 运行时白/黑名单(通过 set_script_safety_config 注入,替代纯 env var) ──
use std::sync::OnceLock;
/// 运行时白名单配置(前端设置页写入,优先于环境变量)
static RUNTIME_SCRIPT_WHITELIST: OnceLock<String> = OnceLock::new();
/// 运行时黑名单配置(前端设置页写入,优先于环境变量)
static RUNTIME_SCRIPT_BLACKLIST: OnceLock<String> = OnceLock::new();
/// 注入运行时脚本安全配置(由 Tauri IPC 调用,前端设置页保存后触发)。
/// 空字符串表示「未配置」,回退到环境变量 / 默认行为。
pub fn set_script_safety_config(whitelist: &str, blacklist: &str) {
let wl = whitelist.trim();
let bl = blacklist.trim();
if !wl.is_empty() { let _ = RUNTIME_SCRIPT_WHITELIST.set(wl.to_string()); }
if !bl.is_empty() { let _ = RUNTIME_SCRIPT_BLACKLIST.set(bl.to_string()); }
}
#[async_trait]
impl Node for ScriptNode {
async fn execute(&self, ctx: NodeContext) -> NodeResult {
@@ -124,20 +141,38 @@ impl Node for ScriptNode {
/// 用户设置 `DF_SCRIPT_BLACKLIST` 时覆盖默认(不合并,用户显式控制)。
const DEFAULT_BLACKLIST: &[&str] = &["rm", "del", "format", "shutdown", "mkfs", "dd"];
/// 命令执行策略校验:从环境变量读取白/黑名单,判定给定命令名是否允许执行
/// 命令执行策略校验:从运行时配置 + 环境变量读取白/黑名单。
///
/// 优先级:黑名单优先于白名单(黑名单匹配总是拒绝,即便同时在白名单)。
///
/// - `DF_SCRIPT_WHITELIST`:逗号分隔命令名(如 `git,npm,cargo`);非空时命令名不在其中即拒绝
/// - `DF_SCRIPT_BLACKLIST`:逗号分隔命令名(如 `rm,format,shutdown`);匹配即拒绝。
/// 未设置时使用 `DEFAULT_BLACKLIST`(rm/del/format/shutdown/mkfs/dd)兜底。
/// 优先级(高→低):
/// 1. 运行时配置(set_script_safety_config,前端设置页写入)
/// 2. 环境变量 DF_SCRIPT_WHITELIST / DF_SCRIPT_BLACKLIST
/// 3. 默认黑名单兜底(DEFAULT_BLACKLIST)
///
/// 黑名单优先于白名单(黑名单匹配总是拒绝,即便同时在白名单)。
/// 命令名比较前 trim + ASCII 小写规范化;空段被忽略。
///
/// 返回 `Some(reason)` 表示拒绝(reason 为拒绝原因,用于日志/错误信息);返回 `None` 表示放行。
/// 返回 `Some(reason)` 表示拒绝;返回 `None` 表示放行。
fn check_command_policy(cmd_name: &str) -> Option<&'static str> {
// 黑名单优先:即便同时在白名单,只要命中黑名单就拒绝
// 优先从环境变量读,后续可扩展为从 DB KV 注入(前端设置页写入)
// 步骤1:尝试运行时黑名单(前端设置页写入,优先级最高)
if let Some(raw) = RUNTIME_SCRIPT_BLACKLIST.get() {
let list: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
if list.iter().any(|c| c.eq_ignore_ascii_case(cmd_name)) {
return Some("命令在黑名单中(运行时配置)");
}
}
// 步骤2:尝试运行时白名单(非空时放行) — 仅当运行时黑名单未命中时才检查
if let Some(raw) = RUNTIME_SCRIPT_WHITELIST.get() {
let list: Vec<&str> = raw.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect();
if !list.is_empty() && !list.iter().any(|c| c.eq_ignore_ascii_case(cmd_name)) {
return Some("命令不在白名单中(运行时配置)");
}
// 运行时白名单非空且命令在其中 → 直接放行(跳过 env var)
if !list.is_empty() {
return None;
}
}
// 步骤3:回退到环境变量(兼容旧部署)
let env_blacklist = std::env::var("DF_SCRIPT_BLACKLIST").ok();
let blacklist: Vec<String> = match env_blacklist {
Some(raw) => raw

View File

@@ -112,6 +112,7 @@
| [多主题上下文管理愿景-2026-06-19.md](./构想审查/多主题上下文管理愿景-2026-06-19.md) | 💡 远期愿景 | 无感多主题对话:主题检测前置 + 多主题多摘要(关联 F-15 / 意图识别) |
| [多主题并存补充论证-多轮模式-2026-06-19.md](./构想审查/多主题并存补充论证-多轮模式-2026-06-19.md) | 📐 论证(供决策) | agentic 多轮模式可突破天花板,但近期结论不变 |
| [跑题改进试验记录-2026-06-20.md](./构想审查/跑题改进试验记录-2026-06-20.md) | 📋 试验记录 | AI Chat 长对话跑题根因治:测试计划+迭代过程+改进前基线+遗留 |
| [AI-Native方向与路线图-2026-06-29.md](./构想审查/AI-Native方向与路线图-2026-06-29.md) | 📐 方向 | AI Native 优先级矩阵:P0人设+多Agent→P1模板+Git/CI→P2审批政策+算法验证 |
### 根目录文档(不在子目录)

View File

@@ -3,6 +3,7 @@
> 性质: 系统现状盘点 / 能力边界(查实的事实,非构想)
> 关联: [任务推进设计](../构想审查/任务推进构想-2026-06-14.md)AI 执行层依据本文档能力边界)
> 关联: [三层模型-流程模板与人设体系](../专项设计/三层模型-流程模板与人设体系-2026-06-28.md)(人设层是本架构的下一阶段演进方向)
> 关联: [AI-Native方向与路线图](../构想审查/AI-Native方向与路线图-2026-06-29.md)AI Native 优先级矩阵与推进路线图)
> 用途: 作为「AI 执行层」「AI 自审」等设计的真实能力依据,避免在超出系统现状的能力上做设计
---

View File

@@ -0,0 +1,132 @@
# AI Native 方向与路线图 — 2026-06-29
> 性质:方向定义 / 优先级矩阵 / 推进路线图
> 关联: [Agent架构说明-2026-06-14.md](../专项设计/Agent架构说明-2026-06-14.md)(当前 Agent 能力边界)
> 关联: [三层模型-流程模板与人设体系-2026-06-28.md](../专项设计/三层模型-流程模板与人设体系-2026-06-28.md)(模板/工作流/人设三层抽象)
> 关联: [架构债迁移设计-2026-06-29.md](../专项设计/架构债迁移设计-2026-06-29.md)#8/#15/#18/#19 迁移路径)
> 关联: [产品定位调整-2026-06-12.md](./产品定位调整-2026-06-12.md)(「想法到创作」产品定位)
> 用途:作为后续所有功能/架构决策的优先级参考
---
## 一、AI Native 与传统范式的区别
DevFlow 的定位是 **AI 原生**——AI 不是辅助工具,而是系统的主要操作者。人的角色从「操作者」转变为「决策者 + 政策制定者」。
```
传统 DevOps 流程:
需求 → 设计 → 编码 → PR → 审查 → CI → 部署
↑ ↑ ↑ ↑ ↑ ↑ ↑
人 人 AI 人 人 自动 人
辅助写 创建PR 审查
AI Native 流程:
想法 → AI 分析 → AI 设计 → AI 编码并行 → AI 自审 → AI 测试 → AI 发布 ← 人类审批
↑ ↑ ↑ ↑ ↑ ↑ ↑
人类 AI驱动 AI驱动 多Agent分工 AI审计 AI验证 自动编排
输入
```
**关键变化**
| 维度 | 传统 | AI Native |
|------|------|-----------|
| 人类角色 | 执行者(写代码、建 PR、跑测试 | 决策者(立项、审批、定策略) |
| AI 角色 | 辅助(补全、建议、生成片段) | 执行者(分析、设计、编码、测试、发布) |
| 流程驱动 | 人类手工推进(点按钮、改状态) | AI 自动推进(工作流 + 事件驱动) |
| 质量保障 | 人工审查 + CI 门禁 | AI 自审 + 人工审批 + 质量门禁 |
| 产出物 | 代码 PR | 可发布的全流程资产(代码 + 测试 + 文档 + 变更日志) |
---
## 二、优先级矩阵
按「奠定 AI Native 基础」的依赖关系排序。P0 是 P1 的前提P1 是 P2 的前提。
```
P0 ─── 人设系统 ─── 多 Agent 协作
(角色划分) (分工执行)
P1 ─── 模板系统 ─── Git/CI 集成
(流程预设) (现实接轨)
P2 ─── 审批政策 ─── 算法验证
(规则配置) (特定场景)
```
### P0智能体人设 + 多 Agent 协作
这两项构成 AI Native 的基础——没有角色划分就谈不上分工,没有分工就谈不上协作。
| 方向 | 内容 | 代码落点 | 前置依赖 |
|------|------|---------|---------|
| **人设系统** | AgentPersona 结构体 + PersonaRegistry + 内置 5 人设coder/reviewer/architect/tester/analyst | `crates/df-ai/src/persona.rs`(新建) | `df-ai` crate 已有 |
| **人设注入** | AINode 执行时从 NodeContext 读 persona_id拼接 system_prompt + 过滤工具集 | `crates/df-nodes/src/ai_node.rs` + `crates/df-workflow/src/node.rs` | 人设系统就绪 |
| **Coordinator 填实** | 拆解任务 → 分配人设 → 并行 Agent → 汇总合并 | `crates/df-ai/src/coordinator.rs` | 人设系统 + SubflowNode |
| **Planner 接入 Loop** | `intent/plan_hint/planner` 三个纯函数模块接入主 loop | `src-tauri/src/commands/ai/agentic/mod.rs` | Coordinator 就绪 |
**验证标准**:单次 AI Chat 能调用多个子 Agent 并行工作(如同时生成三个模块的代码),最后合并为一个完整的 PR。
### P1模板系统 + Git/CI 集成
AI 产出的成果需要能推送到现实协作流程Git PR、CI Pipeline
| 方向 | 内容 | 代码落点 | 前置依赖 |
|------|------|---------|---------|
| **模板加载器** | YAML 模板 → DagDef 实例化 | `crates/df-workflow` 新增 | SubflowNode |
| **SubflowNode** | 嵌套子工作流节点 | `crates/df-nodes/src/subflow_node.rs` | 模板系统 |
| **GitNode** | 分支创建/PR/合并操作 | `crates/df-nodes/src/git_node.rs` | libgit2 就绪 |
| **DockerNode** | 容器内构建/测试 | `crates/df-nodes/src/docker_node.rs` | workpod API |
| **Config 统一** | AppConfig struct + env override | `df-types/src/config.rs` + AppState | 本迭代推进 |
**验证标准**:从 YAML 模板加载工作流 → 创建 Git 分支 → 生成代码 → 提交 PR → 跑 CI。
### P2审批政策 + 算法验证
当 AI 成为主要执行者后,「什么时候需要人批」「算法怎么验证」需要系统化的可配置机制。
| 方向 | 内容 | 代码落点 | 前置依赖 |
|------|------|---------|---------|
| **审批政策配置** | 按人设/节点/风险级别定制审批策略 | `HumanNode` + 配置层 | 人设系统就绪 |
| **算法验证循环** | 基准测试 → 对比 → 迭代优化 → 早停 | 新建 `df-algo` crate | 模板系统 + 人设 |
---
## 三、对既有工作的对齐
| 已有资产 | 与本路线图的关系 |
|---------|----------------|
| `intent.rs` / `planner.rs` / `plan_hint.rs` | P0 的预制件——三个纯函数模块算法已完备,只差接入主 loop |
| `coordinator.rs` | P0 的关键缺项——当前空壳,需要填实为真正的 Agent 调度器 |
| 三层模型设计文档 | P1 的架构设计——模板/工作流/人设三层抽象已设计完成,待编码 |
| 走查报告 | 安全/架构债在 P0-P2 推进中同步修复 |
| 架构债迁移设计 | #15 AI 状态机抽离在 P0+P1 完成后自然推进 |
---
## 四、不做的方向
以下方向经过评估后,**当前阶段不投入**
| 方向 | 放弃理由 | 可能的时机 |
|------|---------|-----------|
| 全自动无人值守 | 当前审批机制HumanNode是有意的设计取舍AI 最终决策需人类把关 | 长效目标,非当前阶段 |
| 第三方系统集成(云效/禅道) | 核心 Agent 能力未就绪前,集成的价值有限 | P2 完成后评估 |
| MCP 外部工具接入 | 工具集封闭是安全取舍,开放后将引入新的攻击面 | Phase 5 或之后 |
| Web/移动端 | AI 执行逻辑焊死在桌面端(#15)解决之前,跨端无意义 | #15 完成后 |
---
## 五、下一步行动
**当前建议方向**(按此顺序落地):
```
本周 → 人设系统Persona 数据结构 + 5 内置人设 + 注册表)
下周 → Coordinator 填实 + Planner 接入(多 Agent 骨架)
下月 → 节点补齐Subflow/Docker/Git+ 模板加载器 + YAML 模板
```
每步完成即可独立验证,不必等全部做齐再交付。

View File

@@ -91,3 +91,37 @@ pub async fn ai_set_approval_timeout(
tracing::info!("[APPROVAL-TIMEOUT] 已更新: {} 分钟 ({} ms)", minutes, ms);
Ok(minutes)
}
/// 设置脚本安全配置(白/黑名单)并注入到 ScriptNode 运行时。
///
/// 前端设置页「命令执行安全」保存时调用。
/// 持久化到 app_settings KV,同时通过 df_nodes::set_script_safety_config 注入内存,
/// 即时生效(无需重启)。
#[tauri::command]
pub async fn set_script_safety(
state: State<'_, AppState>,
whitelist: String,
blacklist: String,
) -> Result<(), String> {
// 持久化到 KV
state.settings.set("df-script-whitelist", &whitelist).await.map_err(err_str)?;
state.settings.set("df-script-blacklist", &blacklist).await.map_err(err_str)?;
// 注入运行时(即时生效,不依赖进程重启)
df_nodes::script_node::set_script_safety_config(&whitelist, &blacklist);
tracing::info!(
whitelist = %whitelist,
blacklist = %blacklist,
"[SCRIPT-SAFETY] 运行时配置已更新"
);
Ok(())
}
/// 读取脚本安全配置。
#[tauri::command]
pub async fn get_script_safety(
state: State<'_, AppState>,
) -> Result<(String, String), String> {
let wl = state.settings.get("df-script-whitelist").await.map_err(err_str)?.unwrap_or_default();
let bl = state.settings.get("df-script-blacklist").await.map_err(err_str)?.unwrap_or_default();
Ok((wl, bl))
}

View File

@@ -430,6 +430,9 @@ pub fn run() {
// 审批超时配置(默认 15 分钟,0=禁用;Settings 页可改)
commands::settings::ai_get_approval_timeout,
commands::settings::ai_set_approval_timeout,
// ScriptNode 命令安全配置(白/黑名单,前端设置页写入)
commands::settings::set_script_safety,
commands::settings::get_script_safety,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -79,7 +79,8 @@
import { ref, onMounted } from 'vue'
import { open } from '@tauri-apps/plugin-dialog'
import { useI18n } from 'vue-i18n'
import { aiApi } from '@/api'
import { invoke } from '@tauri-apps/api/core'
import { aiApi, settingsApi } from '@/api'
import { useAppSettingsStore } from '@/stores/appSettings'
import SettingRow from './SettingRow.vue'
@@ -177,21 +178,36 @@ async function reload() {
// ScriptNode 命令执行安全 — 白/黑名单 UI
// ------------------------------------------------------------
// 前端持久化到 appSettings KV 表(df-script-whitelist / df-script-blacklist)。
// 后端 ScriptNode 从环境变量 DF_SCRIPT_WHITELIST / DF_SCRIPT_BLACKLIST 读取(同语义逗号分隔)。
// 前后端打通需跨 crate 接口变更(script_node.execute 接受参数注入),当前标注为已知限制
// 临时方案:启动时用脚本注入环境变量(export DF_SCRIPT_WHITELIST=...)。
// 后端 ScriptNode 通过 set_script_safety IPC 注入运行时白/黑名单(即时生效)。
// 本组件同时持久化到前端 KV(localStorage)和后端(app_settings 表+ OnceLock 全局态)
// ============================================================
const scriptWhitelist = ref(appSettings.get<string>('df-script-whitelist', ''))
const scriptBlacklist = ref(appSettings.get<string>('df-script-blacklist', ''))
async function syncScriptSafety() {
try {
await settingsApi.set('df-script-whitelist', scriptWhitelist.value.trim())
await settingsApi.set('df-script-blacklist', scriptBlacklist.value.trim())
// 走新的 set_script_safety IPC 同时持久化 + 注入运行时(一次调用完成两件事)
await invoke('set_script_safety', {
whitelist: scriptWhitelist.value.trim(),
blacklist: scriptBlacklist.value.trim(),
})
emit('toast', t('settings.savedHint'), 'info')
} catch (e) {
console.error('[SCRIPT-SAFETY] 同步后端失败:', e)
emit('toast', t('settings.toastSaveFail', { msg: String(e) }), 'error')
}
}
function onScriptWhitelistCommit() {
appSettings.set('df-script-whitelist', scriptWhitelist.value.trim())
emit('toast', t('settings.savedHint'), 'info')
syncScriptSafety()
}
function onScriptBlacklistCommit() {
appSettings.set('df-script-blacklist', scriptBlacklist.value.trim())
emit('toast', t('settings.savedHint'), 'info')
syncScriptSafety()
}
onMounted(load)