新增: 执行中轨迹卡展开与占用期消息必达
- 执行中 step 卡支持点击展开(复用轨迹展开态, 实时过程工具卡回看) - 占用期消息改落待发箱自动续投, 不再拒绝让用户重发(959) - 待发项派发后完成结果回推来源飞书会话(35min 弃守) - 961 轮次语义/960 聚合防抖设计要点登记
This commit is contained in:
@@ -242,6 +242,9 @@ async fn dispatch_claimed(
|
||||
emit(app, "ai-outbox-sent", &item.id, &conv, None, None);
|
||||
emit(app, "ai-outbox-changed", &item.id, &conv, None, None);
|
||||
tracing::info!("[outbox] 派发 {} → 会话 {conv}", item.id);
|
||||
// 959 结果回推:待发项派发后完成结果推回来源渠道(飞书 chat 映射)。
|
||||
// 桌面端在看的会话重复可见无害;未绑 chat 时 push_to_chat 内部静默跳过
|
||||
spawn_completion_push(app.clone(), conv.clone());
|
||||
Ok(conv_id)
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -444,6 +447,19 @@ pub async fn ai_outbox_enqueue(
|
||||
skill: Option<String>,
|
||||
model_override: Option<String>,
|
||||
conversation_id: String,
|
||||
) -> Result<String, String> {
|
||||
enqueue_internal(&app, &state, text, due_at, skill, model_override, conversation_id).await
|
||||
}
|
||||
|
||||
/// 入队内部口(#959):前端 command 与飞书受理守卫拒绝路径共用,单点校验/唤醒/事件。
|
||||
pub(crate) async fn enqueue_internal(
|
||||
app: &AppHandle,
|
||||
state: &AppState,
|
||||
text: String,
|
||||
due_at: Option<i64>,
|
||||
skill: Option<String>,
|
||||
model_override: Option<String>,
|
||||
conversation_id: String,
|
||||
) -> Result<String, String> {
|
||||
validate_text(&text)?;
|
||||
validate_conv_id(&conversation_id)?;
|
||||
@@ -477,7 +493,7 @@ pub async fn ai_outbox_enqueue(
|
||||
.map_err(|e| format!("待发项写入失败: {e}"))?;
|
||||
// 尽快项/到期项即时唤醒调度器(周期 tick 仅兜底);事件供各窗口视图刷新。
|
||||
notify().notify_one();
|
||||
emit(&app, "ai-outbox-changed", &id, &conversation_id, None, None);
|
||||
emit(app, "ai-outbox-changed", &id, &conversation_id, None, None);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -731,3 +747,43 @@ mod tests {
|
||||
assert!(ensure_cancellable(STATUS_CANCELED).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
/// 959 结果回推任务:订阅事件总线等该会话完成(AiAgentFinished),终版报告推回
|
||||
/// 来源渠道(push_to_chat 按 conv 反查飞书 chat 映射)。35min 超时弃守(对齐渠道
|
||||
/// watcher 量级;引擎仍在跑则结果在桌面端可见,不阻塞)。
|
||||
fn spawn_completion_push(app: AppHandle, conv: String) {
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let state = app.state::<AppState>();
|
||||
let mut rx = state.ai_event_bus.subscribe();
|
||||
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(35 * 60);
|
||||
loop {
|
||||
let ev = tokio::time::timeout_at(deadline, rx.recv()).await;
|
||||
match ev {
|
||||
Err(_) => {
|
||||
tracing::warn!("[outbox] 完成回推超时弃守 conv={conv}");
|
||||
return;
|
||||
}
|
||||
Ok(Err(tokio::sync::broadcast::error::RecvError::Closed)) => return,
|
||||
Ok(Err(tokio::sync::broadcast::error::RecvError::Lagged(_))) => continue,
|
||||
Ok(Ok(v)) => {
|
||||
if v.get("type").and_then(|t| t.as_str()) != Some("AiAgentFinished") {
|
||||
continue;
|
||||
}
|
||||
if v.get("conversation_id").and_then(|c| c.as_str()) != Some(conv.as_str()) {
|
||||
continue;
|
||||
}
|
||||
let summary = v
|
||||
.get("summary")
|
||||
.and_then(|s| s.as_str())
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
if summary.is_empty() {
|
||||
return;
|
||||
}
|
||||
crate::feishu_channel::push_to_chat(&app, &summary, Some(&conv)).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -764,11 +764,40 @@ async fn handle_message(
|
||||
|
||||
let state = ctx.app.state::<crate::state::AppState>();
|
||||
// #20 受理守卫:发票(受理卡)前先过引擎读侧闸。不可启动(前任务占用/压缩暂存/停止中)
|
||||
// 直接回实况话术,不发票、不 spawn、不注册桥与流式任务,堵「受理卡已发后续零通知」
|
||||
// 的伪受理断层(13:38 实证形态)
|
||||
// 不发票、不 spawn、不注册桥与流式任务,堵「受理卡已发后续零通知」的伪受理断层。
|
||||
// 959 必达改造(对齐 u-claw 收件箱续投):拒绝路径改落入待发箱(queue 尽快项),
|
||||
// 前序任务完成调度器自动派发+结果回推本 chat——重试责任不在用户,不再让用户重发。
|
||||
// 入队失败(写库异常等)降级回原占用话术,消息不丢语义不倒退
|
||||
if !state.conv_states.can_accept_request(&conv_id) {
|
||||
feishu_log(&format!("受理守卫拒绝 chat={chat_id} conv={conv_id}"));
|
||||
deliver_result(&ctx.client, &placeholder_id, &message_id, &chat_id, QUEUE_BUSY_MSG).await;
|
||||
match crate::commands::ai::outbox::enqueue_internal(
|
||||
&ctx.app,
|
||||
&state,
|
||||
text.clone(),
|
||||
None, // 尽快队列项:前序任务空闲即发
|
||||
None,
|
||||
None,
|
||||
conv_id.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
feishu_log(&format!("已入待发箱 chat={chat_id} conv={conv_id}"));
|
||||
deliver_result(
|
||||
&ctx.client,
|
||||
&placeholder_id,
|
||||
&message_id,
|
||||
&chat_id,
|
||||
"📥 已登记待发:前序任务完成后将自动发出,无需重发(期间可继续追加,均按补充登记)。",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Err(e) => {
|
||||
feishu_log(&format!("待发箱入队失败 chat={chat_id}: {e}"));
|
||||
deliver_result(&ctx.client, &placeholder_id, &message_id, &chat_id, QUEUE_BUSY_MSG)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 通知桥注册(#14):oneshot 接收端有意 drop——两段式下终版走事件广播
|
||||
|
||||
Reference in New Issue
Block a user