新增: 知识库图召回激活扩散与治理续批

- 阶段2 图召回: list_neighbors_for_seeds(多种子 1-hop 展开,排除种子与 archived)+knowledge_search 结果附 related 段(权重降序限 5)
- 蒸馏合并: knowledge_distill.rs(archived 按 kind 分组 LLM 合订本+merges 边连组)
- V54 knowledge_edges 边表与渐进披露批配套迁移/模型字段
- 提炼链 related_ids 协议+检索补 id 字段
This commit is contained in:
lxy
2026-10-26 01:36:19 +08:00
parent a30f599fe2
commit 0dd95e0e6e
15 changed files with 953 additions and 135 deletions
+2 -55
View File
@@ -29,7 +29,6 @@
* - listSkills(): 拉技能列表(list_skills Command → AiSkillList 事件回流 skills)
* - listEntities(): 拉实体列表(list_entities Command → AiEntityList 事件回流 entities)
* - approve(toolCallId, approved): 审批工具调用(approve Command → device invoke ai_approve)
* - authorizeDir(toolCallId, decision): 路径授权(authorize_dir Command → device invoke ai_authorize_dir)
* - continueLoop(convId): 继续循环(continue_loop Command → device invoke ai_continue_loop)
* - stopLoop(convId): 停止循环(stop_loop Command → device invoke ai_stop_loop)
*/
@@ -235,7 +234,7 @@ const deviceOnline = ref<boolean>(false)
* 现机制(130s 持续 watchdog,非首事件清除):
* - send 后 resetWatchdog 启动
* - 活跃事件(AiTextDelta/AiAgentRound/AiToolCallStarted/Heartbeat/Retry)resetWatchdog 重置 130s
* - 终态(AiCompleted/AiError)/挂起(Approval/DirAuth/MaxRounds/Help)/断连 clearWatchdog 清除
* - 终态(AiCompleted/AiError)/挂起(Approval/MaxRounds/Help)/断连 clearWatchdog 清除
* - 超时(130s 无活跃事件)= 中途断 → flush + 错误气泡 + reset generating,用户可重发
*/
let streamTimer: ReturnType<typeof setTimeout> | null = null
@@ -312,7 +311,7 @@ function resetWatchdog(): void {
const APPROVAL_TIMEOUT_MS = 300_000 // 5 分钟
const _approvalTimers = new Map<string, ReturnType<typeof setTimeout>>()
/** 启动审批超时计时器(AiApprovalRequired/AiDirAuthRequired 触发)。 */
/** 启动审批超时计时器(AiApprovalRequired 触发)。 */
function startApprovalTimer(tcId: string, toolName: string): void {
if (_approvalTimers.has(tcId)) return // 幂等
const timer = setTimeout(() => {
@@ -768,37 +767,6 @@ export function handleEvent(event: AiChatEvent): void {
break
}
case 'AiDirAuthRequired': {
clearWatchdog() // 挂起等授权,停看门狗
startApprovalTimer(event.id, event.tool) // 5min 超时自动拒绝
// F-#95 P0-3:路径授权进 pendingApprovals(kind=path),chat 页渲染 once/always/deny 按钮
// (替代旧 system 文案降级「请在桌面端处理」,miniapp 成轻量审批终端,治 loop 卡死)。
const authInfo: AiToolCallInfo = {
id: event.id,
name: event.tool,
args: {},
status: 'pending_approval',
kind: 'path',
dir: event.dir,
path: event.path,
}
// req3 去重:同 AiApprovalRequired(sync_pending 重发 + 实时事件竞态防重复)
if (!pendingApprovals.value.some((p) => p.id === authInfo.id)) {
pendingApprovals.value.push(authInfo)
}
// 对齐 AiApprovalRequired:同步更新消息内工具卡片为 pending_approval + path 授权信息,
// 使 chat 页消息内联渲染 once/always/deny 按钮(单一渲染源,避免消息 tc 卡 running 态)。
const authTc = findToolCall(event.id)
if (authTc) {
authTc.status = 'pending_approval'
authTc.kind = 'path'
authTc.dir = event.dir
authTc.path = event.path
triggerRef(messages) // P1-5:路径授权态嵌套变更,刷新工具卡
}
break
}
case 'AiCompleted': {
stopping = false // F9:终态清停止标志
flushCurrentText()
@@ -1158,26 +1126,6 @@ function approve(toolCallId: string, approved: boolean): void {
}
/**
* 路径授权(authorize_dir Command → relay → device invoke ai_authorize_dir)
*
* Phase3 §2.2 路由表:{cmd:"authorize_dir", args:{tool_call_id, decision}}
* 对应事件:AiDirAuthRequired(决策:'once'=本次 / 'always'=永久 / 'deny'=拒绝)。
*/
function authorizeDir(toolCallId: string, decision: 'once' | 'always' | 'deny'): void {
if (!toolCallId) return
const cmd: MiniCommand = {
cmd: 'authorize_dir',
args: {
tool_call_id: toolCallId,
decision,
},
}
if (!wsClient.send(cmd)) {
// 发送失败:wsClient.send 返 false(未连接),提示用户操作未发出
uni.showToast({ title: '未连接,操作未发出', icon: 'none' })
}
}
/**
* 继续循环(continue_loop Command → relay → device invoke ai_continue_loop)
*
@@ -1475,7 +1423,6 @@ export function useAiChat() {
setActiveModel,
newConversation,
approve,
authorizeDir,
continueLoop,
stopLoop,
handleEvent,
+3 -28
View File
@@ -543,7 +543,7 @@ function handleSend(): void {
}
// —— 工具卡片 + 审批 UI(对齐桌面端 ToolCard,适配 mp-weixin 深色风格) ——
// 后端推 AiToolCallStarted/AiApprovalRequired/AiDirAuthRequired → useAiChat 收进
// 后端推 AiToolCallStarted/AiApprovalRequired → useAiChat 收进
// 消息 m.toolCalls(状态机)+ pendingApprovals。消息内联渲染工具状态 + 待审批按钮,
// miniapp 成轻量审批终端(单一渲染源,对齐桌面 ToolCard 嵌消息气泡)。
@@ -666,11 +666,6 @@ function onReject(tc: AiToolCallInfo): void {
ai.approve(tc.id, false)
}
/** 路径授权(path 类:once=本次 / always=永久 / deny=拒绝) */
function onAuthorize(tc: AiToolCallInfo, decision: 'once' | 'always' | 'deny'): void {
ai.authorizeDir(tc.id, decision)
}
/** 连接状态中文文案(F21:裸枚举字符串 disconnected/connecting 不可读) */
const STATUS_TEXT: Record<string, string> = {
connecting: '连接中…',
@@ -782,23 +777,14 @@ function tokenInOf(t: TokenUsage): number {
<text class="tool-arg-val">{{ a.val }}</text>
</view>
</view>
<view v-if="p.kind === 'path' && p.dir" class="tool-dir">
<text>📁 {{ p.dir }}</text>
</view>
<view v-if="p.reason" class="tool-reason">
<text>⚠ {{ p.reason }}</text>
</view>
<!-- risk 类:批准/拒绝 -->
<view v-if="p.kind !== 'path'" class="approval-actions">
<!-- 批准/拒绝 -->
<view class="approval-actions">
<button class="approval-btn approve" size="mini" @tap="onApprove(p)">批准</button>
<button class="approval-btn reject" size="mini" @tap="onReject(p)">拒绝</button>
</view>
<!-- path 类:本次/永久/拒绝 -->
<view v-else class="approval-actions">
<button class="approval-btn approve" size="mini" @tap="onAuthorize(p, 'once')">本次</button>
<button class="approval-btn always" size="mini" @tap="onAuthorize(p, 'always')">永久</button>
<button class="approval-btn reject" size="mini" @tap="onAuthorize(p, 'deny')">拒绝</button>
</view>
</view>
</view>
@@ -859,10 +845,6 @@ function tokenInOf(t: TokenUsage): number {
<text class="tool-arg-val">{{ a.val }}</text>
</view>
</view>
<!-- path 授权待授权目录 -->
<view v-if="tc.kind === 'path' && tc.status === 'pending_approval' && tc.dir" class="tool-dir">
<text>📁 {{ tc.dir }}</text>
</view>
<!-- 风险说明 -->
<view v-if="tc.reason" class="tool-reason">
<text>⚠ {{ tc.reason }}</text>
@@ -1494,13 +1476,6 @@ function tokenInOf(t: TokenUsage): number {
flex: 1;
word-break: break-all; /* 工具参数值(路径/长字符串)断行防撑开 */
}
.tool-dir {
margin-top: 4px;
}
.tool-dir text {
color: #6bd99a;
font-size: 12px;
}
.tool-reason {
margin-top: 6px;
padding: 4px 6px;
-12
View File
@@ -64,14 +64,6 @@ export type AiChatEvent =
| { type: 'AiHeartbeat'; conversation_id?: string | null }
| { type: 'AiMaxRoundsReached'; conversation_id?: string | null }
| { type: 'AiStreamRetry'; attempt: number; max_attempts: number; conversation_id?: string | null }
| {
type: 'AiDirAuthRequired'
id: string
tool: string
path: string
dir: string
conversation_id?: string | null
}
| { type: 'AiContextCleared'; conversation_id?: string | null }
| { type: 'AiCompressing'; conversation_id?: string | null }
// 治 Task#1:拆分手动/自动两变体(字段相同仅 type 字面量不同),桌面按 type 分流,
@@ -154,10 +146,6 @@ export interface AiToolCallInfo {
status: 'running' | 'completed' | 'failed' | 'pending_approval' | 'rejected'
reason?: string
diff?: string
/** 审批类型(risk 普通 / path 路径授权,对齐后端 ApprovalKind) */
kind?: 'risk' | 'path'
dir?: string
path?: string
}
/** 挂起审批项(复用 AiToolCallInfo 结构,miniapp 不实现审批 UI 仅展示) */
+2 -17
View File
@@ -86,9 +86,9 @@ export type ControlMessage =
* - cmd:命令名(对齐 src-tauri command 函数名)
* - args:命令参数(对齐各 command 的参数)
*
* 路由表全覆盖(14 条):
* 路由表全覆盖:
* - send_message / stop / switch_conversation / regenerate(MVP 已发)
* - approve / authorize_dir / continue_loop / stop_loop(阶段3 补齐,见 useAiChat.ts)
* - approve / continue_loop / stop_loop(见 useAiChat.ts;authorize_dir 已随 path_auth 退役)
* - list_conversations / load_messages(F-#95 扩展,会话列表/历史消息读取)
* - list_skills / list_entities(F-#95 联想扩展,技能/项目·任务·灵感列表,见 useAiChat.ts)
* - rename_conversation(会话重命名,2026-06-23)/ sync_pending(重连审批恢复,2026-06-23)
@@ -207,21 +207,6 @@ export interface ApproveArgs {
approved: boolean
}
/**
* authorize_dir 命令参数(对齐 src-tauri ai_authorize_dir,Phase3 §2.2 路由表)
*
* decision 取值:对齐 src-tauri ai_authorize_dir 的 decision 参数
* - 'once':本次授权(下次同路径仍需问)
* - 'always':永久授权(写入会话级信任目录)
* - 'deny':拒绝(本次不授权)
*/
export interface AuthorizeDirArgs {
/** 待授权的工具调用 id(对应 AiDirAuthRequired 事件的 id) */
tool_call_id: string
/** 授权决策('once' / 'always' / 'deny') */
decision: 'once' | 'always' | 'deny'
}
/** continue_loop 命令参数(对齐 src-tauri ai_continue_loop,Phase3 §2.2 路由表) */
export interface ContinueLoopArgs {
/** 需继续运行的会话 id */