mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
chore: finalize v1.25.0 release artifacts
This commit is contained in:
@@ -728,3 +728,45 @@ npx eslint "src/components/agent/chat/components/MessageList.tsx" "src/component
|
||||
```
|
||||
|
||||
结果:性能指标汇总单测通过,ESLint touched files 继续通过。
|
||||
|
||||
### 2026-04-30:P1 第十四刀,旧会话首帧延后 Markdown hydrate
|
||||
|
||||
采集事实:
|
||||
|
||||
- DevBridge `3030` 仍未监听,真实 Playwright 多旧会话切换暂不可测。
|
||||
- 当前机器已有 `tsc --noEmit` 与多条 `rustc` 高 CPU 进程,继续启动 Tauri / GUI smoke 会污染用户反馈的鼠标 loading 与 CPU 飙高采样。
|
||||
- 第十三刀已跳过旧会话首帧的 `contentParts` 细节扫描,但短历史 assistant 正文仍会进入 `StreamingRenderer -> MarkdownRenderer -> ReactMarkdown`,即使 `renderMode=light` 也会同步解析多条历史 Markdown。
|
||||
- 旧会话首帧的产品目标是“先看到消息文本与布局”,Markdown 标题、列表、代码高亮、表格等可等 idle 后恢复。
|
||||
|
||||
已完成:
|
||||
|
||||
- `MessageList.tsx`:
|
||||
- 增加 `MESSAGE_LIST_STRUCTURED_HISTORY_CONTENT_RE` 与 `hasStructuredHistoricalContentHint`,避免把 A2UI / write_file / document 这类结构化协议用纯文本提前露出。
|
||||
- 增加 `HistoricalMarkdownHydrationPreview`,旧会话首帧用 `whitespace-pre-wrap` 纯文本直接展示 assistant 正文,不挂载 `StreamingRenderer` / `ReactMarkdown`。
|
||||
- 增加 `historicalMarkdownDeferredCount` 指标,记录旧会话首帧被延后的 Markdown hydrate 数量。
|
||||
- 当 historical timeline idle-ready 后,自动恢复原有 `StreamingRenderer` / light Markdown 渲染。
|
||||
- `MessageList.test.tsx`:更新旧会话首帧回归,断言首帧出现 `message-list-historical-markdown-preview`、不挂载 `streaming-renderer`,idle 后移除 preview 并恢复 renderer。
|
||||
- `agentUiPerformanceMetrics.ts` / `.test.ts`:summary 增加 `historicalMarkdownDeferredMax`,方便 Playwright summary 直接读取 Markdown hydrate 延后命中量。
|
||||
|
||||
已验证:
|
||||
|
||||
```bash
|
||||
npm exec -- vitest run "src/components/agent/chat/components/MessageList.test.tsx" -t "旧会话首帧应延后历史助手 contentParts 与 Markdown 细节扫描|已分页旧会话首帧应只把尾部相关 turns 的 threadItems 纳入计算|旧会话消息较少但执行过程很多时也应延后构建 timeline|旧会话里的长助手回复应先展示纯文本预览"
|
||||
npm exec -- vitest run "src/lib/agentUiPerformanceMetrics.test.ts"
|
||||
npx eslint "src/components/agent/chat/components/MessageList.tsx" "src/components/agent/chat/components/MessageList.test.tsx" "src/lib/agentUiPerformanceMetrics.ts" "src/lib/agentUiPerformanceMetrics.test.ts" --max-warnings 0
|
||||
npm run bridge:health -- --timeout-ms 5000
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
- MessageList 定向回归:通过,`4` 个测试通过。
|
||||
- 性能指标汇总单测:通过,`2` 个测试通过。
|
||||
- ESLint touched files:通过。
|
||||
- DevBridge 健康检查:失败,`http://127.0.0.1:3030/health` 未监听。
|
||||
- TypeScript:本轮未追加全量 typecheck;本机已有其它 `tsc --noEmit` 高 CPU 进程在运行,避免重复启动。
|
||||
|
||||
下一步:
|
||||
|
||||
1. DevBridge 恢复后复测真实旧会话,读取 `historicalMarkdownDeferredMax`、`historicalContentPartsDeferredMax`、`threadItemsScanDeferredCount`、`clickToMessageListPaintMs`、`longTaskMaxMs`。
|
||||
2. 若首帧仍慢,下一刀优先看 `visibleMessages.filter` / `buildMessageTurnGroups` 是否需要基于 sessionId 做更强 memo 或窗口化。
|
||||
3. 若首帧已快但 idle 后出现卡顿,把 Markdown hydrate / timeline hydrate 拆成分批 idle,而不是一次性恢复完整历史。
|
||||
|
||||
@@ -688,6 +688,22 @@ fn build_success_criteria(
|
||||
"未命名合同"
|
||||
)
|
||||
));
|
||||
let execution_profile_keys =
|
||||
modality_contract_execution_profile_keys(modality_runtime_contracts);
|
||||
if !execution_profile_keys.is_empty() {
|
||||
criteria.push(format!(
|
||||
"回放必须保留 execution profile 决策输入:{}。",
|
||||
format_text_list(&execution_profile_keys, "未记录 execution profile")
|
||||
));
|
||||
}
|
||||
let executor_adapter_keys =
|
||||
modality_contract_executor_adapter_keys(modality_runtime_contracts);
|
||||
if !executor_adapter_keys.is_empty() {
|
||||
criteria.push(format!(
|
||||
"回放必须保留 executor adapter 绑定:{},不能退回自由工具选择或旧 CLI 旁路。",
|
||||
format_text_list(&executor_adapter_keys, "未记录 executor adapter")
|
||||
));
|
||||
}
|
||||
}
|
||||
if modality_contract_has_browser_control(modality_runtime_contracts) {
|
||||
criteria.push(
|
||||
@@ -914,6 +930,21 @@ fn build_modality_contract_checks(modality_runtime_contracts: &Value) -> Vec<Str
|
||||
format_text_list(&assessment_sources, "未记录判定来源")
|
||||
));
|
||||
}
|
||||
let execution_profile_keys =
|
||||
modality_contract_execution_profile_keys(modality_runtime_contracts);
|
||||
if !execution_profile_keys.is_empty() {
|
||||
checks.push(format!(
|
||||
"确认 replay 保留 execution profile:{},用于解释模型角色、权限与 LimeCore policy 合并输入。",
|
||||
format_text_list(&execution_profile_keys, "未记录 execution profile")
|
||||
));
|
||||
}
|
||||
let executor_adapter_keys = modality_contract_executor_adapter_keys(modality_runtime_contracts);
|
||||
if !executor_adapter_keys.is_empty() {
|
||||
checks.push(format!(
|
||||
"确认 replay 保留 executor adapter:{},用于解释真实执行器绑定、产物输出与失败映射。",
|
||||
format_text_list(&executor_adapter_keys, "未记录 executor adapter")
|
||||
));
|
||||
}
|
||||
|
||||
if modality_contract_has_routing_block(modality_runtime_contracts) {
|
||||
checks.push(format!(
|
||||
@@ -1086,6 +1117,12 @@ fn infer_replay_suite_tags(
|
||||
for contract_key in modality_contract_keys(modality_runtime_contracts) {
|
||||
push_unique_owned_tag(&mut tags, format!("modality-{contract_key}"));
|
||||
}
|
||||
if !modality_contract_execution_profile_keys(modality_runtime_contracts).is_empty() {
|
||||
push_unique_text_tag(&mut tags, "execution-profile");
|
||||
}
|
||||
if !modality_contract_executor_adapter_keys(modality_runtime_contracts).is_empty() {
|
||||
push_unique_text_tag(&mut tags, "executor-adapter");
|
||||
}
|
||||
}
|
||||
|
||||
if modality_contract_has_routing_block(modality_runtime_contracts) {
|
||||
@@ -1309,6 +1346,48 @@ fn modality_contract_failure_codes(modality_runtime_contracts: &Value) -> Vec<St
|
||||
collect_unique_snapshot_strings(modality_runtime_contracts, "failureCode")
|
||||
}
|
||||
|
||||
fn modality_contract_execution_profile_keys(modality_runtime_contracts: &Value) -> Vec<String> {
|
||||
let mut values =
|
||||
collect_unique_snapshot_strings(modality_runtime_contracts, "executionProfileKey");
|
||||
collect_unique_snapshot_strings_into(
|
||||
modality_runtime_contracts,
|
||||
"execution_profile_key",
|
||||
&mut values,
|
||||
);
|
||||
collect_unique_string_array_at_pointer(
|
||||
modality_runtime_contracts,
|
||||
"/snapshotIndex/executionProfileKeys",
|
||||
&mut values,
|
||||
);
|
||||
collect_unique_string_array_at_pointer(
|
||||
modality_runtime_contracts,
|
||||
"/snapshot_index/execution_profile_keys",
|
||||
&mut values,
|
||||
);
|
||||
values
|
||||
}
|
||||
|
||||
fn modality_contract_executor_adapter_keys(modality_runtime_contracts: &Value) -> Vec<String> {
|
||||
let mut values =
|
||||
collect_unique_snapshot_strings(modality_runtime_contracts, "executorAdapterKey");
|
||||
collect_unique_snapshot_strings_into(
|
||||
modality_runtime_contracts,
|
||||
"executor_adapter_key",
|
||||
&mut values,
|
||||
);
|
||||
collect_unique_string_array_at_pointer(
|
||||
modality_runtime_contracts,
|
||||
"/snapshotIndex/executorAdapterKeys",
|
||||
&mut values,
|
||||
);
|
||||
collect_unique_string_array_at_pointer(
|
||||
modality_runtime_contracts,
|
||||
"/snapshot_index/executor_adapter_keys",
|
||||
&mut values,
|
||||
);
|
||||
values
|
||||
}
|
||||
|
||||
fn modality_contract_has_browser_control(modality_runtime_contracts: &Value) -> bool {
|
||||
modality_contract_snapshots(modality_runtime_contracts)
|
||||
.iter()
|
||||
@@ -1724,16 +1803,37 @@ fn collect_unique_snapshot_strings(
|
||||
field_name: &str,
|
||||
) -> Vec<String> {
|
||||
let mut values = Vec::new();
|
||||
collect_unique_snapshot_strings_into(modality_runtime_contracts, field_name, &mut values);
|
||||
values
|
||||
}
|
||||
|
||||
fn collect_unique_snapshot_strings_into(
|
||||
modality_runtime_contracts: &Value,
|
||||
field_name: &str,
|
||||
values: &mut Vec<String>,
|
||||
) {
|
||||
for snapshot in modality_contract_snapshots(modality_runtime_contracts) {
|
||||
if let Some(value) = snapshot
|
||||
.get(field_name)
|
||||
.and_then(Value::as_str)
|
||||
.and_then(|value| normalize_optional_text(Some(value.to_string())))
|
||||
{
|
||||
push_unique_owned_tag(&mut values, value);
|
||||
push_unique_owned_tag(values, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_unique_string_array_at_pointer(value: &Value, pointer: &str, values: &mut Vec<String>) {
|
||||
if let Some(items) = value.pointer(pointer).and_then(Value::as_array) {
|
||||
for item in items {
|
||||
if let Some(value) = item
|
||||
.as_str()
|
||||
.and_then(|value| normalize_optional_text(Some(value.to_string())))
|
||||
{
|
||||
push_unique_owned_tag(values, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
values
|
||||
}
|
||||
|
||||
fn format_text_list(values: &[String], fallback: &str) -> String {
|
||||
@@ -3253,6 +3353,12 @@ mod tests {
|
||||
"executor_binding": {
|
||||
"executor_kind": "skill",
|
||||
"binding_key": "research"
|
||||
},
|
||||
"execution_profile": {
|
||||
"profile_key": "web_research_profile"
|
||||
},
|
||||
"executor_adapter": {
|
||||
"adapter_key": "skill:research"
|
||||
}
|
||||
},
|
||||
"entry_source": "at_report_command"
|
||||
@@ -3296,12 +3402,42 @@ mod tests {
|
||||
.and_then(Value::as_str),
|
||||
Some("executor_invoked")
|
||||
);
|
||||
assert_eq!(
|
||||
input
|
||||
.pointer("/runtimeContext/modalityRuntimeContracts/snapshots/0/executionProfileKey")
|
||||
.and_then(Value::as_str),
|
||||
Some("web_research_profile")
|
||||
);
|
||||
assert_eq!(
|
||||
input
|
||||
.pointer("/runtimeContext/modalityRuntimeContracts/snapshots/0/executorAdapterKey")
|
||||
.and_then(Value::as_str),
|
||||
Some("skill:research")
|
||||
);
|
||||
assert_eq!(
|
||||
input
|
||||
.pointer(
|
||||
"/runtimeContext/modalityRuntimeContracts/snapshotIndex/executionProfileKeys/0",
|
||||
)
|
||||
.and_then(Value::as_str),
|
||||
Some("web_research_profile")
|
||||
);
|
||||
assert_eq!(
|
||||
input
|
||||
.pointer(
|
||||
"/runtimeContext/modalityRuntimeContracts/snapshotIndex/executorAdapterKeys/0",
|
||||
)
|
||||
.and_then(Value::as_str),
|
||||
Some("skill:research")
|
||||
);
|
||||
let suite_tags = input
|
||||
.pointer("/classification/suiteTags")
|
||||
.and_then(Value::as_array)
|
||||
.expect("suite tags");
|
||||
for expected_tag in [
|
||||
"modality-web_research",
|
||||
"execution-profile",
|
||||
"executor-adapter",
|
||||
"web-research",
|
||||
"research-skill",
|
||||
"web-research-trace",
|
||||
@@ -3317,12 +3453,16 @@ mod tests {
|
||||
assert!(expected.contains("Skill(report_generate)"));
|
||||
assert!(expected.contains("search_query"));
|
||||
assert!(expected.contains("lime_site_*"));
|
||||
assert!(expected.contains("web_research_profile"));
|
||||
assert!(expected.contains("skill:research"));
|
||||
assert!(expected.contains("model_memory_only_answer"));
|
||||
assert!(expected.contains("local_file_search_before_research_skill"));
|
||||
assert!(expected.contains("\"requiresHumanReview\": false"));
|
||||
|
||||
let grader = fs::read_to_string(grader_path).expect("grader");
|
||||
assert!(grader.contains("多模态运行合同检查"));
|
||||
assert!(grader.contains("web_research_profile"));
|
||||
assert!(grader.contains("skill:research"));
|
||||
assert!(grader.contains("Skill(research)"));
|
||||
assert!(grader.contains("Skill(report_generate)"));
|
||||
assert!(grader.contains("model_memory_only_answer"));
|
||||
@@ -3337,6 +3477,18 @@ mod tests {
|
||||
.and_then(Value::as_str),
|
||||
Some("web_research_skill_trace.modality_runtime_contract")
|
||||
);
|
||||
assert_eq!(
|
||||
links
|
||||
.pointer("/modalityRuntimeContracts/snapshotIndex/executionProfileKeys/0")
|
||||
.and_then(Value::as_str),
|
||||
Some("web_research_profile")
|
||||
);
|
||||
assert_eq!(
|
||||
links
|
||||
.pointer("/modalityRuntimeContracts/snapshotIndex/executorAdapterKeys/0")
|
||||
.and_then(Value::as_str),
|
||||
Some("skill:research")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user