过程卡工具文案改为用户语义
- MCP 工具按写入、浏览、读取素材、导入、生成图片等用户动作显示文案 - 写入工具与文件变更统一显示正在写入文件和项目相对路径 - 命令显示具体命令,验证类命令显示正在验证游戏 - 未知工具只显示正在调用工具,不暴露内部工具名和未审核参数 - 工具详情限制路径长度并拒绝绝对路径与上跳路径 - stream=false 时继续保留新的具体工具执行文案 - 补充工具映射、安全边界、命令分类与过程卡心跳回归 - 决策记录同步工具语义文案规则
This commit is contained in:
@@ -544,15 +544,10 @@ fn direct_codex_safe_activity_for_item_value(item: &serde_json::Value) -> &'stat
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default();
|
||||
if item_type == "commandExecution" {
|
||||
let command = item
|
||||
if item
|
||||
.get("command")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
if command.contains("game.static_smoke")
|
||||
|| command.contains("preview.validate")
|
||||
|| command.contains("verify")
|
||||
|| command.contains("test")
|
||||
.is_some_and(direct_codex_command_is_game_verification)
|
||||
{
|
||||
return "game-verify";
|
||||
}
|
||||
@@ -580,6 +575,108 @@ fn direct_codex_safe_activity_for_item_value(item: &serde_json::Value) -> &'stat
|
||||
direct_codex_safe_activity_for_item(item_type)
|
||||
}
|
||||
|
||||
fn direct_codex_command_is_game_verification(command: &str) -> bool {
|
||||
let command = command.to_ascii_lowercase();
|
||||
command.contains("game.static_smoke")
|
||||
|| command.contains("preview.validate")
|
||||
|| command.contains("verify")
|
||||
|| command.contains("test")
|
||||
}
|
||||
|
||||
fn direct_codex_bounded_detail(value: &str, max_chars: usize) -> Option<String> {
|
||||
let value = value.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(value.chars().take(max_chars).collect())
|
||||
}
|
||||
|
||||
fn direct_codex_project_path_detail(item: &serde_json::Value, pointer: &str) -> Option<String> {
|
||||
let value = item.pointer(pointer)?.as_str()?;
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let path = std::path::Path::new(value);
|
||||
if path.is_absolute()
|
||||
|| path
|
||||
.components()
|
||||
.any(|component| matches!(component, std::path::Component::ParentDir))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
direct_codex_bounded_detail(value, 120)
|
||||
}
|
||||
|
||||
fn direct_codex_mcp_tool_intermediate_text(item: &serde_json::Value) -> String {
|
||||
let arguments = item
|
||||
.get("arguments")
|
||||
.cloned()
|
||||
.unwrap_or(serde_json::Value::Null);
|
||||
let optional_path = |pointer: &str| {
|
||||
direct_codex_project_path_detail(
|
||||
&serde_json::json!({ "arguments": arguments.clone(), "tool": "" }),
|
||||
pointer,
|
||||
)
|
||||
};
|
||||
let optional_text = |field: &str, max_chars: usize| {
|
||||
arguments
|
||||
.get(field)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.and_then(|value| direct_codex_bounded_detail(value, max_chars))
|
||||
};
|
||||
match item.get("tool").and_then(serde_json::Value::as_str) {
|
||||
Some("taonier_prepare_game_art") => "正在准备美术素材".to_string(),
|
||||
Some("agc_generate_image") => match direct_codex_project_path_detail(
|
||||
&serde_json::json!({ "arguments": arguments.clone() }),
|
||||
"/arguments/outputPath",
|
||||
) {
|
||||
Some(path) => format!("正在生成图片:{path}"),
|
||||
None => "正在生成图片".to_string(),
|
||||
},
|
||||
Some("agc_edit_image") => match optional_text("assetName", 80) {
|
||||
Some(name) => format!("正在编辑图片:{name}"),
|
||||
None => "正在编辑图片".to_string(),
|
||||
},
|
||||
Some("agc_list_registered_assets") => match optional_text("query", 80) {
|
||||
Some(query) => format!("正在读取素材库:{query}"),
|
||||
None => "正在读取素材库".to_string(),
|
||||
},
|
||||
Some("agc_list_project_files") => {
|
||||
match optional_path("/arguments/path").or_else(|| optional_text("query", 80)) {
|
||||
Some(detail) => format!("正在浏览项目文件:{detail}"),
|
||||
None => "正在浏览项目文件".to_string(),
|
||||
}
|
||||
}
|
||||
Some("agc_write_file") => match optional_path("/arguments/path") {
|
||||
Some(path) => format!("正在写入文件:{path}"),
|
||||
None => "正在写入文件".to_string(),
|
||||
},
|
||||
Some("agc_list_account_assets") => match optional_text("query", 80) {
|
||||
Some(query) => format!("正在读取账户素材:{query}"),
|
||||
None => "正在读取账户素材".to_string(),
|
||||
},
|
||||
Some("agc_import_account_assets") => {
|
||||
let local_path_count = arguments
|
||||
.get("localPaths")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.map(Vec::len)
|
||||
.filter(|count| *count > 0);
|
||||
match local_path_count {
|
||||
Some(count) => format!("正在导入素材:{count} 项"),
|
||||
None => "正在导入素材".to_string(),
|
||||
}
|
||||
}
|
||||
Some("agc_create_or_derive_resource") => "正在创建素材资源".to_string(),
|
||||
Some("agc_remove_background") => "正在去除图片背景".to_string(),
|
||||
Some("agc_browser_playtest") => "正在试玩游戏".to_string(),
|
||||
Some("agc_web_search") => match optional_text("query", 80) {
|
||||
Some(query) => format!("正在搜索资料:{query}"),
|
||||
None => "正在搜索资料".to_string(),
|
||||
},
|
||||
_ => "正在调用工具".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Project a started Codex item into a short user-visible progress line.
|
||||
/// Codex app-server 0.147/0.149 only pushes structural item/started events
|
||||
/// (with the concrete command/tool/path) while tools run; it does not push
|
||||
@@ -592,28 +689,22 @@ fn direct_codex_item_intermediate_text(item: &serde_json::Value) -> Option<Strin
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default();
|
||||
let text = match item_type {
|
||||
"mcpToolCall" => {
|
||||
let tool = item
|
||||
.get("tool")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or("工具");
|
||||
match item
|
||||
.pointer("/arguments/path")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
{
|
||||
Some(path) => format!("正在调用 {tool}:{path}"),
|
||||
None => format!("正在调用 {tool}"),
|
||||
}
|
||||
}
|
||||
"mcpToolCall" => direct_codex_mcp_tool_intermediate_text(item),
|
||||
"commandExecution" => {
|
||||
let command = item
|
||||
.get("command")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.filter(|value| !value.trim().is_empty());
|
||||
match command {
|
||||
Some(command) => format!("正在执行:{command}"),
|
||||
Some(command) => {
|
||||
let command = direct_codex_bounded_detail(command, 120)
|
||||
.unwrap_or_else(|| "命令".to_string());
|
||||
if direct_codex_command_is_game_verification(&command) {
|
||||
format!("正在验证游戏:{command}")
|
||||
} else {
|
||||
format!("正在执行命令:{command}")
|
||||
}
|
||||
}
|
||||
None => "正在执行命令".to_string(),
|
||||
}
|
||||
}
|
||||
@@ -624,11 +715,11 @@ fn direct_codex_item_intermediate_text(item: &serde_json::Value) -> Option<Strin
|
||||
.or_else(|| item.get("path").and_then(serde_json::Value::as_str))
|
||||
.filter(|value| !value.trim().is_empty());
|
||||
match path {
|
||||
Some(path) => format!("正在修改:{path}"),
|
||||
None => "正在修改项目文件".to_string(),
|
||||
Some(path) => format!("正在写入文件:{path}"),
|
||||
None => "正在写入文件".to_string(),
|
||||
}
|
||||
}
|
||||
"webSearch" => "正在联网搜索".to_string(),
|
||||
"webSearch" => "正在搜索资料".to_string(),
|
||||
"contextCompaction" => "正在整理上下文".to_string(),
|
||||
_ => return None,
|
||||
};
|
||||
@@ -3584,7 +3675,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&mcp).as_deref(),
|
||||
Some("正在调用 agc_write_file:game/index.html")
|
||||
Some("正在写入文件:game/index.html")
|
||||
);
|
||||
|
||||
let command = serde_json::json!({
|
||||
@@ -3593,7 +3684,7 @@ mod tests {
|
||||
});
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&command).as_deref(),
|
||||
Some("正在执行:npm run build")
|
||||
Some("正在执行命令:npm run build")
|
||||
);
|
||||
assert_eq!(
|
||||
direct_codex_safe_activity_for_item_value(&serde_json::json!({
|
||||
@@ -3602,6 +3693,14 @@ mod tests {
|
||||
})),
|
||||
"game-verify"
|
||||
);
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&serde_json::json!({
|
||||
"type": "commandExecution",
|
||||
"command": "preview.validate"
|
||||
}))
|
||||
.as_deref(),
|
||||
Some("正在验证游戏:preview.validate")
|
||||
);
|
||||
|
||||
let file_change = serde_json::json!({
|
||||
"type": "fileChange",
|
||||
@@ -3609,13 +3708,121 @@ mod tests {
|
||||
});
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&file_change).as_deref(),
|
||||
Some("正在修改:game/player.gd")
|
||||
Some("正在写入文件:game/player.gd")
|
||||
);
|
||||
|
||||
let reasoning = serde_json::json!({ "type": "reasoning" });
|
||||
assert_eq!(direct_codex_item_intermediate_text(&reasoning), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_mcp_tool_details_use_user_facing_work_labels() {
|
||||
let detail = |tool: &str, arguments: serde_json::Value| {
|
||||
direct_codex_item_intermediate_text(&serde_json::json!({
|
||||
"type": "mcpToolCall",
|
||||
"tool": tool,
|
||||
"arguments": arguments
|
||||
}))
|
||||
.expect("mcp tool detail")
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
detail("taonier_prepare_game_art", serde_json::json!({})),
|
||||
"正在准备美术素材"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_generate_image",
|
||||
serde_json::json!({ "outputPath": "assets/hero.png" })
|
||||
),
|
||||
"正在生成图片:assets/hero.png"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_edit_image",
|
||||
serde_json::json!({ "assetName": "主角头像" })
|
||||
),
|
||||
"正在编辑图片:主角头像"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_list_registered_assets",
|
||||
serde_json::json!({ "query": "hero" })
|
||||
),
|
||||
"正在读取素材库:hero"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_list_project_files",
|
||||
serde_json::json!({ "path": "assets" })
|
||||
),
|
||||
"正在浏览项目文件:assets"
|
||||
);
|
||||
assert_eq!(
|
||||
detail("agc_list_account_assets", serde_json::json!({})),
|
||||
"正在读取账户素材"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_import_account_assets",
|
||||
serde_json::json!({ "localPaths": ["assets/a.png", "assets/b.png"] })
|
||||
),
|
||||
"正在导入素材:2 项"
|
||||
);
|
||||
assert_eq!(
|
||||
detail("agc_create_or_derive_resource", serde_json::json!({})),
|
||||
"正在创建素材资源"
|
||||
);
|
||||
assert_eq!(
|
||||
detail("agc_remove_background", serde_json::json!({})),
|
||||
"正在去除图片背景"
|
||||
);
|
||||
assert_eq!(
|
||||
detail("agc_browser_playtest", serde_json::json!({})),
|
||||
"正在试玩游戏"
|
||||
);
|
||||
assert_eq!(
|
||||
detail(
|
||||
"agc_web_search",
|
||||
serde_json::json!({ "query": "tauri webview" })
|
||||
),
|
||||
"正在搜索资料:tauri webview"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_mcp_tool_details_do_not_expose_unreviewed_names_or_unsafe_paths() {
|
||||
let unknown = direct_codex_item_intermediate_text(&serde_json::json!({
|
||||
"type": "mcpToolCall",
|
||||
"tool": "SECRET_TOOL_/private/project",
|
||||
"arguments": { "prompt": "Bearer secret-token /private/project" }
|
||||
}))
|
||||
.expect("safe unknown-tool detail");
|
||||
assert_eq!(unknown, "正在调用工具");
|
||||
assert!(!unknown.contains("SECRET_TOOL"));
|
||||
assert!(!unknown.contains("/private/project"));
|
||||
assert!(!unknown.contains("secret-token"));
|
||||
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&serde_json::json!({
|
||||
"type": "mcpToolCall",
|
||||
"tool": "agc_write_file",
|
||||
"arguments": { "path": "C:/Users/private/secret.txt" }
|
||||
}))
|
||||
.as_deref(),
|
||||
Some("正在写入文件")
|
||||
);
|
||||
assert_eq!(
|
||||
direct_codex_item_intermediate_text(&serde_json::json!({
|
||||
"type": "mcpToolCall",
|
||||
"tool": "agc_write_file",
|
||||
"arguments": { "path": "../outside.txt" }
|
||||
}))
|
||||
.as_deref(),
|
||||
Some("正在写入文件")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_preparing_notifications_emit_thinking_activity_without_raw_text() {
|
||||
let reasoning = serde_json::json!({ "delta": "hidden reasoning must not leak" });
|
||||
|
||||
@@ -3697,12 +3697,23 @@ fn project_direct_codex_accumulated_text(
|
||||
}
|
||||
|
||||
fn is_direct_codex_item_started_work_detail(value: &str) -> bool {
|
||||
const PREFIXES: [&str; 5] = [
|
||||
"正在执行:",
|
||||
"正在调用",
|
||||
"正在修改:",
|
||||
"正在联网搜索",
|
||||
const PREFIXES: [&str; 16] = [
|
||||
"正在写入文件:",
|
||||
"正在浏览项目文件",
|
||||
"正在读取素材库",
|
||||
"正在读取账户素材",
|
||||
"正在导入素材",
|
||||
"正在生成图片",
|
||||
"正在编辑图片",
|
||||
"正在准备美术素材",
|
||||
"正在创建素材资源",
|
||||
"正在去除图片背景",
|
||||
"正在试玩游戏",
|
||||
"正在搜索资料:",
|
||||
"正在执行命令:",
|
||||
"正在验证游戏:",
|
||||
"正在整理上下文",
|
||||
"正在调用工具",
|
||||
];
|
||||
PREFIXES.iter().any(|prefix| value.starts_with(prefix))
|
||||
}
|
||||
@@ -4696,14 +4707,15 @@ mod tests {
|
||||
#[test]
|
||||
fn direct_item_started_work_detail_survives_stream_disabled() {
|
||||
assert!(is_direct_codex_item_started_work_detail(
|
||||
"正在执行:npm run build"
|
||||
"正在执行命令:npm run build"
|
||||
));
|
||||
assert!(is_direct_codex_item_started_work_detail(
|
||||
"正在调用 agc_read_file:game/index.html"
|
||||
"正在浏览项目文件:game/index.html"
|
||||
));
|
||||
assert!(is_direct_codex_item_started_work_detail(
|
||||
"正在修改:game/player.gd"
|
||||
"正在写入文件:game/player.gd"
|
||||
));
|
||||
assert!(is_direct_codex_item_started_work_detail("正在调用工具"));
|
||||
assert!(!is_direct_codex_item_started_work_detail("阶段性回复"));
|
||||
assert!(!is_direct_codex_item_started_work_detail(
|
||||
"hidden reasoning must not leak"
|
||||
|
||||
@@ -319,6 +319,23 @@ function directCodexActivityDetail(
|
||||
}
|
||||
}
|
||||
|
||||
const DIRECT_CODEX_SPECIFIC_WORK_DETAIL_PREFIXES = [
|
||||
'正在写入文件:',
|
||||
'正在浏览项目文件',
|
||||
'正在读取素材库',
|
||||
'正在读取账户素材',
|
||||
'正在导入素材',
|
||||
'正在生成图片',
|
||||
'正在编辑图片',
|
||||
'正在准备美术素材',
|
||||
'正在创建素材资源',
|
||||
'正在去除图片背景',
|
||||
'正在试玩游戏',
|
||||
'正在搜索资料:',
|
||||
'正在执行命令:',
|
||||
'正在验证游戏:',
|
||||
] as const;
|
||||
|
||||
function directCodexProcessDetail({
|
||||
accumulatedText,
|
||||
activity,
|
||||
@@ -348,7 +365,9 @@ function directCodexProcessDetail({
|
||||
}
|
||||
|
||||
function isDirectCodexSpecificWorkDetail(text: string) {
|
||||
return /^(?:正在执行:|正在调用|正在修改:)/u.test(text);
|
||||
return DIRECT_CODEX_SPECIFIC_WORK_DETAIL_PREFIXES.some((prefix) =>
|
||||
text.startsWith(prefix),
|
||||
);
|
||||
}
|
||||
|
||||
function directCodexTransientReplyText({
|
||||
|
||||
@@ -5755,7 +5755,7 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
sequence: 5,
|
||||
status: 'running',
|
||||
activity: 'command-exec',
|
||||
accumulatedText: `正在执行:npm run smoke,${'heartbeat 不得覆盖具体命令。'.repeat(12)}`,
|
||||
accumulatedText: `正在执行命令:npm run smoke,${'heartbeat 不得覆盖具体命令。'.repeat(12)}`,
|
||||
updatedAt: 5000,
|
||||
},
|
||||
});
|
||||
@@ -5774,6 +5774,35 @@ export function registerProjectSupervisorSurfaceTests() {
|
||||
within(streamingProcessCard).getByText(/heartbeat 不得覆盖具体命令/u),
|
||||
).not.toBeNull();
|
||||
expect(within(streamingProcessCard).queryByText('正在执行命令')).toBeNull();
|
||||
await act(async () => {
|
||||
directTurnUpdateHandler?.({
|
||||
payload: {
|
||||
projectPath,
|
||||
turnId: firstTurnId,
|
||||
sequence: 7,
|
||||
status: 'running',
|
||||
activity: 'controlled-tool',
|
||||
accumulatedText: `正在写入文件:game/index.html,${'写入详情需要保持展开状态。'.repeat(12)}`,
|
||||
updatedAt: 5200,
|
||||
},
|
||||
});
|
||||
directTurnUpdateHandler?.({
|
||||
payload: {
|
||||
projectPath,
|
||||
turnId: firstTurnId,
|
||||
sequence: 8,
|
||||
status: 'running',
|
||||
activity: 'controlled-tool',
|
||||
updatedAt: 5300,
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(
|
||||
within(streamingProcessCard).getByText(
|
||||
/正在写入文件:game\/index\.html/u,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(within(streamingProcessCard).queryByText('正在调用工具')).toBeNull();
|
||||
expect(
|
||||
within(supervisorSurface).getByLabelText('陶泥儿实时回复').textContent,
|
||||
).toBe('DIRECT_STREAM:先完成正式客户端玩法拆解');
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
## 2026-09-02 Direct 过程卡按回合阶段状态驱动
|
||||
|
||||
- 背景:DirectProject 结果卡把工具活动词、中间文本和真实回复增量都当成“实时回复”,标题随最近一次事件跳动;上游常整包返回正文时还叠加合成打字机,用户看到的是行为名而非当前阶段。
|
||||
- 决策:Direct 过程卡顶部标题只由 `GameCreatorDirectTurnUpdateStatus` 决定(accepted=需求已接收 / running=任务执行中 / streaming=回复生成中 / finalizing=结果整理中 / completed=回复已生成 / failed=处理失败),小字只展示当前正在执行的具体内容并统一加“正在”前缀;真实回复增量(AccumulatedText)才标记 streaming,计划、推理、工具输出与 Activity 一律 running。生成中的累计回复直接作为 assistant 消息气泡在会话列表中原位更新,不再拼进过程卡;进入 finalizing / completed 时保留完整累计回复直到正式消息接管,失败时清除未完成正文。移除合成打字机回放;工具说明/中间文本不再触发 streaming。计划/推理通知收敛为 `preparing` 活动并在界面显示“正在思考中”,原始推理/计划正文不进入 UI,思考期的心跳按 1.2s 限流。命令/文件/工具执行细节(例如“正在执行:<命令>”)与回复流解耦,`stream=false` 时仍展示在过程卡;同一 command-exec 后续无正文的活动心跳不得用通用“正在执行命令”覆盖已展示的具体命令。展开/收起是同一 `project + clientTurnId` 内的持久状态,内容更新不重置,切换新回合才收起;展开详情的滚动条轨道和角落保持透明。
|
||||
- 决策:Direct 过程卡顶部标题只由 `GameCreatorDirectTurnUpdateStatus` 决定(accepted=需求已接收 / running=任务执行中 / streaming=回复生成中 / finalizing=结果整理中 / completed=回复已生成 / failed=处理失败),小字只展示当前正在执行的具体内容并统一加“正在”前缀;真实回复增量(AccumulatedText)才标记 streaming,计划、推理、工具输出与 Activity 一律 running。生成中的累计回复直接作为 assistant 消息气泡在会话列表中原位更新,不再拼进过程卡;进入 finalizing / completed 时保留完整累计回复直到正式消息接管,失败时清除未完成正文。移除合成打字机回放;工具说明/中间文本不再触发 streaming。计划/推理通知收敛为 `preparing` 活动并在界面显示“正在思考中”,原始推理/计划正文不进入 UI,思考期的心跳按 1.2s 限流。命令/文件/工具执行细节与回复流解耦,`stream=false` 时仍展示在过程卡;MCP 工具按用户语义显示(例如 `agc_write_file` 为“正在写入文件:<项目相对路径>”、图片/素材/搜索/试玩分别显示生成、导入、搜索、试玩等动作),未知工具只显示“正在调用工具”不暴露内部工具名;命令显示“正在执行命令:<命令>”,验证类命令显示“正在验证游戏:<命令>”。同一活动后续无正文的心跳不得用通用文案覆盖已展示的具体工作。展开/收起是同一 `project + clientTurnId` 内的持久状态,内容更新不重置,切换新回合才收起;展开详情的滚动条轨道和角落保持透明。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs` 的 DirectProject observer、`apps/ai-game-creator-shell/src/App.tsx` 的事件投影、`ProjectSupervisorView` 过程卡渲染与对应 AppSurface 回归。
|
||||
- 验证方式:Rust 单测证明只有开启流式时的 AccumulatedText 是 streaming、preparing 通知只产生 thinking 活动词且不携带原始推理文本、执行细节在 `stream=false` 时仍保留;AppSurface 覆盖接受态、preparing 显示“正在思考中”、running 长文本展开、command-exec 心跳不覆盖具体命令、streaming 正文进入 assistant 气泡且过程卡只显示阶段、同一回合后续 running 不覆盖正文也不收起、失败后清除未完成正文、正式消息接管不重复;样式核对确认展开详情的滚动条轨道与角落透明;AGC typecheck、全量 appSurface、rustfmt、`npm run check:encoding`、`git diff --check` 通过。
|
||||
- 验证方式:Rust 单测证明只有开启流式时的 AccumulatedText 是 streaming、preparing 通知只产生 thinking 活动词且不携带原始推理文本、执行细节在 `stream=false` 时仍保留,并覆盖全部 AGC MCP 工具语义、未知工具不泄漏、绝对路径 / 上跳路径不展示;AppSurface 覆盖接受态、preparing 显示“正在思考中”、running 长文本展开、command-exec 与写文件心跳不覆盖具体工作、streaming 正文进入 assistant 气泡且过程卡只显示阶段、同一回合后续 running 不覆盖正文也不收起、失败后清除未完成正文、正式消息接管不重复;样式核对确认展开详情的滚动条轨道与角落透明;AGC typecheck、全量 appSurface、rustfmt、`npm run check:encoding`、`git diff --check` 通过。
|
||||
- 关联文档:`docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、分支 `feat/agc-llm-router-official-chain`。
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user