修复: 飞书交互批: 推送汇总卡/伪受理/回查/watcher弃守/status/md净化
- 34 push_to_chat 滚动汇总卡: 同 chat 在途事务卡原地追加, ✅/❌ 终态另起新卡, 更新失败回退重绑; L3 过程不再逐条新开卡片刷屏 - 20 伪受理断层: 发票前过 can_accept_request 守卫, 不可启动回实况话术; inflight 移交 watcher 持有, 运行期计数不随 handler 返回归零 - 22 兜底回查限定受理时间戳边界(纯函数+单测), 缺时间戳视为边界外, 禁投旧回答冒充 - 23 watcher 消费本会话 AiError 视同终局投失败卡, 不再空挂 70min - 26 话术改实况: 未受理(暂不入队)可稍后重发, 删假排队承诺 - 27 快任务兜底改受理卡发出后 8s 宽限窗判定, 替换入场恒假死分支 - 21 /status 命令桌面入口: ai_chat_send 拦截, 零 LLM 零 spawn 返回进度摘要 - 38 出站 md 净化: 标题转粗体/表格转列表/围栏保留, feishu_channel 与 df-im-gateway 同规则互引 - 受理卡符号对齐规范(任务开始用🚀, ✅仅完成) - 验收: cargo check 零 error + feishu 13/df-im-gateway 19/chat 17 passed + vue-tsc 0
This commit is contained in:
@@ -654,6 +654,14 @@ pub async fn ai_chat_send(
|
||||
// None/空 → 无 @ mention(向后兼容,仅 / 技能走 skill 参数);非空 → 按 kind 投影成 Augmentation。
|
||||
mention_spans: Option<Vec<MentionSpanDto>>,
|
||||
) -> Result<String, String> {
|
||||
// /status 进度查询命令(2026-10-06):双端话术引用 /status 但命令此前不存在,
|
||||
// 发送即被当任务烧一轮 LLM。置于技能解析之前拦截,零 LLM 零 spawn。飞书渠道在
|
||||
// feishu_channel.rs 受理前有同命令拦截,本分支补齐桌面入口。
|
||||
if message.trim() == "/status" {
|
||||
return reply_status_summary(&app, &state, conversation_id.clone())
|
||||
.await
|
||||
.map(|_| "ok".to_string());
|
||||
}
|
||||
// H1 根修:前导 `/技能名` 解析。此前技能唯一触发路是前端浮层选中(skill 参数),
|
||||
// 用户直接打字 `/name xxx` 时整串当普通消息,技能注入零发生。
|
||||
// 此处对照 skills_cached() 已扫技能名(含 commands/*.md 来源)匹配前导斜杠令牌,
|
||||
@@ -692,6 +700,87 @@ pub async fn ai_chat_send(
|
||||
.map(|_| "ok".to_string())
|
||||
}
|
||||
|
||||
/// /status 进度查询(2026-10-06):命令拦截应答,零 LLM 零 spawn。
|
||||
///
|
||||
/// 读 conv_states 生命周期态 + per_conv 轮次/最近输出,拼几行摘要作为 assistant 消息落库。
|
||||
/// 事件序列与常规轮收尾同契约(AiAgentRound 建泡 → AiTextDelta 填文 → AiCompleted 复位),
|
||||
/// 缺完成事件前端 doSend 已置的 streaming 无法复位即悬挂。不迁移 ConvState(只读诊断),
|
||||
/// 不写 working_context(/status 是探针不是任务目标)。
|
||||
///
|
||||
/// 会话定位与 spawn_agentic_session 同序(入参优先 → active);无会话/会话已删时 Err 透出
|
||||
/// (前端 toast),零落库零事件。轮次/目标快照短锁读即释,不与 loop 写交叉。
|
||||
async fn reply_status_summary(
|
||||
app: &AppHandle,
|
||||
state: &AppState,
|
||||
conversation_id: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
let target = match conversation_id.filter(|s| !s.is_empty()) {
|
||||
Some(id) => id,
|
||||
None => state
|
||||
.ai_session
|
||||
.lock()
|
||||
.await
|
||||
.active_conversation_id
|
||||
.clone()
|
||||
.ok_or_else(|| "📭 当前无会话,发送消息即自动创建。".to_string())?,
|
||||
};
|
||||
let (state_line, pinned_goals) = {
|
||||
let cs = state.conv_states.get(&target);
|
||||
let session = state.ai_session.lock().await;
|
||||
let conv = session
|
||||
.conv_read(&target)
|
||||
.ok_or_else(|| "📭 会话不存在或已删除。".to_string())?;
|
||||
let line = match cs {
|
||||
ConvState::Generating => {
|
||||
format!("🚀 任务执行中(已 {} 轮),完成后自动回填本会话。", conv.iteration_used)
|
||||
}
|
||||
ConvState::Compressed => "⏳ 上下文压缩中,完成后自动续跑。".to_string(),
|
||||
ConvState::Stopping => "⏹ 收到停止指令,正在收尾退出。".to_string(),
|
||||
ConvState::Error => "❌ 上次任务出错,处理后重发消息即可重试。".to_string(),
|
||||
ConvState::Idle => "✅ 会话空闲,无进行中任务。".to_string(),
|
||||
};
|
||||
(line, conv.pinned_goals.clone())
|
||||
};
|
||||
// 最近输出预览(复用 agentic 收敛的末条 assistant 取数;空=尚无产出,首 token/工具执行中)
|
||||
let recent = super::super::agentic::last_active_assistant_text(&state.ai_session, &target).await;
|
||||
let recent_line = if recent.trim().is_empty() {
|
||||
"最近输出:暂无".to_string()
|
||||
} else {
|
||||
let snippet: String = recent.chars().take(80).collect();
|
||||
format!("最近输出:{snippet}")
|
||||
};
|
||||
let summary = format!("{state_line}\n{recent_line}");
|
||||
|
||||
// 问答成对落库(重开可见,LLM 历史连贯);save 增量路径与 loop 同 per-conv 锁串行
|
||||
{
|
||||
let mut session = state.ai_session.lock().await;
|
||||
let conv = session.conv(&target);
|
||||
conv.messages.push(ChatMessage::user("/status"));
|
||||
conv.messages.push(ChatMessage::assistant(summary.clone()));
|
||||
}
|
||||
save_conversation(&state.ai_session, &state.db, &target, None, None, true).await;
|
||||
// pinned_goals 原样透传防前端目标区回退(与 agentic completed_event 同口径)
|
||||
emit_ai_chat_event(app, AiChatEvent::AiAgentRound { round: 1, conversation_id: Some(target.clone()) });
|
||||
emit_ai_chat_event(app, AiChatEvent::AiTextDelta { delta: summary, conversation_id: Some(target.clone()) });
|
||||
emit_ai_chat_event(
|
||||
app,
|
||||
AiChatEvent::AiCompleted {
|
||||
total_tokens: 0,
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
prompt_cache_hit_tokens: 0,
|
||||
prompt_cache_miss_tokens: 0,
|
||||
reasoning_tokens: 0,
|
||||
is_estimated: false,
|
||||
incomplete: None,
|
||||
incomplete_reason: None,
|
||||
conversation_id: Some(target),
|
||||
pinned_goals,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 发送消息并启动 agentic 会话的共用核心(纯搬迁自 ai_chat_send,行为逐字等价)。
|
||||
///
|
||||
/// 调用方:ai_chat_send IPC(前端对话,pin_active=true)/ 工作流 AiAgentNode 编排会话
|
||||
|
||||
Reference in New Issue
Block a user