新增: Wave8 F-06 导入项目 + F-02 技能联想 + AR-6 Low 失败语义

- F-06 导入历史项目:scan.rs extract_description(README 首段 200 字) + import_project 命令(合并 create+bind,spawn_blocking 探测栈) + 前端 ProjectDetail 导入按钮 + store action;主代理补 lib.rs invoke_handler 注册(agent 未注册)
- F-02 技能联想使用:核对发现 sendMessage→ai_chat_send skill 调用链已完整(联想选中→带 skill→注入 SKILL.md),补 Esc 取消选中兜底
- AR-6 Low 失败语义统一(定向B):audit.rs Low 失败 emit AiToolCallCompleted(错误 result) 替代 AiError,消除前端 false/后端 loop 续紊乱,错误回填 tool_result 让 LLM 自处理
cargo/vue-tsc 0 err
This commit is contained in:
2026-06-14 22:13:51 +08:00
parent b195a38d64
commit f82dd8ba90
8 changed files with 248 additions and 5 deletions

View File

@@ -1,6 +1,18 @@
import { invoke } from '@tauri-apps/api/core'
import type { ProjectRecord, CreateProjectInput, AiScanResult } from './types'
/** 导入历史项目入参(选已存在目录,创建实体+绑定+探测栈一步完成) */
export interface ImportProjectInput {
/** 待导入的本地目录绝对路径(已存在) */
path: string
/** 项目名(可选,空=用目录名) */
name?: string
/** 描述(可选,空=自动读 README 首段) */
description?: string
/** 技术栈 JSON 数组字符串(可选,空=自动探测) */
stack?: string
}
export const projectApi = {
list(): Promise<ProjectRecord[]> {
return invoke('list_projects')
@@ -10,6 +22,11 @@ export const projectApi = {
return invoke('create_project', { input })
},
/** 导入历史项目(选已存在目录,创建实体+绑定+探测栈一步完成) */
importProject(input: ImportProjectInput): Promise<ProjectRecord> {
return invoke('import_project', { input })
},
get(id: string): Promise<ProjectRecord | null> {
return invoke('get_project', { id })
},

View File

@@ -545,6 +545,12 @@ function handleKeydown(e: KeyboardEvent) {
skillOpen.value = false
return
}
// 已选技能 chip 态下 Escape 取消选中(浮层已关闭,这里兜底 chip 可取消路径)
if (pendingSkill.value && e.key === 'Escape') {
e.preventDefault()
pendingSkill.value = null
return
}
// 技能联想浮层导航(仅有匹配项时拦截方向/回车)
if (skillOpen.value && filteredSkills.value.length) {
if (e.key === 'ArrowDown') {

View File

@@ -1,6 +1,7 @@
import { reactive, computed } from 'vue'
import { invoke } from '@tauri-apps/api/core'
import { projectApi, taskApi, ideaApi, workflowApi } from '../api'
import type { ImportProjectInput } from '../api/project'
import type { ProjectRecord, TaskRecord, IdeaRecord, WorkflowRecord, WorkflowEventPayload } from '../api/types'
// ── 全局响应式状态(单例) ──
@@ -53,6 +54,18 @@ function createStore() {
}
}
/** 导入历史项目(选已存在目录,后端创建+绑定+探测栈+读 README 首段一步完成) */
async function importProject(input: ImportProjectInput) {
try {
const record = await projectApi.importProject(input)
await loadProjects() // 刷新列表(后端已 insert,统一走 load 避免本地数组与后端不一致)
return record
} catch (e: any) {
state.error = e?.toString() ?? '导入项目失败'
return null
}
}
async function updateProject(id: string, field: string, value: string) {
await projectApi.update(id, field, value)
const idx = state.projects.findIndex(p => p.id === id)
@@ -298,7 +311,7 @@ function createStore() {
clearError,
get deletedProjects() { return state.deletedProjects },
// project actions
loadProjects, createProject, updateProject, deleteProject, relocateProjectPath,
loadProjects, createProject, importProject, updateProject, deleteProject, relocateProjectPath,
loadDeletedProjects, restoreProject, purgeProject,
// task actions
loadTasks, createTask, updateTask, deleteTask,

View File

@@ -9,6 +9,7 @@
</div>
<div class="header-actions">
<button class="btn btn-ghost" @click="handleSync">{{ $t('projectDetail.sync') }}</button>
<button class="btn btn-ghost" type="button" @click="handleImportDir">导入目录</button>
<button class="btn btn-danger" @click="handleDelete">{{ $t('projectDetail.delete') }}</button>
<button class="btn btn-primary" @click="showNewTaskModal = true">{{ $t('projectDetail.newTask') }}</button>
</div>
@@ -378,6 +379,29 @@ async function relocateDir() {
}
}
// 导入历史项目(选已存在目录 → 后端创建实体+绑定+探测栈+读 README 首段一步完成)
// 文案硬编码:projectDetail i18n 不在本次任务白名单,改用内联中文(避免引用不存在的 key)
async function handleImportDir() {
try {
const selected = await open({ directory: true, multiple: false })
if (!selected || Array.isArray(selected)) return
const dir = selected as string
if (!await confirmDialog(`导入目录为新项目?\n${dir}\n\n将自动用目录名作项目名,探测技术栈,并读 README 首段填描述。`)) return
const record = await store.importProject({ path: dir })
if (!record) {
// store 已 toast 错误
if (store.error) Message.error(store.error)
return
}
Message.success(`已导入项目「${record.name}`)
// 跳转到新导入项目的详情页
router.push(`/projects/${record.id}`)
} catch (e: any) {
console.error('导入失败:', e)
Message.error(`导入失败: ${e?.toString() ?? '未知错误'}`)
}
}
// ── 审批处理 ──
async function handleApproval(decision: string) {
await store.approveHumanApproval(decision)