新增: 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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user