修复: 安全加固+DRY 收敛+文档同步+测试补齐

安全:
- ScriptNode 默认黑名单兜底(rm/del/format/shutdown/mkfs/dd)
- bind_directory 分段 .. 检测替代 contains 子串(对齐 tool_registry)
- ai_providers 白名单移除 api_key(防 update_field 旁路写明文)

DRY:
- useAiEvents 抽 cleanupTerminatedConversation 统一三分支收尾
- 新增 useStoreAction 工具,4 个 store 替换 38 处 try/catch 样板

文档:
- df-core → df-types 批量替换(ARCHITECTURE/PROGRESS/SQLite-CRUD)
- INDEX 补齐 9 漏列文档(单对话并行多轮/跑题试验/工程系统设计等)
- Agent架构说明 死链修复(../构想审查/)
- AI对话引擎工具清单改为数量+按风险分组(不再用固定数字)
- ARCH 状态标签 设计阶段 → Phase 2 验证

测试:
- df-relay 新增 registry_test: ConnRegistry 路由 + RelayState + 16 项单测
This commit is contained in:
2026-06-29 21:57:07 +08:00
parent dfed588b54
commit ff3f153d45
17 changed files with 761 additions and 563 deletions

View File

@@ -1,6 +1,7 @@
import { reactive, computed } from 'vue'
import { knowledgeApi } from '@/api'
import { t } from '@/i18n/i18n-helpers'
import { runWithCatch, runWithCatchGuarded } from '@/composables/useStoreAction'
import type {
KnowledgeRecord,
KnowledgeDetailPayload,
@@ -140,73 +141,58 @@ export function useKnowledgeStore() {
}
async function create(input: CreateKnowledgeInput) {
try {
const record = await knowledgeApi.create(input)
// 手动录入默认 candidate,刷新收件箱。
// create 已成功返回 record,刷新失败不应污染成功语义 —— 直接走原始 API + 非阻塞 console.warn,
// 不经 loadCandidates(后者会写 state.error,污染 create 成功语义)。
const record = await runWithCatch(state, t('knowledge.err.createFailed'), async () => {
const r = await knowledgeApi.create(input)
// 手动录入默认 candidate,刷新收件箱。刷新失败不污染 create 成功语义(独立 console.warn)。
try {
state.candidates = await knowledgeApi.listCandidates()
} catch (e: any) {
console.warn('create 后刷新收件箱失败(非阻塞)', e?.toString?.() ?? e)
} catch (e: unknown) {
console.warn('create 后刷新收件箱失败(非阻塞)', e)
}
return record
} catch (e: any) {
state.error = e?.toString() ?? t('knowledge.err.createFailed')
return null
}
return r
})
return record ?? null
}
async function updateStatus(id: string, status: string) {
try {
await runWithCatch(state, t('knowledge.err.updateStatusFailed'), async () => {
await knowledgeApi.updateStatus(id, status)
// 从 items 和 candidates 中同步移除
state.items = state.items.filter(k => k.id !== id)
state.candidates = state.candidates.filter(k => k.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? t('knowledge.err.updateStatusFailed')
}
})
}
async function archive(id: string) {
try {
await runWithCatch(state, t('knowledge.err.archiveFailed'), async () => {
await knowledgeApi.archive(id)
state.items = state.items.filter(k => k.id !== id)
state.candidates = state.candidates.filter(k => k.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? t('knowledge.err.archiveFailed')
}
})
}
// ── 配置 ──
async function loadConfig() {
const seq = ++_configReqSeq
try {
await runWithCatchGuarded(state, t('knowledge.err.loadConfigFailed'), () => seq === _configReqSeq, async () => {
const result = await knowledgeApi.getConfig()
if (seq !== _configReqSeq) return // 旧响应丢弃,只取最新
if (seq !== _configReqSeq) return // 旧响应丢弃
state.config = result
} catch (e: any) {
if (seq !== _configReqSeq) return
state.error = e?.toString() ?? t('knowledge.err.loadConfigFailed')
}
})
}
async function saveConfig(config: KnowledgeConfig) {
try {
await runWithCatch(state, t('knowledge.err.saveConfigFailed'), async () => {
await knowledgeApi.saveConfig(config)
state.config = config
} catch (e: any) {
state.error = e?.toString() ?? t('knowledge.err.saveConfigFailed')
}
})
}
async function extractNow() {
try {
await runWithCatch(state, t('knowledge.err.extractFailed'), async () => {
await knowledgeApi.extractNow()
await loadCandidates()
} catch (e: any) {
state.error = e?.toString() ?? t('knowledge.err.extractFailed')
}
})
}
// ── 生命线:详情 / 编辑 / 事件查询(按需调用,不入全局 state) ──

View File

@@ -1,6 +1,7 @@
import { ideaApi } from '@/api'
import type { IdeaQuery } from '@/api/types'
import { t } from '@/i18n/i18n-helpers'
import { runWithCatch } from '@/composables/useStoreAction'
import { state } from './state'
/** 灵感 CRUD 子 store(共享全局 state) */
@@ -13,22 +14,18 @@ export function createIdeasStore() {
* - 不传 → 全量(created_at DESC,等价旧行为)。
*/
async function loadIdeas(query?: IdeaQuery) {
try {
await runWithCatch(state, t('ideas.err.loadFailed'), async () => {
state.ideas = await ideaApi.list(query)
} catch (e: any) {
state.error = e?.toString() ?? t('ideas.err.loadFailed')
}
})
}
async function createIdea(input: { title: string; description?: string; priority?: number; tags?: string; source?: string }) {
try {
const record = await ideaApi.create(input)
state.ideas.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? t('ideas.err.createFailed')
return null
}
const record = await runWithCatch(state, t('ideas.err.createFailed'), async () => {
const r = await ideaApi.create(input)
state.ideas.push(r)
return r
})
return record ?? null
}
async function updateIdea(id: string, field: string, value: string) {
@@ -40,12 +37,10 @@ export function createIdeasStore() {
}
async function deleteIdea(id: string) {
try {
await runWithCatch(state, t('ideas.err.deleteFailed'), async () => {
await ideaApi.delete(id)
state.ideas = state.ideas.filter(i => i.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? t('ideas.err.deleteFailed')
}
})
}
async function evaluateIdea(id: string) {
@@ -62,12 +57,10 @@ export function createIdeasStore() {
}
async function relateIdeas(subjectId: string, targetIds: string[]) {
try {
await runWithCatch(state, t('ideas.err.relateFailed'), async () => {
await ideaApi.relateIdeas(subjectId, targetIds)
await loadIdeas() // 后端已原子更新全部受影响灵感,刷新列表保持 store 一致
} catch (e: any) {
state.error = e?.toString() ?? t('ideas.err.relateFailed')
}
})
}
return {

View File

@@ -1,6 +1,7 @@
import { projectApi } from '@/api'
import type { ImportProjectInput, ProjectQuery } from '@/api/project'
import { t } from '@/i18n/i18n-helpers'
import { runWithCatch } from '@/composables/useStoreAction'
import { state, clearError } from './state'
/** 项目 CRUD 子 store(共享全局 state) */
@@ -12,36 +13,29 @@ export function createProjectsStore() {
async function loadProjects(query?: ProjectQuery) {
state.loading = true
state.error = null
try {
await runWithCatch(state, t('projects.err.loadFailed'), async () => {
state.projects = await projectApi.list(query)
} catch (e: any) {
state.error = e?.toString() ?? t('projects.err.loadFailed')
} finally {
state.loading = false
}
})
state.loading = false
}
async function createProject(name: string, description = '', ideaId?: string, path?: string, stack?: string) {
try {
const record = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
state.projects.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? t('projects.err.createFailed')
return null
}
const record = await runWithCatch(state, t('projects.err.createFailed'), async () => {
const r = await projectApi.create({ name, description, idea_id: ideaId, path, stack })
state.projects.push(r)
return r
})
return record ?? null
}
/** 导入历史项目(选已存在目录,后端创建+绑定+探测栈+读 README 首段一步完成) */
async function importProject(input: ImportProjectInput) {
try {
const record = await projectApi.importProject(input)
const record = await runWithCatch(state, t('projects.err.importFailed'), async () => {
const r = await projectApi.importProject(input)
await loadProjects() // 刷新列表(后端已 insert,统一走 load 避免本地数组与后端不一致)
return record
} catch (e: any) {
state.error = e?.toString() ?? t('projects.err.importFailed')
return null
}
return r
})
return record ?? null
}
async function updateProject(id: string, field: string, value: string) {
@@ -61,20 +55,16 @@ export function createProjectsStore() {
}
async function deleteProject(id: string) {
try {
await runWithCatch(state, t('projects.err.deleteFailed'), async () => {
await projectApi.delete(id) // 软删 → 回收站(可恢复)
state.projects = state.projects.filter(p => p.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? t('projects.err.deleteFailed')
}
})
}
async function loadDeletedProjects() {
try {
await runWithCatch(state, t('projects.err.loadTrashFailed'), async () => {
state.deletedProjects = await projectApi.listDeleted()
} catch (e: any) {
state.error = e?.toString() ?? t('projects.err.loadTrashFailed')
}
})
}
async function restoreProject(id: string) {

View File

@@ -1,6 +1,7 @@
import { taskApi } from '@/api'
import type { TaskQuery } from '@/api/types'
import { t } from '@/i18n/i18n-helpers'
import { runWithCatch } from '@/composables/useStoreAction'
import { state } from './state'
/** 任务 CRUD 子 store(共享全局 state) */
@@ -16,43 +17,35 @@ export function createTasksStore() {
* status/keyword 下沉后端 WHERE(P1/P2),取代 Tasks.vue 旧的前端内存 filter。
*/
async function loadTasks(queryOrProjectId?: TaskQuery | string) {
try {
await runWithCatch(state, t('tasks.err.loadFailed'), async () => {
state.tasks = await taskApi.list(queryOrProjectId)
} catch (e: any) {
state.error = e?.toString() ?? t('tasks.err.loadFailed')
}
})
}
async function createTask(input: { project_id: string; title: string; description?: string; priority?: number; branch_name?: string; assignee?: string; idea_id?: string }) {
try {
const record = await taskApi.create(input)
state.tasks.push(record)
return record
} catch (e: any) {
state.error = e?.toString() ?? t('tasks.err.createFailed')
return null
}
const record = await runWithCatch(state, t('tasks.err.createFailed'), async () => {
const r = await taskApi.create(input)
state.tasks.push(r)
return r
})
return record ?? null
}
async function updateTask(id: string, field: string, value: string) {
try {
await runWithCatch(state, t('tasks.err.updateFailed'), async () => {
await taskApi.update(id, field, value)
const idx = state.tasks.findIndex(t => t.id === id)
if (idx >= 0) {
(state.tasks[idx] as any)[field] = value
}
} catch (e: any) {
state.error = e?.toString() ?? t('tasks.err.updateFailed')
}
})
}
async function deleteTask(id: string) {
try {
await runWithCatch(state, t('tasks.err.deleteFailed'), async () => {
await taskApi.delete(id)
state.tasks = state.tasks.filter(t => t.id !== id)
} catch (e: any) {
state.error = e?.toString() ?? t('tasks.err.deleteFailed')
}
})
}
return {