新增: 无人值守安全系列(H1-H3) + P5 审计日志与部署运维 + #14 异步委托
- H3 外部干预感知: run_agentic_loop 增 h3_watch_dir 参数, loop 启动取 git HEAD 基线, 每 5 轮探测, HEAD 被外部改写(切分支/reset/他话提交)即 emit_task_parked(stop_kind=external_change) + teardown 停靠; 4 调用方(发送/编辑/强制发送/审批续跑)从 allowed_dirs.persistent 排序取首目录, 修复初版 current_dir 恒空缺陷 - P5 审计日志: df-im-gateway audit.rs append-only SQLite(tunnel_audit 表, payload 4KB 截断, recent/by_request 查询), main.rs 8 打点点位(inbound/command/outbound), 写失败不阻断主链路, 17 tests passed - P5 部署运维: publish-im-gateway.ps1(musl 交叉编译→scp .new→systemd 原子切换→9191 验证→自动回滚) + systemd unit + nginx im.1216.top 反代 + 运维手册 - #14 异步委托: 受理/完成两段式 + 桌面任务卡(后台进行中徽章/轮次/切换) + 飞书双端同构, 关联缺陷 #8-#12 速清 验收: cargo check workspace EXIT 0, cargo test workspace 全绿(711+560+33+12...), vue-tsc EXIT 0
This commit is contained in:
@@ -332,6 +332,11 @@ export const aiApi = {
|
||||
return invoke('ai_list_recoverable')
|
||||
},
|
||||
|
||||
/** B4 恢复 parked 会话(断点续跑闭环:注入恢复引导消息,重走 spawn 正典链路) */
|
||||
recoverParked(conversationId: string): Promise<string> {
|
||||
return invoke('ai_recover_parked', { conversationId })
|
||||
},
|
||||
|
||||
/** 切换到指定对话 */
|
||||
switchConversation(conversationId: string): Promise<AiConversationDetail> {
|
||||
return invoke('ai_conversation_switch', { conversationId })
|
||||
|
||||
@@ -166,11 +166,13 @@
|
||||
</button>
|
||||
<template v-if="recoverableTasks.length">
|
||||
<div class="ai-running-tasks-sep"></div>
|
||||
<button v-for="task in recoverableTasks" :key="task.id" type="button" class="ai-running-task-item" @click="switchToRunning(task.id)">
|
||||
<span class="ai-running-task-dot ai-running-task-dot--parked"></span>
|
||||
<span class="ai-running-task-title">{{ task.title }}</span>
|
||||
<span class="ai-running-task-round ai-running-task-round--parked">{{ t('aiChat.recoverableTask') }}</span>
|
||||
</button>
|
||||
<div v-for="task in recoverableTasks" :key="task.id" class="ai-running-task-item ai-running-task-item--parked">
|
||||
<button type="button" class="ai-running-task-main" @click="switchToRunning(task.id)">
|
||||
<span class="ai-running-task-dot ai-running-task-dot--parked"></span>
|
||||
<span class="ai-running-task-title">{{ task.title }}</span>
|
||||
</button>
|
||||
<button type="button" class="ai-running-task-recover" @click="recoverParked(task.id)" :title="t('aiChat.recoverNow')">{{ t('aiChat.recoverNow') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -487,6 +489,21 @@ async function switchToRunning(id: string) {
|
||||
await switchConversation(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** B4:恢复 parked 会话(注入恢复引导消息续跑),成功后切回该会话 */
|
||||
const recoveringIds = ref(new Set<string>())
|
||||
async function recoverParked(id: string) {
|
||||
if (recoveringIds.value.has(id)) return
|
||||
recoveringIds.value.add(id)
|
||||
try {
|
||||
await aiApi.recoverParked(id)
|
||||
recoveringIds.value.delete(id)
|
||||
await switchToRunning(id)
|
||||
} catch (e) {
|
||||
recoveringIds.value.delete(id)
|
||||
console.warn('[topbar] 恢复失败:', e)
|
||||
}
|
||||
}
|
||||
const taskProgressText = computed(() => {
|
||||
const p = taskProgress.value
|
||||
if (!p) return ''
|
||||
@@ -1112,9 +1129,48 @@ onBeforeUnmount(() => {
|
||||
.ai-running-task-dot--parked {
|
||||
background: var(--df-warning, #e6a23c);
|
||||
}
|
||||
.ai-running-task-round--parked {
|
||||
/* B4 parked 行改双钮布局(切回 | 恢复),恢复钮琥珀描边醒目 */
|
||||
.ai-running-task-item--parked {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0;
|
||||
}
|
||||
.ai-running-task-item--parked:hover {
|
||||
background: none;
|
||||
}
|
||||
.ai-running-task-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 5px 0 5px 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: var(--df-text);
|
||||
}
|
||||
.ai-running-task-main:hover {
|
||||
background: var(--df-hover, rgba(128,128,128,0.08));
|
||||
}
|
||||
.ai-running-task-recover {
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--df-warning, #e6a23c);
|
||||
border-radius: 4px;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
color: var(--df-warning, #e6a23c);
|
||||
}
|
||||
.ai-running-task-recover:hover {
|
||||
background: var(--df-warning, #e6a23c);
|
||||
color: #fff;
|
||||
}
|
||||
.ai-history-inline-list {
|
||||
position: absolute;
|
||||
left: auto; right: 0;
|
||||
|
||||
@@ -270,6 +270,7 @@ export default {
|
||||
runningRounds: '{n} rounds running',
|
||||
untitledConversation: 'Untitled conversation',
|
||||
recoverableTask: 'Interrupted · recoverable',
|
||||
recoverNow: 'Resume',
|
||||
viewHistory: 'View history messages',
|
||||
viewSummary: 'View context summary',
|
||||
viewContext: 'View AI context',
|
||||
|
||||
@@ -271,6 +271,7 @@ export default {
|
||||
runningRounds: '进行中 {n} 轮',
|
||||
untitledConversation: '未命名会话',
|
||||
recoverableTask: '中断·可恢复',
|
||||
recoverNow: '恢复',
|
||||
viewHistory: '查看历史消息',
|
||||
viewSummary: '查看上下文摘要',
|
||||
viewContext: '查看 AI 上下文',
|
||||
|
||||
Reference in New Issue
Block a user