新增: ai_list_recoverable IPC+桌面可恢复入口双轨数据源(B3)
This commit is contained in:
+1
-1
@@ -1058,7 +1058,7 @@ graph TD
|
|||||||
|
|
||||||
## 无人值守可靠性专项(2026-10-06 swarm 立项,设计档:docs/02-架构设计/无人值守可靠性专项设计-20261006.md+temp/V49断点续跑设计-20261006.md)
|
## 无人值守可靠性专项(2026-10-06 swarm 立项,设计档:docs/02-架构设计/无人值守可靠性专项设计-20261006.md+temp/V49断点续跑设计-20261006.md)
|
||||||
- [x] B1(S):H1 汇报 git 实测附录 ✅(2026-10-06:deliver_completion_report_if_waited 单点 append_git_facts——branch/HEAD/未提交计数三行,非 git 仓静默跳过,超长报告不追,git.rs 增 git_exec 只读薄封装)+ 探活告警口径修正 ✅(同日 18 批:inflight 计数点前移到 handle_message 白名单通过处,覆盖占位卡 HTTP+per-chat 锁排队窗口,旧计数点删除防双计)- [ ] B2(M):H2 token 三闸(抄 sub_agent round_budget_stop)+ H4 产出审计 subscriber
|
- [x] B1(S):H1 汇报 git 实测附录 ✅(2026-10-06:deliver_completion_report_if_waited 单点 append_git_facts——branch/HEAD/未提交计数三行,非 git 仓静默跳过,超长报告不追,git.rs 增 git_exec 只读薄封装)+ 探活告警口径修正 ✅(同日 18 批:inflight 计数点前移到 handle_message 白名单通过处,覆盖占位卡 HTTP+per-chat 锁排队窗口,旧计数点删除防双计)- [ ] B2(M):H2 token 三闸(抄 sub_agent round_budget_stop)+ H4 产出审计 subscriber
|
||||||
- [ ] B3(M):V49 防覆盖定稿+单测 + ai_list_recoverable IPC + 桌面「可恢复」入口
|
- [x] B3(M):V49 防覆盖定稿+单测 ✅(本会话前批:#11 update_full 剥离 loop_state 列,df-storage conversation 8 passed)+ ai_list_recoverable IPC ✅(2026-10-06:conversation.rs parked 专项完备真相清单 id/title/model/updated_at,不受侧栏 limit 截断,lib.rs 已注册)+ 桌面「可恢复」入口 ✅(2026-10-06 本批:TopBar recoverableTasks 双轨数据源——conversations loop_state 实时投影+ai_list_recoverable IPC 完备真相合并去重,面板展开触发 IPC 拉取,失败退回投影;ai.ts listRecoverable 封装)。验收:cargo check EXIT 0 + vue-tsc EXIT 0。
|
||||||
- [ ] B4(L):ai_recover_parked 重启恢复 + H3 外部干预感知(git HEAD 比对→Parked)
|
- [ ] B4(L):ai_recover_parked 重启恢复 + H3 外部干预感知(git HEAD 比对→Parked)
|
||||||
|
|
||||||
## UI/UX 体验设计实施项(2026-10-06 设计档:temp/异步任务会话UIUX设计-20261006.md,依赖 13/14/16/17 机制先行)
|
## UI/UX 体验设计实施项(2026-10-06 设计档:temp/异步任务会话UIUX设计-20261006.md,依赖 13/14/16/17 机制先行)
|
||||||
|
|||||||
@@ -278,6 +278,30 @@ pub async fn ai_conversation_list(
|
|||||||
Ok(active)
|
Ok(active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// B3(无人值守可靠性专项):列出可恢复会话(loop_state='parked')。
|
||||||
|
///
|
||||||
|
/// V49 断点续跑的完备真相查询:重启时 mark_running_as_parked 改写的会话在此列出,
|
||||||
|
/// 桌面任务卡「可恢复」分区与飞书投影共用本 IPC(单一事实源)。与 ai_conversation_list
|
||||||
|
/// 的 loop_state 透出互补——list 是侧栏全量摘要(截断 limit 内),本 IPC 是 parked 专项
|
||||||
|
/// 全量清单(不受侧栏 limit 截断),且附任务上下文(标题+最后更新时间)供恢复决策。
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn ai_list_recoverable(
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
|
let records = state.ai_conversations.list_parked().await.map_err(err_str)?;
|
||||||
|
Ok(records
|
||||||
|
.iter()
|
||||||
|
.map(|r| {
|
||||||
|
serde_json::json!({
|
||||||
|
"id": r.id,
|
||||||
|
"title": r.title,
|
||||||
|
"model": r.model,
|
||||||
|
"updated_at": r.updated_at,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
/// 切换到指定对话(从 DB 加载 messages 到内存 + 返回 messages 给前端)
|
/// 切换到指定对话(从 DB 加载 messages 到内存 + 返回 messages 给前端)
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn ai_conversation_switch(
|
pub async fn ai_conversation_switch(
|
||||||
|
|||||||
@@ -449,6 +449,7 @@ pub fn run() {
|
|||||||
// AI 对话管理
|
// AI 对话管理
|
||||||
commands::ai::ai_conversation_create,
|
commands::ai::ai_conversation_create,
|
||||||
commands::ai::ai_conversation_list,
|
commands::ai::ai_conversation_list,
|
||||||
|
commands::ai::ai_list_recoverable,
|
||||||
commands::ai::ai_conversation_switch,
|
commands::ai::ai_conversation_switch,
|
||||||
// S3 子代理轨迹只读窥视(复用 switch 读路径,不切任何会话状态)
|
// S3 子代理轨迹只读窥视(复用 switch 读路径,不切任何会话状态)
|
||||||
commands::ai::ai_conversation_peek,
|
commands::ai::ai_conversation_peek,
|
||||||
|
|||||||
@@ -327,6 +327,11 @@ export const aiApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** B3 可恢复会话清单(loop_state='parked' 完备真相,不受侧栏 limit 截断) */
|
||||||
|
listRecoverable(): Promise<{ id: string; title: string | null; model: string | null; updated_at: string }[]> {
|
||||||
|
return invoke('ai_list_recoverable')
|
||||||
|
},
|
||||||
|
|
||||||
/** 切换到指定对话 */
|
/** 切换到指定对话 */
|
||||||
switchConversation(conversationId: string): Promise<AiConversationDetail> {
|
switchConversation(conversationId: string): Promise<AiConversationDetail> {
|
||||||
return invoke('ai_conversation_switch', { conversationId })
|
return invoke('ai_conversation_switch', { conversationId })
|
||||||
|
|||||||
@@ -456,14 +456,31 @@ const runningTasks = computed(() => {
|
|||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
// V49 可恢复入口(断点续跑读侧):conversations 中 loop_state='parked' 的会话(重启中断待续),
|
// V49 可恢复入口(B3):数据源双轨——conversations 中 loop_state='parked'(实时投影,
|
||||||
// 独立分区展示在任务面板下方,点击切回(继续与否由用户在会话内决定,不做自动续跑)
|
// 侧栏 limit 内)+ ai_list_recoverable 完备真相 IPC(面板展开时拉取,补侧栏截断漏项)。
|
||||||
const recoverableTasks = computed(() => {
|
// 合并去重按 id;点击切回,继续与否由用户在会话内决定,不做自动续跑
|
||||||
|
const recoverableFromList = computed(() => {
|
||||||
return store.state.conversations
|
return store.state.conversations
|
||||||
.filter(c => c.loop_state === 'parked' && c.id !== store.state.activeConversationId)
|
.filter(c => c.loop_state === 'parked' && c.id !== store.state.activeConversationId)
|
||||||
.slice(0, 10)
|
|
||||||
.map(c => ({ id: c.id, title: c.title || t('aiChat.untitledConversation') }))
|
.map(c => ({ id: c.id, title: c.title || t('aiChat.untitledConversation') }))
|
||||||
})
|
})
|
||||||
|
const recoverableFromIpc = ref<{ id: string; title: string | null }[]>([])
|
||||||
|
async function refreshRecoverable() {
|
||||||
|
try {
|
||||||
|
const items = await aiApi.listRecoverable()
|
||||||
|
recoverableFromIpc.value = items.map(i => ({ id: i.id, title: i.title }))
|
||||||
|
} catch { /* IPC 失败退回 list 投影,不阻断面板 */ }
|
||||||
|
}
|
||||||
|
const recoverableTasks = computed(() => {
|
||||||
|
const seen = new Set<string>()
|
||||||
|
const merged: { id: string; title: string }[] = []
|
||||||
|
for (const item of [...recoverableFromList.value, ...recoverableFromIpc.value]) {
|
||||||
|
if (item.id === store.state.activeConversationId || seen.has(item.id)) continue
|
||||||
|
seen.add(item.id)
|
||||||
|
merged.push({ id: item.id, title: item.title || t('aiChat.untitledConversation') })
|
||||||
|
}
|
||||||
|
return merged.slice(0, 10)
|
||||||
|
})
|
||||||
async function switchToRunning(id: string) {
|
async function switchToRunning(id: string) {
|
||||||
runningTasksExpanded.value = false
|
runningTasksExpanded.value = false
|
||||||
if (id !== store.state.activeConversationId) {
|
if (id !== store.state.activeConversationId) {
|
||||||
@@ -593,6 +610,10 @@ function togglePanel(panel: 'goals' | 'history' | 'summary' | 'context' | 'runni
|
|||||||
const willOpen = !refs[panel].value
|
const willOpen = !refs[panel].value
|
||||||
goalsExpanded.value = historyExpanded.value = summaryExpanded.value = contextExpanded.value = runningTasksExpanded.value = false
|
goalsExpanded.value = historyExpanded.value = summaryExpanded.value = contextExpanded.value = runningTasksExpanded.value = false
|
||||||
refs[panel].value = willOpen
|
refs[panel].value = willOpen
|
||||||
|
// B3:任务面板展开时拉一次可恢复清单完备真相(补侧栏 limit 截断漏项);失败退回 list 投影
|
||||||
|
if (panel === 'running' && willOpen) {
|
||||||
|
void refreshRecoverable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextInfo = computed<ContextInfo>(() => {
|
const contextInfo = computed<ContextInfo>(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user