新增: 知识库图召回激活扩散与治理续批
- 阶段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:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 仅展示) */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user