feat(ai-assistant): 新增 close_app 动作支持关闭当前应用窗口

打开微应用后 AI 页面操作默认锁定在该应用 iframe 内部,无法关闭应用本身
(关闭控件在主程序外壳层)。新增外壳层 close_app 动作绕过 iframe 作用域:

- action-executor 注册 close_app,先用 store 状态判断有无打开应用(无则报错
  不假报成功),再经事件总线 observeMicroApp:close 投递给 MicroApps 组件。
- MicroApps 复用现成 onAssistClose(findLast(isOpen)+closeByName)作为该事件
  处理器,关闭最前打开的应用,零重复逻辑。
- page-context-collector 在有微应用打开时向模型注入 close_app 可用动作。
- 同步 ai-kb page-action chunk 与 events-map。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kuaifan 2026-06-17 03:09:38 +00:00
parent ac2d4c6c4f
commit a19822a617
6 changed files with 112 additions and 81 deletions

View File

@ -3,11 +3,11 @@
> **本文件由脚本自动生成,请勿手改。**
>
> - 生成命令: `node scripts/gen-events-map.mjs`
> - 扫描范围: `resources/assets/js` 下所有 `.js` / `.vue` 文件(共 268 个)
> - 扫描范围: `resources/assets/js` 下所有 `.js` / `.vue` 文件(共 267 个)
> - 事件总线: `resources/assets/js/store/events.js`mitt 实例)
> - 仅匹配裸 `emitter.emit/on/off(` 调用;`xxx.emitter.emit(`(如 Quill 内部 emitter不属于本总线已排除
**29** 个静态可解析事件,**125** 处 `emitter.emit/on/off` 调用。
**29** 个静态可解析事件,**121** 处 `emitter.emit/on/off` 调用。
## 事件清单
@ -15,14 +15,14 @@
- **emit10**
- `resources/assets/js/App.vue:420`
- `resources/assets/js/pages/manage.vue:1236`
- `resources/assets/js/pages/manage.vue:1243`
- `resources/assets/js/pages/manage/application.vue:1188`
- `resources/assets/js/pages/manage/application.vue:1194`
- `resources/assets/js/pages/manage.vue:1192`
- `resources/assets/js/pages/manage.vue:1199`
- `resources/assets/js/pages/manage/application.vue:1181`
- `resources/assets/js/pages/manage/application.vue:1187`
- `resources/assets/js/pages/manage/components/ChatInput/index.vue:1882`
- `resources/assets/js/pages/manage/components/DialogView/index.vue:621`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2017`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2025`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2012`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2020`
- `resources/assets/js/pages/manage/messenger.vue:1219`
- **on1**
- `resources/assets/js/pages/manage/components/MeetingManager/index.vue:187`
@ -33,17 +33,17 @@
- **emit3**
- `resources/assets/js/pages/manage/calendar.vue:255`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:3513`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:3508`
- `resources/assets/js/pages/manage/components/ProjectPanel.vue:1357`
- **on1**
- `resources/assets/js/pages/manage.vue:621`
- `resources/assets/js/pages/manage.vue:592`
- **off1**
- `resources/assets/js/pages/manage.vue:641`
- `resources/assets/js/pages/manage.vue:610`
### `aiAssistantClosed`
- **emit1**
- `resources/assets/js/components/AIAssistant/index.vue:420`
- `resources/assets/js/components/AIAssistant/index.vue:442`
- **on1**
- `resources/assets/js/components/AIAssistant/float-button.vue:154`
- **off1**
@ -52,22 +52,12 @@
### `aiOperationRequest`
- **emit1**
- `resources/assets/js/store/actions.js:4781`
- `resources/assets/js/store/actions.js:4752`
- **on1**
- `resources/assets/js/components/AIAssistant/float-button.vue:155`
- **off1**
- `resources/assets/js/components/AIAssistant/float-button.vue:163`
### `approveDetails`
- **emit2**
- `resources/assets/js/pages/manage/approve/index.vue:497`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:3826`
- **on1**
- `resources/assets/js/pages/manage.vue:624`
- **off1**
- `resources/assets/js/pages/manage.vue:644`
### `clickAgainDialog`
- **emit1**
@ -80,30 +70,30 @@
### `createGroup`
- **emit3**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2871`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2866`
- `resources/assets/js/pages/manage/components/UserDetail.vue:288`
- `resources/assets/js/pages/manage/messenger.vue:1224`
- **on1**
- `resources/assets/js/pages/manage.vue:622`
- `resources/assets/js/pages/manage.vue:593`
- **off1**
- `resources/assets/js/pages/manage.vue:642`
- `resources/assets/js/pages/manage.vue:611`
### `dialogMsgPush`
- **emit1**
- `resources/assets/js/store/actions.js:4852`
- `resources/assets/js/store/actions.js:4823`
- **on2**
- `resources/assets/js/components/Mobile/Tabbar.vue:49`
- `resources/assets/js/pages/manage.vue:623`
- `resources/assets/js/pages/manage.vue:594`
- **off2**
- `resources/assets/js/components/Mobile/Tabbar.vue:53`
- `resources/assets/js/pages/manage.vue:643`
- `resources/assets/js/pages/manage.vue:612`
### `handleMoveTop`
- **emit2**
- `resources/assets/js/store/actions.js:2727`
- `resources/assets/js/store/actions.js:3720`
- `resources/assets/js/store/actions.js:2698`
- `resources/assets/js/store/actions.js:3691`
- **on2**
- `resources/assets/js/pages/manage/components/DialogModal.vue:41`
- `resources/assets/js/pages/manage/components/TaskModal.vue:49`
@ -111,43 +101,52 @@
- `resources/assets/js/pages/manage/components/DialogModal.vue:45`
- `resources/assets/js/pages/manage/components/TaskModal.vue:53`
### `observeMicroApp:close`
- **emit1**
- `resources/assets/js/components/AIAssistant/action-executor.js:234`
- **on1**
- `resources/assets/js/components/MicroApps/index.vue:145`
- **off1**
- `resources/assets/js/components/MicroApps/index.vue:151`
### `observeMicroApp:open`
- **emit1**
- `resources/assets/js/store/actions.js:5361`
- `resources/assets/js/store/actions.js:5321`
- **on1**
- `resources/assets/js/components/MicroApps/index.vue:144`
- **off1**
- `resources/assets/js/components/MicroApps/index.vue:149`
- `resources/assets/js/components/MicroApps/index.vue:150`
### `observeMicroApp:updatedOrUninstalled`
- **emit1**
- `resources/assets/js/store/mutations.js:429`
- **on1**
- `resources/assets/js/components/MicroApps/index.vue:145`
- `resources/assets/js/components/MicroApps/index.vue:146`
- **off1**
- `resources/assets/js/components/MicroApps/index.vue:150`
- `resources/assets/js/components/MicroApps/index.vue:152`
### `openAIAssistant`
- **emit7**
- `resources/assets/js/components/AIAssistant/float-button.vue:476`
- `resources/assets/js/components/SearchBox.vue:582`
- `resources/assets/js/pages/manage.vue:1267`
- `resources/assets/js/pages/manage.vue:1223`
- `resources/assets/js/pages/manage/components/ChatInput/index.vue:1925`
- `resources/assets/js/pages/manage/components/ReportDetail.vue:176`
- `resources/assets/js/pages/manage/components/ReportEdit.vue:267`
- `resources/assets/js/pages/manage/components/TaskAdd.vue:703`
- `resources/assets/js/pages/manage/components/TaskAdd.vue:710`
- **on1**
- `resources/assets/js/components/AIAssistant/index.vue:361`
- `resources/assets/js/components/AIAssistant/index.vue:382`
- **off1**
- `resources/assets/js/components/AIAssistant/index.vue:368`
- `resources/assets/js/components/AIAssistant/index.vue:389`
### `openAIAssistantGlobal`
- **emit1**
- `resources/assets/js/pages/manage.vue:1255`
- `resources/assets/js/pages/manage.vue:1211`
- **on1**
- `resources/assets/js/components/AIAssistant/float-button.vue:153`
- **off1**
@ -156,7 +155,7 @@
### `openDownloadClient`
- **emit1**
- `resources/assets/js/pages/manage.vue:1128`
- `resources/assets/js/pages/manage.vue:1092`
- **on1**
- `resources/assets/js/components/RightBottom.vue:73`
- **off1**
@ -165,25 +164,25 @@
### `openFavorite`
- **emit1**
- `resources/assets/js/pages/manage/application.vue:1061`
- `resources/assets/js/pages/manage/application.vue:1054`
- **on1**
- `resources/assets/js/pages/manage.vue:626`
- `resources/assets/js/pages/manage.vue:596`
- **off1**
- `resources/assets/js/pages/manage.vue:646`
- `resources/assets/js/pages/manage.vue:614`
### `openManageExport`
- **emit1**
- `resources/assets/js/pages/manage/application.vue:1108`
- `resources/assets/js/pages/manage/application.vue:1101`
- **on1**
- `resources/assets/js/pages/manage.vue:628`
- `resources/assets/js/pages/manage.vue:598`
- **off1**
- `resources/assets/js/pages/manage.vue:648`
- `resources/assets/js/pages/manage.vue:616`
### `openMobileNotification`
- **emit1**
- `resources/assets/js/pages/manage.vue:1641`
- `resources/assets/js/pages/manage.vue:1597`
- **on1**
- `resources/assets/js/components/Mobile/Notification.vue:38`
- **off1**
@ -201,20 +200,20 @@
### `openRecent`
- **emit1**
- `resources/assets/js/pages/manage/application.vue:1064`
- `resources/assets/js/pages/manage/application.vue:1057`
- **on1**
- `resources/assets/js/pages/manage.vue:627`
- `resources/assets/js/pages/manage.vue:597`
- **off1**
- `resources/assets/js/pages/manage.vue:647`
- `resources/assets/js/pages/manage.vue:615`
### `openReport`
- **emit1**
- `resources/assets/js/pages/manage/application.vue:1058`
- `resources/assets/js/pages/manage/application.vue:1051`
- **on1**
- `resources/assets/js/pages/manage.vue:625`
- `resources/assets/js/pages/manage.vue:595`
- **off1**
- `resources/assets/js/pages/manage.vue:645`
- `resources/assets/js/pages/manage.vue:613`
### `openSearch`
@ -227,11 +226,10 @@
### `openUser`
- **emit5**
- **emit4**
- `resources/assets/js/components/UserAvatar/index.vue:184`
- `resources/assets/js/pages/manage/approve/details.vue:546`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2940`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:4494`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:2935`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:4485`
- `resources/assets/js/pages/manage/messenger.vue:1229`
- **on1**
- `resources/assets/js/pages/manage/components/UserDetail.vue:166`
@ -241,7 +239,7 @@
### `receiveTask`
- **emit2**
- `resources/assets/js/pages/manage/components/ProjectPanel.vue:1768`
- `resources/assets/js/pages/manage/components/ProjectPanel.vue:1775`
- `resources/assets/js/pages/manage/components/TaskRow.vue:280`
- **on1**
- `resources/assets/js/pages/manage/components/TaskDetail.vue:738`
@ -251,16 +249,16 @@
### `streamMsgData`
- **emit1**
- `resources/assets/js/store/actions.js:4469`
- `resources/assets/js/store/actions.js:4440`
- **on1**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:946`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:943`
- **off1**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:956`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:953`
### `taskRelationUpdate`
- **emit1**
- `resources/assets/js/store/actions.js:4992`
- `resources/assets/js/store/actions.js:4963`
- **on1**
- `resources/assets/js/pages/manage/components/TaskDetail.vue:739`
- **off1**
@ -269,7 +267,7 @@
### `updateNotification`
- **emit2**
- `resources/assets/js/pages/manage.vue:1125`
- `resources/assets/js/pages/manage.vue:1089`
- `resources/assets/js/pages/manage/setting/index.vue:191`
- **on1**
- `resources/assets/js/components/RightBottom.vue:65`
@ -281,16 +279,16 @@
- **emit1**
- `resources/assets/js/components/RightBottom.vue:231`
- **on1**
- `resources/assets/js/pages/login.vue:217`
- `resources/assets/js/pages/login.vue:219`
- **off1**
- `resources/assets/js/pages/login.vue:222`
- `resources/assets/js/pages/login.vue:224`
### `userActive`
- **emit3**
- `resources/assets/js/store/actions.js:870`
- `resources/assets/js/store/actions.js:952`
- `resources/assets/js/store/actions.js:4769`
- `resources/assets/js/store/actions.js:841`
- `resources/assets/js/store/actions.js:923`
- `resources/assets/js/store/actions.js:4740`
- **on1**
- `resources/assets/js/components/UserAvatar/index.vue:43`
- **off1**
@ -299,21 +297,19 @@
### `websocketMsg`
- **emit1**
- `resources/assets/js/store/actions.js:4786`
- **on3**
- `resources/assets/js/pages/manage/approve/index.vue:380`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:945`
- `resources/assets/js/store/actions.js:4757`
- **on2**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:942`
- `resources/assets/js/pages/manage/components/FileContent.vue:202`
- **off3**
- `resources/assets/js/pages/manage/approve/index.vue:383`
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:957`
- **off2**
- `resources/assets/js/pages/manage/components/DialogWrapper.vue:954`
- `resources/assets/js/pages/manage/components/FileContent.vue:225`
## 动态事件名(无法静态解析)
以下调用的第一参数不是字符串字面量,无法静态解析事件名:
- `resources/assets/js/components/MicroApps/index.vue:365` — `emitter.emit(actionName...)`
- `resources/assets/js/components/MicroApps/index.vue:375` — `emitter.emit(actionName...)`
## 统计

View File

@ -22,7 +22,7 @@ negative:
- 一次只能操作当前会话所在的页面,不能开新标签页
- 关闭浏览器或切到别的标签页时,页面操作会断连失败
- 跨源(外部站点)微应用 iframe 的内部不可操作,仅同源微应用插件可
last_verified: v1.7.90
last_verified: v1.7.91
---
# AI 操作页面的机制
@ -37,6 +37,7 @@ AI 助手通过高层导航和低层元素操作两类能力操作用户当前
## 受支持的高层动作
- `open_task``open_dialog``open_project``open_file``open_folder`
- `navigate_to_dashboard / messenger / calendar / files`
- `close_app`:关闭当前打开的应用窗口(仅在有微应用打开时出现)。属外壳层动作,哪怕 AI 正停在该应用内部、甚至应用跨源读不到内部,也能直接关闭
## 受支持的低层元素动作
- `click``type``select``focus``scroll``hover`

View File

@ -22,7 +22,7 @@ negative:
- 只在当前浏览器标签内导航,不会新开标签
- AI 不能打开你没权限访问的资源(会被后端拒绝)
- 若任务被删AI 跳转后会落到 404 / 提示「任务不存在」
last_verified: v1.7.90
last_verified: v1.7.91
---
# 让 AI 帮我跳页面/打开任务
@ -37,6 +37,7 @@ last_verified: v1.7.90
- "帮我打开仪表盘"
- "跳到日历"
- "打开文件 ID 5678"
- "关闭这个应用" / "把当前应用关掉"(打开微应用插件后,可让 AI 直接关闭当前应用窗口,无需自己点关闭按钮)
## 支持的跳转目标
- 任务详情open_task / goto_task / navigate_to_task

View File

@ -11,6 +11,7 @@
import { resolveActiveContext } from './active-context';
import { selectBackend } from './input-backends';
import emitter from '../../store/events';
/**
* 创建操作执行器
@ -44,6 +45,9 @@ class ActionExecutor {
navigate_to_calendar: this.navigateToCalendar.bind(this),
navigate_to_files: this.navigateToFiles.bind(this),
// 关闭当前应用窗口(外壳层,不受 iframe 作用域限制)
close_app: this.closeApp.bind(this),
// 别名支持
goto_task: this.openTask.bind(this),
goto_project: this.openProject.bind(this),
@ -214,6 +218,23 @@ class ActionExecutor {
return { navigated: true, page: 'files' };
}
// ========== 应用窗口 ==========
/**
* 关闭当前打开的应用窗口最前那个
*
* 关闭属于外壳层动作不受 iframe 作用域限制投递事件给 MicroApps 组件
* 复用其现成的关闭逻辑先用 store 状态判断有无打开应用避免无应用时假报成功
*/
async closeApp() {
const hasOpen = (this.store?.state?.microApps || []).some(a => a && a.isOpen);
if (!hasOpen) {
throw new Error('当前没有打开的应用');
}
emitter.emit('observeMicroApp:close');
return { closed: true };
}
// ========== 元素级操作 ==========
/**

View File

@ -156,6 +156,15 @@ function getAvailableActions(routeName, store) {
},
];
// 关闭当前应用 - 仅在有微应用打开时可用(外壳层,不受 iframe 作用域限制)
const appWindowActions = [];
if ((store?.state?.microApps || []).some(a => a && a.isOpen)) {
appWindowActions.push({
name: 'close_app',
description: '关闭当前打开的应用窗口',
});
}
// 根据页面类型添加特定操作
const pageSpecificActions = [];
@ -231,7 +240,7 @@ function getAvailableActions(routeName, store) {
);
}
return [...pageSpecificActions, ...commonActions];
return [...appWindowActions, ...pageSpecificActions, ...commonActions];
}
/**

View File

@ -142,11 +142,13 @@ export default {
mounted() {
emitter.on('observeMicroApp:open', this.onOpen);
emitter.on('observeMicroApp:close', this.onAssistClose);
emitter.on('observeMicroApp:updatedOrUninstalled', this.onUpdatedOrUninstalled);
},
beforeDestroy() {
emitter.off('observeMicroApp:open', this.onOpen);
emitter.off('observeMicroApp:close', this.onAssistClose);
emitter.off('observeMicroApp:updatedOrUninstalled', this.onUpdatedOrUninstalled);
},
@ -664,7 +666,8 @@ export default {
},
/**
* 关闭之前判断助理
* 关闭之前判断助理同时作为 observeMicroApp:close 事件处理器
* AI 助手等调用方触发关闭最前打开的应用
* @returns {Promise<unknown>}
*/
onAssistClose() {