修复: 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:
@@ -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
|
||||
|
||||
@@ -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})',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -33,5 +33,9 @@ export default {
|
||||
purged: '已彻底删除',
|
||||
purgeIneffective: '清除未生效',
|
||||
workflowHint: '请到工作流页面运行',
|
||||
|
||||
// 审批参数特化(id/project_id 回显项目名,查不到标注原 id)
|
||||
projectLabel: '「{name}」',
|
||||
projectIdNotFound: '(项目已不存在, id={id})',
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user