ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

WordPress.com Agents Manager Custom Actions 跨 Bundle 桥接机制全解析:从 Public API 到事件广播

WordPress.com Agents Manager Custom Actions 跨 Bundle 桥接机制全解析:从 Public API 到事件广播 前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载本文以 WordPress.com 开源仓库 wp-calypso 中packages/agents-manager/src/hooks/custom-actions/README.md为骨架结合useSetupCustomActions/useRegisterCustomActions的源码实现与单元测试系统讲解 Agents Manager 如何通过window.__agentsManagerActions把聊天能力暴露给 React 树之外的代码其他 bundle、外部脚本以及宿主如何通过agents-manager-ready、agents-manager-conversation-changed与三个 agent activity 事件与其协作。读完你将掌握这套跨 bundle 桥的完整调用面、事件契约、扩展步骤与安全边界能够直接在自己的插件或宿主 bundle 中驱动 Agent 聊天。背景为什么需要一条跨 bundle 的桥Agents Manager智能体管理器是一个运行在 WordPress.com 各类宿主页面wp-admin、Gutenberg 编辑器、Woo AI 等上的聊天 Agent 产品。它的 React 组件树内部有自己的状态、路由和 store但页面上的其他代码——例如 Jetpack 的 AI 侧边栏、omnibar 的 AI 与帮助按钮、Woo AI 的批量操作卡片——并不在这棵 React 树里它们是独立打包、独立加载的 bundle。原文档开门见山地给出了这条桥的本质Warning:Cross-bundle bridge for the Big Sky migration. Actions live onwindow.__agentsManagerActions— dont expose anything sensitive.也就是说packages/agents-manager/src/hooks/custom-actions/这个目录的唯一职责是向window全局对象发布__agentsManagerActions让 React 树之外的代码能够驱动 Agents Manager。这是 Big Sky 迁移期间特意保留的兼容通道因此安全约束极为严格该全局对象对页面上所有脚本可见绝不能挂载特权操作或携带敏感数据这一点在源码与 README 中反复强调。两个 Hook 的分工README 用一张表概括了目录下两个核心 Hook 的职责Hook角色useSetupCustomActions随 Agent dock 挂载。注册内置动作并触发agents-manager-ready事件。useRegisterCustomActions允许任何组件把自己的动作发布到全局对象上。内部使用也用于新增动作。从源码 index.ts 可以看到两者的具体实现方式。useRegisterCustomActions通用的发布原语export function useRegisterCustomActions( actions: Partial AgentsManagerActions ): void { useEffect( () { const current ( window.__agentsManagerActions ?? {} as AgentsManagerActions ); Object.assign( current, actions ); return () { const latest window.__agentsManagerActions; if ( ! latest ) { return; } ( Object.keys( actions ) as ( keyof AgentsManagerActions )[] ).forEach( ( key ) { if ( latest[ key ] actions[ key ] ) { delete latest[ key ]; } } ); }; } ); }这里有几个值得注意的实现细节源码即文档没有 deps 数组Effect 在每次 commit 后都重新同步全局对象。注释解释了原因——动态计算 deps 数组必须保持固定长度而动作集合的键可能会变化所以干脆每次提交都Object.assign覆盖式合并。合并而非替换??只在全局尚未存在时初始化空对象随后用Object.assign把传入的动作合并进去保留其他发布者写入的键。值感知的清理value-aware cleanup卸载时并不直接delete所有键而是先检查latest[ key ] actions[ key ]——只有当键的值仍是自己发布的那个引用时才删除。这意味着如果另一个组件在你之后重写了同一个键你的清理逻辑会放过它从而保证全局对象在重叠挂载overlapping mounts场景下保持一致。useSetupCustomActions内置动作的装配点useSetupCustomActions从AGENTS_MANAGER_STOREwordpress/data读取hasLoaded、isOpen、isDocked、isMinimized、floatingPosition、isChatVisible等状态从useAgentsManagerContext()拿到getTabSessionId与resumeChat然后用react-router-dom的useNavigate拿到chatNavigate最后统一调用useRegisterCustomActions发布全部内置动作并在 Effect 中触发agents-manager-readyuseRegisterCustomActions( { getChatState, isChatVisible: getIsChatVisible, getCurrentRoute, getSessionId: getTabSessionId, getTabId, getTurnId, recordBigSkyTracksEvent: recordGuardedBigSkyTracksEvent, setChatOpen, setChatDocked, setChatEnabled, setChatCompactMode, setChatDesktopMediaQuery, setContextEntry: publishExternalContextEntry, removeContextEntry: removeExternalContextEntry, setContextCard: setExternalContextCard, removeContextCard: removeExternalContextCard, setSiteEditorAction, chatNavigate: navigate, resumeChat, isReady: true, broadcastsAgentActivity: true, } ); useEffect( () { window.dispatchEvent( new CustomEvent( agents-manager-ready ) ); }, [] );isReady: true与broadcastsAgentActivity: true在这里被显式装配正是为了让宿主读取这两个标记时能够信任事件链路已经接好。其中setChatOpen的实现尤其值得注意index.ts它会通过markActionOrigin( open | close, host )标记动作来源确保宿主调用导致的打开/关闭不会被 Tracks 误记为triggeruser同时只持久化真正发生变化的状态避免冗余保存与并发写互相覆盖的竞态。Public API完整的动作清单全局对象window.__agentsManagerActions的类型定义在 src/global.d.tsAgentsManagerActions接口运行时装配在 index.ts。README 给出的完整方法表如下全部动作均已实现并发布方法签名说明getChatState() Promise{ isOpen, isDocked, floatingPosition }当前聊天状态。等待 store 加载完成后再 resolve。getSessionId() string当前会话 ID。getTabId() string聊天 Tracks 事件上携带的tab_id宿主事件可据此做 join。getTurnId() string当前turn_id最近一次发送的首次发送前为。recordBigSkyTracksEvent(eventName: BigSkyEventName, props?) void以 Big Sky 基础属性集记录完整的jetpack_big_sky_*事件名。isChatVisible() boolean聊天是否可见打开且未最小化。getCurrentRoute() string聊天当前路由如/chat、/history、/support-guides。setChatOpen(isOpen: boolean) void打开或关闭聊天打开时同时从最小化栏展开。setChatDocked(isDocked: boolean) void停靠或取消停靠聊天。setChatEnabled(isEnabled: boolean) void启用聊天或禁用其输入框但保持聊天可见。setChatCompactMode(isCompact: boolean) void切换紧凑模式仅未停靠时。setChatDesktopMediaQuery(query: string) void决定聊天能否停靠进侧边栏的媒体查询。setChatInput*(value: string) void设置聊天输入框的值并聚焦。submitChatMessage*(message?: string) Promisevoid以编程方式提交消息省略参数则提交当前输入框内容。setContextEntry(entry) void添加或替换随下一条聊天消息发送的上下文条目。removeContextEntry(id: string) void移除上下文条目关联卡片contextEntryIds一并移除。setContextCard(card) void添加或替换聊天内展示的卡片。removeContextCard(id: string) void移除卡片。setSiteEditorAction(name, value) void记录一条 Site Editor 动作name → value供聊天读取。chatNavigateNavigateFunctionreact-router-dom的 navigate 函数支持路径选项或 delta。resumeChat() void重新打开聊天恢复本标签页的会话而非新建会话。isReadybooleanAPI 完全填充、可安全调用后为true。broadcastsAgentActivityboolean当前构建是否触发 agent activity 事件。*setChatInput与submitChatMessage仅在聊天面板挂载期间可用。即使isReady已经是true这两个方法也可能为undefined调用时务必使用可选链optional chaining。两个签名上的额外约束同样来自 README且在源码中得到印证recordBigSkyTracksEvent在较旧的 Agents Manager bundle 上不存在——同样要可选链调用。BigSkyEventName是模板字面量类型jetpack_big_sky_${ string }必须携带完整前缀。在 index.ts 的recordGuardedBigSkyTracksEvent中前缀不匹配、或裸前缀本身eventName BIG_SKY_EVENT_PREFIX的调用会被直接丢弃避免打出jetpack_big_sky_undefined之类的脏事件。聊天与反馈事件发送消息、建议渲染/点击、响应渲染/操作、点赞/点踩会同时以calypso_agents_manager_同后缀的名称配合共享属性记录其他事件名只按 Big Sky 名称记录。镜像映射表MIRRORED_BIG_SKY_SUFFIXES定义在 utils/tracks.ts包括chat_input_send_message、chat_suggestions_rendered、chat_suggestion_click、chat_response_rendered、chat_response_action、response_action_thumbs_up、response_action_thumbs_down七个后缀。几个值得展开的实现要点getChatState的等待语义源码中getChatState是useCallback包装的 Promise。store 未加载hasLoaded为 false时调用方会拿到一个 pending 的 Promiseresolve 回调被存入resolveRef配套的 Effect 在hasLoaded翻转为 true 后立即 resolve 该 Promise 并携带最新状态。单元测试 hooks/tests/custom-actions.test.ts 中resolves a pending getChatState once the store finishes loading正是验证这一等待行为。getTabId的会话拼接语义tab_id由 utils/tab-id.ts 生成首次使用生成 UUID 并写入sessionStorage在同一标签页内跨导航存活、标签页关闭即失效。由于会话 ID 由服务器在第一次回复时分配其前的聊天事件打开、建议、首次发送没有会话 ID只能携带tab_id后续事件会重复该 ID从而把前后两段事件拼接起来。复制的标签页共享sessionStorage因此会共享tab_id并恢复同一会话。setContextEntry/setContextCard的底层它们落在 utils/external-context.ts 的模块级Map上每次变更都会派发agents-manager-context-change事件并重建稳定快照entriesSnapshot/cardsSnapshot。快照必须稳定是为了兼容 React 18 的useSyncExternalStore——它靠快照引用的身份identity判断是否重渲染每次返回新数组会导致死循环。removeContextEntry与consumeNextMessageExternalContextEntries都会级联清理contextEntryIds中引用该条目的卡片实现“消费即清理”的双向一致。另外publishExternalContextEntry包装器在发布上下文时会额外记录calypso_agents_manager_context_published事件含source、type、delivery的归一化值把“宿主把某物交给聊天”这一交接动作纳入埋点旅程。getCurrentRoute的稳定引用技巧useSetupCustomActions用useRef保存最新locationlocationRef.current location在每个渲染期直接赋值getCurrentRoute只读取locationRef.current.pathname。这样发布出去的getCurrentRoute始终是同一函数引用对消费者缓存友好却总能报告当前路由。Ready signal先等事件、再查标记Agents Manager 挂载并填好 API 后会在window上触发一次agents-manager-ready事件。README 给出了关键的时间线规则在 Agents Manager 之前加载的宿主应当监听agents-manager-ready事件在 Agents Manager 之后加载的宿主应同步检查isReady——事件只会触发一次晚到的监听器永远等不到它。推荐的双保险写法README 原例function openChat() { window.__agentsManagerActions?.setChatOpen( true ); } if ( window.__agentsManagerActions?.isReady ) { openChat(); } else { window.addEventListener( agents-manager-ready, openChat, { once: true } ); }源码层面事件派发位于 index.ts 的 Effect 中仅在挂载时执行一次空 deps 数组isReady: true则由useRegisterCustomActions在挂载时写入。测试 hooks/tests/custom-actions.test.ts 中fires agents-manager-ready only once across re-renders验证了该事件跨重渲染只触发一次的行为。Conversation activity会话推进的再同步信号agents-manager-conversation-changed事件在会话推进时触发——每当消息被发送或接收、或一轮 turn 处理完毕。它的典型用途是宿主 bundle 在聊天转录transcript内部渲染了某些状态例如一张反映聊天中执行的批量操作状态的卡片需要在不整页刷新的前提下与最新会话重新同步。该事件不携带任何 detail触发时宿主应从自己的状态或 API 里读取所需数据function resync() { // re-fetch / re-render whatever the chat may have changed } window.addEventListener( agents-manager-conversation-changed, resync );README 特别强调与聊天交互时应优先使用这个事件而不是 provider 契约——provider 是留给 agent 配置setup用的。事件的派发点在 hooks/use-broadcast-conversation-activity.tsuseBroadcastConversationActivity( messageCount )以转录消息数为 key消息数非零时每次增长都派发一次事件消息数为 0 时跳过由OrchestratorChat随转录增长调用。以messageCount为 key 的设计保证了一条 turn 中途追加的 tool/agent 消息也会触发事件从而覆盖“从聊天执行/回滚”的用例。Agent activity区分 Agent 与用户的写入对于在聊天周围渲染自己的编辑界面的宿主典型如 Big Sky 的 easy mode最大的难题是页面上没有任何东西能区分“Agent 写入的改动”和“用户自己写的改动”——编辑器自身的 dirty 状态在挂载时也会变化一个页面打开造成的脏读与 Agent 编辑造成的脏读看起来完全一样。为此 Agents Manager 额外广播三个 window 事件事件detail触发时机agents-manager-turn-started—一轮 turn 开始——消息已发送Agent 正在处理。agents-manager-turn-ended—该轮 turn 结束或被中止。agents-manager-ability-completed{ name: string, ok: boolean }一个 ability 运行完毕——在其 resolve 或 throw 之后触发因此它写入的内容此时已落盘。ok在 throw 或返回success: false时为false。turn 事件是边edge而非状态它们只在变化瞬间触发中途才挂上的监听器在下一轮 turn 结束前什么都听不到。turn 的归属取自 agent manager 而非聊天——dock 只是其中的一条路由可能在流式传输期间卸载如果 turn 在聊天离开期间结束其结束事件要等聊天回来时才补发在此之前监听器会一直被告知 Agent 仍在工作。ability-completed的 detail 类型AbilityCompletedDetail{ name, ok }定义在 utils/agent-activity-events.ts三个事件的派发函数broadcastTurnEvent/broadcastAbilityCompleted也在此文件内部就是window.dispatchEvent( new CustomEvent( ... ) )turn 事件的广播由 hooks/use-broadcast-turn-activity.ts 在 turn 激活状态翻转时调用ability 完成广播则由 utils/ability-completion-broadcast.ts 在 ability resolve/throw 后调用。依赖这些事件之前必须先检查broadcastsAgentActivity在不广播的旧构建上任何事件都不会到达Agent 看起来永远沉默这正是 README 警告的“easy mode 会把无人认领的改动归咎于自己”的场景if ( window.__agentsManagerActions?.broadcastsAgentActivity ) { window.addEventListener( agents-manager-turn-started, () { // the agent is working — attribute what changes next to it } ); window.addEventListener( agents-manager-ability-completed, ( event ) { const { name, ok } event.detail; } ); }Initial values挂载前的初始值预设宿主可以在 Agents Manager挂载之前直接在window.__agentsManagerActions上预设三个初始值属性类型默认值说明isCompactModebooleanfalse初始紧凑模式状态。isChatEnabledbooleantrue初始聊天启用状态。desktopMediaQuerystringundefined侧边栏停靠用的初始媒体查询。源码印证agent-dock 在 components/agent-dock/index.tsx 初始化本地 state 时直接读取window.__agentsManagerActions?.isCompactMode ?? false与window.__agentsManagerActions?.desktopMediaQuery测试preserves pre-set initial values across mount验证了预设值在挂载后原样保留useSetupCustomActions的Object.assign合并不会覆盖它们因为内置动作键与这三个初始值键互不冲突。注意预设时要用展开语法保留已有键避免把其他发布者挂上的字段清掉见下方完整示例。实战示例驱动聊天、附加上下文、展示卡片README 给出了一个完整可复制的示例脚本覆盖读状态、驱动聊天、导航、恢复会话、附加上下文与卡片以及预设初始值// Read state const state await window.__agentsManagerActions.getChatState(); const sessionId window.__agentsManagerActions.getSessionId(); // Drive the chat window.__agentsManagerActions.setChatOpen( true ); window.__agentsManagerActions.setChatDocked( true ); window.__agentsManagerActions.setChatCompactMode( true ); window.__agentsManagerActions.setChatEnabled( false ); window.__agentsManagerActions.setChatDesktopMediaQuery( (min-width: 1200px) ); // Navigate within the chat window.__agentsManagerActions.chatNavigate( /chat, { state: { isNewChat: true }, replace: true, } ); window.__agentsManagerActions.chatNavigate( /history ); // Reopen the chat, resuming this tabs conversation window.__agentsManagerActions.resumeChat(); // Attach context to the next chat message window.__agentsManagerActions.setContextEntry( { id: my-plugin/current-report, type: my-plugin/report, title: Current report, delivery: next-message, data: { reportId: 123 }, } ); // Show a card linked to that entry. body is publisher-owned React; // Agents Manager only adds the dismiss button and the actions row. window.__agentsManagerActions.setContextCard( { id: my-plugin/current-report-card, contextEntryIds: [ my-plugin/current-report ], body: MyReportCard reportId{ 123 } /, actions: [ { label: Analyze report, type: submit, prompt: Analyze the attached report and recommend next steps., }, ], } ); // Pre-set initial values before mount window.__agentsManagerActions { ...window.__agentsManagerActions, isCompactMode: true, isChatEnabled: false, desktopMediaQuery: (min-width: 1200px), };结合源码对示例中的关键点做补充说明setContextEntry的delivery支持next-message下一条消息携带后即被消费、卡片联动清理与conversation持续存在于会话中默认next-messageExternalContextEntry还支持source、description、createdAt等可选字段类型见 src/global.d.ts 与 utils/external-context.ts。setContextCard的body是发布者自有的 React 节点Agents Manager 只负责渲染卡片框架、添加关闭按钮和动作行动作type支持prefill预填输入与submit带 prompt 直接提交。测试publishes actions onto the global after mount等用例验证了发布与清理路径。submitChatMessage/setChatInput记得始终可选链调用chatNavigate是react-router-dom的NavigateFunction支持(to, options)或数字 delta 两种调用形态。getChatState()返回的floatingPosition是聊天在浮动未停靠模式下的锚点位置setChatOpen(true)在已打开但最小化时先setIsMinimized(false)展开。新增一个动作三步走如果你需要在宿主侧给聊天增加新能力README 给出了明确的扩展流程扩展类型在src/global.d.ts的AgentsManagerActions接口中新增字段。实现并发布在实现所在处调用useRegisterCustomActions——通常放在useSetupCustomActions里但任何组件都可以。补充文档在上方 Public API 表格中登记新方法。用法示例import { useCallback } from wordpress/element; import { useRegisterCustomActions } from ../../hooks/custom-actions; function MyComponent() { const doSomething useCallback( ( arg: string ) { // ... }, [] ); useRegisterCustomActions( { doSomething } ); return null; }三条硬性规则每个键只有一个 owner。两个组件注册同一个键是 bug——后写入者会静默遮蔽先写入者。推荐保持稳定引用。用useCallback包装动作或把函数提升到模块作用域让发布出的函数保持稳定身份。这不是强制的——hook 在每次 commit 都会重新同步——但能避免无谓的抖动并保持任何消费者缓存引用的有效性。清理是值感知的。当其他人重写了你的键你的清理逻辑会放过它从而在重叠挂载下保持全局一致性这正是前文useRegisterCustomActions源码中latest[ key ] actions[ key ]检查的目的。注意事项全局对象对页面上的任何脚本都可见——不要暴露特权操作不要携带秘密数据。外部调用方应每次调用时重新读取window.__agentsManagerActions.foo而不是缓存函数引用——owner 重新注册时引用可能变化。测试视角行为契约的验证useSetupCustomActions与useRegisterCustomActions的行为在 hooks/tests/custom-actions.test.ts 中有系统性的用例覆盖可作为理解契约的补充证据Ready 语义挂载后isReady为trueagents-manager-ready恰好触发一次跨重渲染不重复卸载后setChatOpen与isReady都被清理。初始值保留预先设置的isCompactMode/isChatEnabled在挂载后原样存在且内置动作正常发布。getChatState 等待store 未加载时 Promise 保持 pending加载完成后 resolve 出正确的{ isOpen, isDocked, floatingPosition }。状态读取isChatVisible随 store 状态翻转getCurrentRoute返回当前location.pathnamegetTabId/getTurnId分别读取 tab-id 与 turn-id。发布与清理useRegisterCustomActions挂载后键可用、卸载后键消失、且合并而非替换既有键preserves pre-existing keys (merge, not replace)。这些测试与 index.ts、src/global.d.ts、utils/agent-activity-events.ts、utils/external-context.ts 共同构成了这套跨 bundle 桥的完整行为契约对外是一份稳定的窗口级 API 与事件契约对内是一组“值感知、合并优先、安全第一”的发布原语。小结packages/agents-manager/src/hooks/custom-actions/是 Agents Manager 面向页面其余部分的唯一公开表面。掌握window.__agentsManagerActions的调用面与agents-manager-ready、agents-manager-conversation-changed、agents-manager-turn-*、agents-manager-ability-completed四类事件你的宿主代码就能在不侵入 React 树的前提下读取聊天状态、驱动打开/停靠/导航、注入上下文与卡片、区分 Agent 与用户的写入并安全地扩展自己的动作。赞分享前端CMS【免费下载链接】wp-calypsoThe JavaScript and API powered WordPress.com项目地址https://gitcode.com/gh_mirrors/wp/wp-calypso点击查看免费下载相关推荐ToolJet Actions 事件动作机制全解析从事件触发到 RunJS 动态调用ToolJet Actions 事件动作机制全解析从事件触发到 RunJS 动态调用 导读 Actions动作是 ToolJet 应用构建器中连接事件低代码后端前端AI 应用MCP 服务NodeGui 事件与信号处理机制解析从 QPushButton 看 Qt 事件桥接到 Node.js 的完整实现NodeGui 事件与信号处理机制解析从 QPushButton 看 Qt 事件桥接到 Node.js 的完整实现 本文以 NodeGui 仓库中的 sign桌面应用跨平台发布订阅pybind11事件广播机制发布订阅pybind11事件广播机制 引言 在现代软件开发中事件驱动架构Event Driven Architecture已成为构建松耦合、高可扩展系统开发工具上一篇终极指南如何在Photoshop中免费解锁AI绘画超能力下一篇NSC_BUILDERSwitch游戏文件管理的瑞士军刀31项功能一站式解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表