diff --git a/apps/df-miniapp/src/composables/useAiChat.ts b/apps/df-miniapp/src/composables/useAiChat.ts index 4cdf96b..9e49165 100644 --- a/apps/df-miniapp/src/composables/useAiChat.ts +++ b/apps/df-miniapp/src/composables/useAiChat.ts @@ -24,6 +24,10 @@ * - stop(): 停止生成(stop Command → device invoke ai_chat_stop) * - regenerate(): 重发最后一条 user 消息(重新 send_message) * - switchConversation(id): 切换会话(switch Command → device 切 active) + * - 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) */ import { ref, reactive, computed } from 'vue' @@ -428,6 +432,84 @@ function regenerate(): void { send(lastUserText) } +/** + * 审批工具调用(approve Command → relay → device invoke ai_approve) + * + * Phase3 §2.2 路由表:{cmd:"approve", args:{tool_call_id, approved}} + * 对应事件:AiApprovalRequired(挂起审批列表 pendingApprovals)。 + */ +function approve(toolCallId: string, approved: boolean): void { + if (!toolCallId) return + const cmd: MiniCommand = { + cmd: 'approve', + args: { + tool_call_id: toolCallId, + approved, + }, + } + wsClient.send(cmd) + // 乐观从挂起列表移除(对齐 AiApprovalResult 事件处理,避免 UI 卡 pending) + const idx = pendingApprovals.findIndex((p) => p.id === toolCallId) + if (idx >= 0) pendingApprovals.splice(idx, 1) +} + +/** + * 路径授权(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, + }, + } + wsClient.send(cmd) +} + +/** + * 继续循环(continue_loop Command → relay → device invoke ai_continue_loop) + * + * Phase3 §2.2 路由表:{cmd:"continue_loop", args:{conversation_id}} + * 对应场景:AiMaxRoundsReached 达最大轮次暂停后,用户选「继续」再跑一轮。 + */ +function continueLoop(conversationId?: string | null): void { + const convId = conversationId ?? activeConversationId.value + if (!convId) { + console.warn('[useAiChat] continue_loop 缺 conversation_id') + return + } + const cmd: MiniCommand = { + cmd: 'continue_loop', + args: { conversation_id: convId }, + } + wsClient.send(cmd) +} + +/** + * 停止循环(stop_loop Command → relay → device invoke ai_stop_loop) + * + * Phase3 §2.2 路由表:{cmd:"stop_loop", args:{conversation_id}} + * 对应场景:AiMaxRoundsReached 达最大轮次暂停后,用户选「停止」彻底终止。 + * 与 stop 区别:stop 是中断流式生成,stop_loop 是停止多轮 agent 循环。 + */ +function stopLoop(conversationId?: string | null): void { + const convId = conversationId ?? activeConversationId.value + if (!convId) { + console.warn('[useAiChat] stop_loop 缺 conversation_id') + return + } + const cmd: MiniCommand = { + cmd: 'stop_loop', + args: { conversation_id: convId }, + } + wsClient.send(cmd) +} + /** 切换会话(switch Command → device 切 active_conversation_id) */ function switchConversation(convId: string): void { activeConversationId.value = convId @@ -484,6 +566,10 @@ export function useAiChat() { regenerate, switchConversation, newConversation, + approve, + authorizeDir, + continueLoop, + stopLoop, handleEvent, friendlyError, } diff --git a/apps/df-miniapp/src/types/relay.ts b/apps/df-miniapp/src/types/relay.ts index f24353b..9260e99 100644 --- a/apps/df-miniapp/src/types/relay.ts +++ b/apps/df-miniapp/src/types/relay.ts @@ -83,7 +83,9 @@ export type ControlMessage = * - cmd:命令名(对齐 src-tauri command 函数名) * - args:命令参数(对齐各 command 的参数) * - * MVP 三条(send_message / stop / switch_conversation),其余审批/重试等留后续批。 + * Phase3 §2.2 路由表全覆盖(8 条): + * - send_message / stop / switch_conversation / regenerate(MVP 已发) + * - approve / authorize_dir / continue_loop / stop_loop(阶段3 补齐,见 useAiChat.ts) */ export interface MiniCommand { /** Tauri command 名 */ @@ -108,3 +110,38 @@ export interface SwitchConversationArgs { export interface StopArgs { conversation_id?: string | null } + +/** approve 命令参数(对齐 src-tauri ai_approve,Phase3 §2.2 路由表) */ +export interface ApproveArgs { + /** 待审批的工具调用 id(对应 AiApprovalRequired 事件的 id) */ + tool_call_id: string + /** 是否批准(true=批准执行,false=拒绝) */ + 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 */ + conversation_id: string +} + +/** stop_loop 命令参数(对齐 src-tauri ai_stop_loop,Phase3 §2.2 路由表) */ +export interface StopLoopArgs { + /** 霷停止循环的会话 id */ + conversation_id: string +}