feat(engine): 添加技能查看工具并优化异步任务管理

- 添加SkillViewTool到引擎加载器中,增强技能管理功能
- 在AgentLoop中引入_active_direct_task来跟踪活跃任务
- 实现直接任务执行时的同步处理逻辑
- 更新工具实例化方式以支持依赖注入

feat(config): 增加智能体运行时参数配置支持

- 扩展AgentDefaultsConfig添加max_tokens和temperature字段
- 实现配置解析函数_first_config_value处理多个配置源
- 支持通过Web API动态更新智能体运行时参数
- 添加前端页面配置表单和验证逻辑

refactor(provider): 统一最大令牌数参数类型为可选整型

- 将所有LLM提供者的max_tokens参数改为int | None类型
- 为AnthropicProvider实现模型特定的最大令牌数默认值
- 调整参数传递逻辑,优先级:调用参数 > 配置文件 > 模型默认值
- 移除硬编码的默认值,改用条件判断

feat(process): 增强事件投影功能

- 添加工具调用开始/结束事件的映射逻辑
- 实现技能激活事件的识别和展示
- 添加辅助函数处理工具调用名称和参数提取
- 优化运行记录关联逻辑,提升事件匹配准确性

fix(web): 更新网络请求客户端信任环境设置

- 将WebFetchTool和WebSearchTool的trust_env参数设为True
- 确保HTTP客户端能够正确使用系统代理配置
- 修复可能的网络连接问题

test: 添加配置加载和事件投影相关测试

