修复: AI工具调用健壮性+审批卡片可读化+任务工具补全

- AC1/AC2 anthropic_compat tool_use_id None/空时跳过或占位(防GLM端500卡死)
- AR-3 审批卡片 id→项目名回显(前端白名单特化) + 后端查不到友好提示
- FR-D6 补 delete_task/update_task 工具(防误用 delete_project 清理孤儿任务)
- FR-D7 抽 bind_dir_to_project 消除 create_project/bind_directory 重复
- FR-D8 create_idea schema 补 priority 契约对齐
- FR-S4 SKILL.md 注入加头尾隔离标注防 prompt injection 混淆
- FR-R4 complete() 加 60s 单请求超时(不影响 stream)
This commit is contained in:
2026-06-14 22:48:04 +08:00
parent 49ac0601e1
commit 36d68ddb26
8 changed files with 207 additions and 70 deletions

View File

@@ -32,7 +32,7 @@
<div v-if="toolArgsEntries(tc.args).length" class="ai-tool-approval-args">
<div v-for="arg in toolArgsEntries(tc.args)" :key="arg.key" class="ai-tool-arg">
<span class="ai-tool-arg-key">{{ arg.label }}</span>
<span class="ai-tool-arg-val">{{ formatArgValue(arg.val) }}</span>
<span class="ai-tool-arg-val">{{ displayArgValue(arg) }}</span>
</div>
</div>
<div v-if="tc.reason" class="ai-tool-approval-reason"> {{ tc.reason }}</div>
@@ -254,8 +254,10 @@ function formatToolResult(tc: AiToolCallInfo): string {
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useProjectStore } from '../stores/project'
const { t } = useI18n()
const projectStore = useProjectStore()
const props = defineProps<{
/** 单条工具调用数据 */
@@ -278,6 +280,46 @@ const emit = defineEmits<{
/** 一次解析结果供模板多次读(模板引用 6 次,computed 避免每次 patch 重 parse) */
const parsed = computed(() => parseResult(props.tc.result))
/**
* id → 项目名映射(覆盖活跃 + 回收站):审批 delete/restore/purge 各阶段都可能引用项目,
* 故同时查 projects 与 deletedProjects,无则返回 undefined(调用方走 fallback)。
* 复用 project store 已加载列表,不新增网络请求。
*/
const projectNameById = computed<Record<string, string>>(() => {
const map: Record<string, string> = {}
for (const p of projectStore.projects) map[p.id] = p.name
for (const p of projectStore.deletedProjects) if (!(p.id in map)) map[p.id] = p.name
return map
})
/** 工具名 → 该工具中代表项目 id 的参数 key(仅项目类工具特化;delete_task 的 id 是任务 id,不在此列) */
const PROJECT_ID_TOOL_ARG: Record<string, 'id' | 'project_id'> = {
delete_project: 'id',
restore_project: 'id',
purge_project: 'id',
update_project: 'id',
bind_directory: 'id',
create_task: 'project_id',
}
/**
* 审批参数值展示(AR-3):对项目类工具的 id/project_id 特化——优先回显项目名,查不到则显示
* 「项目已不存在」提示 + 原 id,避免用户只看到一串裸 id 不知道是什么数据。
* 非项目工具(如 delete_task 的 id)或其他 key 走通用 formatArgValue,避免误判。
*/
function displayArgValue(arg: { key: string; val: unknown }): string {
const projectArgKey = PROJECT_ID_TOOL_ARG[props.tc.name]
if (projectArgKey && arg.key === projectArgKey) {
const id = typeof arg.val === 'string' ? arg.val : ''
if (id) {
const name = projectNameById.value[id]
if (name) return t('aiTool.projectLabel', { name })
return t('aiTool.projectIdNotFound', { id })
}
}
return formatArgValue(arg.val)
}
/** 工具显示名称(含路径摘要);无 path 时硬编码 fallback(path 几乎总有,i18n 兜底属过度防御) */
function toolDisplayName(tc: AiToolCallInfo): string {
const p = (tc.args as any)?.path as string | undefined

View File

@@ -31,5 +31,8 @@ export default {
purged: 'Purged',
purgeIneffective: 'Purge ineffective',
workflowHint: 'Run it on the workflow page',
projectLabel: '"{name}"',
projectIdNotFound: '(project not found, id={id})',
},
}

View File

@@ -33,5 +33,9 @@ export default {
purged: '已彻底删除',
purgeIneffective: '清除未生效',
workflowHint: '请到工作流页面运行',
// 审批参数特化(id/project_id 回显项目名,查不到标注原 id)
projectLabel: '「{name}」',
projectIdNotFound: '(项目已不存在, id={id})',
},
}