新增: namespace淘汰治理与可恢复会话分组

- namespace 淘汰治理: pin 机制(关键产出 LRU 永不淘汰, 超池 50% 最旧降级) + EvictionLog 留档富提示(原工具名+内容摘要+重调指引) + owner 软隔离(无主最旧先淘); 子代理报告出口 pin 收编
- 错误路径广播 AiAgentFinished(status=error): 错误任务有终态, watcher/任务卡可收敛
- 桌面侧栏可恢复分组: parked 会话独立分组+点击恢复, 断点续跑读侧闭环
- 验收: cargo check 零 error + vue-tsc EXIT 0
This commit is contained in:
lxy
2026-10-06 23:48:45 +08:00
parent 1ce1360a0b
commit ece7cd3fa8
8 changed files with 369 additions and 17 deletions
+81 -2
View File
@@ -200,6 +200,32 @@
</div>
</template>
<!-- G5 可恢复分组(loop_state='parked',断点续跑闭环读侧;置于活跃分组后、归档前) -->
<template v-if="recoverable.length">
<div class="ai-conv-group-title">
<span class="ai-conv-recover-dot" aria-hidden="true"></span>
{{ t('aiChat.recoverableCount', { n: recoverable.length }) }}
</div>
<div
v-for="conv in recoverable"
:key="'r-' + conv.id"
class="ai-conv-item ai-conv-item--recoverable"
:class="{ 'ai-conv-item--active': conv.id === store.state.activeConversationId }"
tabindex="0"
:title="t('aiChat.recoverableHint')"
@click="onRecover(conv.id)"
@keydown.enter.prevent="onRecover(conv.id)"
>
<div class="ai-conv-item-main">
<span class="ai-conv-item-title">
{{ conv.title || t('aiChat.newConversation') }}
<span class="ai-conv-recover-badge">⏸</span>
</span>
<span class="ai-conv-item-time">{{ formatRelative(conv.updated_at) }}</span>
</div>
</div>
</template>
<!-- 归档分组(可折叠) -->
<template v-if="archivedConvs.length">
<div class="ai-conv-group-title ai-conv-group-title--fold" @click="store.toggleArchivedFold()">
@@ -289,6 +315,7 @@
import { ref, computed, nextTick, watch, onMounted, onBeforeUnmount, type ComponentPublicInstance } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAiStore } from '../../stores/ai'
import { aiApi } from '../../api/ai'
// V1 只读消费:跨会话待审批真相源(state.pendingApprovals 仅是活跃会话投影,后台会话挂起不进投影)
import { convPendingApprovals } from '../../composables/ai/aiShared'
// 无人值守边界徽标只读消费:getConvBoundary 读镜像(reactive Map,模板内读取可追踪;
@@ -579,6 +606,36 @@ const archivedConvs = computed(() =>
|| Number(b.updated_at) - Number(a.updated_at)),
)
// ── G5 可恢复分组(V49 断点续跑读侧:list_parked 桌面消费闭环最后一块) ──
// 数据源 ai_list_recoverable(loop_state='parked' 完备真相,不受侧栏 limit 截断);
// 点击走 ai_recover_parked(注入恢复引导消息,重走 spawn 正典链路)后自然切换会话。
const recoverable = ref<{ id: string; title: string | null; model: string | null; updated_at: string }[]>([])
const recoveringId = ref<string | null>(null)
async function loadRecoverable() {
try {
recoverable.value = await aiApi.listRecoverable()
} catch (e) {
console.error('[G5] loadRecoverable failed', e)
}
}
async function onRecover(id: string) {
if (recoveringId.value) return
recoveringId.value = id
try {
await aiApi.recoverParked(id)
await store.switchConversation(id)
showConvToast(t('aiChat.recoverableDone'))
await loadRecoverable() // 恢复后 loop_state 已迁 running,刷新清单
} catch (e) {
console.error('[G5] recover failed', e)
showConvToast(t('aiChat.recoverableFail', { msg: String(e) }))
} finally {
recoveringId.value = null
}
}
const searchResults = computed<AiConversationSummary[] | null>(
() => store.filteredConversations.value,
)
@@ -603,6 +660,8 @@ function onResizeStart(e: MouseEvent) {
}
onMounted(() => {
// G5 可恢复清单初载(parked 会话列表,断点续跑读侧)
void loadRecoverable()
// 点击外部关闭更多操作菜单
document.addEventListener('click', onActionsOutsideClick)
// 操作菜单 Esc 关闭(P2-6,与外部点击同生命周期)
@@ -862,9 +921,29 @@ body.ai-sidebar-resizing * {
0%, 100% { opacity: 0.35; transform: scale(0.85); }
50% { opacity: 1; transform: scale(1.15); }
}
/* V1: 跨会话待审批徽标(橙点计数,warning 色系与待审批语义一致;固定尺寸不挤标题换行) */
.ai-conv-approve-badge {
/* G5 可恢复分组(parked 会话,amber 系提示「需注意/可继续」语义;⏸ 徽标+分组标题小圆点) */
.ai-conv-recover-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 5px;
background: var(--df-warning, #f0c75e);
animation: ai-conv-gen-pulse 2s ease-in-out infinite;
}
.ai-conv-item--recoverable {
cursor: pointer;
}
.ai-conv-recover-badge {
flex-shrink: 0;
margin-left: 2px;
font-size: 10px;
line-height: 1;
color: var(--df-warning, #f0c75e);
cursor: help;
}
/* V1: 跨会话待审批徽标(橙点计数,warning 色系与待审批语义一致;固定尺寸不挤标题换行) */
.ai-conv-approve-badge { flex-shrink: 0;
min-width: 12px;
height: 12px;
padding: 0 3px;
+4
View File
@@ -21,6 +21,10 @@ export default {
moreActions: 'More actions',
archived: 'Archived',
archivedCount: 'Archived ({n})',
recoverableCount: 'Recoverable ({n})',
recoverableHint: 'Task parked by restart interruption; click to resume',
recoverableDone: 'Task resumed, running in background',
recoverableFail: 'Recover failed: {msg}',
emptyConversation: 'No conversations',
// UX-06 §3.1: conversation search (live title filter, Ctrl+K to focus)
searchPlaceholder: 'Search chats…',
+4
View File
@@ -21,6 +21,10 @@ export default {
moreActions: '更多操作',
archived: '已归档',
archivedCount: '已归档 ({n})',
recoverableCount: '可恢复 ({n})',
recoverableHint: '任务因重启中断停靠,点击恢复继续执行',
recoverableDone: '任务已恢复,继续后台执行',
recoverableFail: '恢复失败: {msg}',
emptyConversation: '暂无对话',
// UX-06 §3.1:对话搜索(实时过滤标题,Ctrl+K 聚焦)
searchPlaceholder: '搜索对话…',