- 新增智能体默认参数配置测试用例
- 实现API配置持久化和重载测试
- 添加技能卡片和工具事件的投影测试
```
This commit is contained in:
2026-05-27 13:37:06 +08:00
parent 55b39563a0
commit 33a9845566
75 changed files with 2599 additions and 114 deletions

View File

@ -27,6 +27,7 @@ const TIMELINE_CARD_TYPES = new Set<TaskTimelineCardType>([
'artifact',
'error',
'result',
'result_history',
'acceptance',
]);
@ -77,10 +78,6 @@ function cardTypeForEvent(event: ProcessEvent): TaskTimelineCardType | null {
return timelineType;
}
if (event.status === 'error') {
return 'error';
}
switch (String(event.kind)) {
case 'task_planned':
case 'run_started':
@ -106,6 +103,9 @@ function cardTypeForEvent(event: ProcessEvent): TaskTimelineCardType | null {
case 'task_error':
return 'error';
default:
if (event.status === 'error') {
return 'error';
}
return null;
}
}
@ -136,6 +136,8 @@ function titleForCard(type: TaskTimelineCardType, actorName?: string): string {
return '执行遇到问题';
case 'result':
return '本轮结果';
case 'result_history':
return '历史结果版本';
case 'acceptance':
return '任务验收';
}
@ -182,6 +184,22 @@ function resultSummary(task: BackendTask): string | undefined {
);
}
function assistantResultForRun(task: BackendTask, runId: string | null | undefined): string | undefined {
if (!runId) return undefined;
const run = (task.runs ?? []).find((item) => item.run_id === runId);
if (!run) return undefined;
const assistantMessages = run.messages.filter((message) => message.role === 'assistant' && message.content.trim());
return lastItem(assistantMessages)?.content.trim();
}
function resultSummaryForEvent(task: BackendTask, event: ProcessEvent): string | undefined {
return firstString(assistantResultForRun(task, event.run_id), summaryForEvent(event));
}
function fallbackResultSummary(task: BackendTask): string | undefined {
return firstString(assistantResultForRun(task, lastItem(task.run_ids)), resultSummary(task));
}
function buildRunMap(processRuns: ProcessRun[]): Map<string, ProcessRun> {
const map = new Map<string, ProcessRun>();
for (const run of processRuns) {
@ -239,12 +257,106 @@ function isCoveredByAcceptanceEvent(
return matchingTypeEvents.length === 1;
}
function cardTime(card: TaskTimelineCard): number {
return toTime(card.createdAt) ?? Number.MAX_SAFE_INTEGER;
}
function cardComment(card: TaskTimelineCard): string | undefined {
return firstString(card.details?.comment, card.summary);
}
function toolCallKeyFromEvent(event: ProcessEvent): string | null {
const toolCallId = firstString(event.metadata?.tool_call_id);
if (toolCallId) return `${event.run_id}:${toolCallId}`;
const toolName = firstString(event.metadata?.tool_name, event.actor_name, event.actor_id);
if (toolName) return `${event.run_id}:${toolName}`;
return null;
}
function buildToolResultStatusByCall(processEvents: ProcessEvent[]): Map<string, string> {
const statuses = new Map<string, string>();
for (const event of processEvents) {
if (cardTypeForEvent(event) !== 'tool_result') continue;
const key = toolCallKeyFromEvent(event);
if (!key) continue;
statuses.set(key, event.status || 'done');
}
return statuses;
}
function buildResultHistoryCard(task: BackendTask, resultCards: TaskTimelineCard[], acceptanceCards: TaskTimelineCard[]): TaskTimelineCard {
const versions = resultCards.map((resultCard) => {
const acceptanceCard = acceptanceCards
.filter((card) => card.runId === resultCard.runId)
.sort((a, b) => cardTime(a) - cardTime(b))
.at(-1);
return {
runId: resultCard.runId ?? null,
result: resultCard.summary ?? '',
createdAt: resultCard.createdAt,
status: acceptanceCard?.status ?? resultCard.status ?? null,
acceptanceType: acceptanceCard?.status ?? null,
comment: acceptanceCard ? cardComment(acceptanceCard) ?? '' : '',
acceptedAt: acceptanceCard?.createdAt ?? null,
};
});
return {
id: `${task.task_id}:result-history`,
taskId: task.task_id,
type: 'result_history',
title: titleForCard('result_history'),
summary: `${resultCards.length} 历史结果版本`,
createdAt: resultCards[0]?.createdAt ?? task.created_at,
details: { versions },
};
}
function collapseHistoricalResults(task: BackendTask, cards: TaskTimelineCard[]): TaskTimelineCard[] {
const resultCards = cards.filter((card) => card.type === 'result');
if (resultCards.length <= 1) return cards;
const finalAcceptedRunId = firstString(task.metadata?.final_accepted_run_id);
const latestResult =
(finalAcceptedRunId ? resultCards.find((card) => card.runId === finalAcceptedRunId) : undefined) ??
[...resultCards].sort((a, b) => cardTime(a) - cardTime(b)).at(-1);
if (!latestResult) return cards;
const oldResults = resultCards
.filter((card) => card.id !== latestResult.id)
.sort((a, b) => cardTime(a) - cardTime(b));
if (oldResults.length === 0) return cards;
const oldRunIds = new Set(oldResults.map((card) => card.runId).filter(Boolean));
const oldAcceptances = cards
.filter((card) => card.type === 'acceptance' && oldRunIds.has(card.runId))
.sort((a, b) => cardTime(a) - cardTime(b));
const foldedIds = new Set([...oldResults, ...oldAcceptances].map((card) => card.id));
const historyCard = buildResultHistoryCard(task, oldResults, oldAcceptances);
const firstOldResultIndex = cards.findIndex((card) => card.id === oldResults[0].id);
const output: TaskTimelineCard[] = [];
for (let index = 0; index < cards.length; index += 1) {
if (index === firstOldResultIndex) {
output.push(historyCard);
}
if (!foldedIds.has(cards[index].id)) {
output.push(cards[index]);
}
}
return output;
}
export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): TaskTimelineCard[] {
const { task } = input;
const processRuns = input.processRuns ?? task.process_runs ?? [];
const processEvents = input.processEvents ?? task.process_events ?? [];
const processArtifacts = input.processArtifacts ?? task.process_artifacts ?? [];
const runsById = buildRunMap(processRuns);
const toolResultStatusByCall = buildToolResultStatusByCall(processEvents);
const runsWithProgressEvents = new Set<string>();
const acceptanceEvents: AcceptanceEventIdentity[] = [];
let hasResultEventCard = false;
@ -285,9 +397,12 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
parentRunId: event.parent_run_id,
type,
title: titleForCard(type, event.actor_name),
summary: summaryForEvent(event),
summary: type === 'result' ? resultSummaryForEvent(task, event) : summaryForEvent(event),
actorName: event.actor_name,
status: event.status,
status:
type === 'tool_call'
? toolResultStatusByCall.get(toolCallKeyFromEvent(event) ?? '') ?? event.status
: event.status,
createdAt: event.created_at,
details: detailsForEvent(event),
});
@ -340,7 +455,7 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
runId: lastItem(task.run_ids),
type: 'result',
title: titleForCard('result'),
summary: resultSummary(task),
summary: fallbackResultSummary(task),
status: task.status,
createdAt: task.closed_at ?? task.updated_at ?? task.created_at,
details: task.validation_result ?? undefined,
@ -366,8 +481,10 @@ export function buildTaskTimelineCards(input: BuildTaskTimelineCardsInput): Task
});
}
return cards
const sortedCards = cards
.map((card, index) => ({ card, index }))
.sort(compareCardsByCreatedAt)
.map(({ card }) => card);
return collapseHistoricalResults(task, sortedCards);
}