Files
ycode/docs/专家使用逻辑-REA核对-20261016.md
T

19 KiB
Raw Blame History

专家使用逻辑:REA 源码核对

2026-10-16;WorkBuddy 5.8.2。只读已保存 REA 导出,没有操作原版 UI、召唤、发消息或修改生产代码。下述是固定版本源码的控制流结论;未用行为实测覆盖所有并发、失败和窗口状态。

核心结论:没有按 expertId 搜索并任意复用历史会话的逻辑。市场默认召唤准备首页草稿;显式携带 targetConversationId / targetSessionId 才绑定指定会话。新输入框加号通过独立 provider 流程切换当前输入上下文,与市场共用 wb.experts.summon 底层,但不是同一个 React 入口。

证据与可复核锚点

已对以下两个 REA JSON 的全部文本重新计算 SHA-256:

  • .tmp/rea-runtime-scripts.json:339 模块;Evidence ev_717fa5e846e64d67ac19c8925d121bff632c0d3aefb3603b10858559cabb2dcb。
  • .tmp/rea-expert-runtime-scripts-20261016.json:329 模块;Evidence ev_e91787b1d4dea651582c6cf12827775b9068b0ee8b80c68ba08d4c1d87d94e90。

两个导出中本报告使用的同名模块 SHA 一致。原包基线 root SHA 为 035254bc76310ea46317433e4840602094543ef323d2d5c5aff7c131729de918。以下函数范围单位均为已导出 JS 的 UTF-16 code unit,start 包含/end 不包含,不是文件行号;以 Babel AST 核对具名函数范围。

模块简称 原 renderer/assets 文件 SHA-256
C contexts-BMeWSUHk.js 5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b
P workbuddy-provider-DkH2YFYM.js b588e875c469978f216beec86bdf4b6c33a31c0266f9bf996881b8dbf37b5dc7
H home-CC-czDJL.js 121d5a07acccb09e48f20f3a94f9fac8b10770e4a82bca1916d2cccd333f8d14
I src-DbE1_mgM.js 6c6ce63c93854783270e855572797d75e536e3b8d4548410e31fe9d147e1feb4
D desktop-sdk-setup-C2AUiJmx.js ac3bdeb6d81776491e175ddfae262e64f77d3dc9c0821d6075a91bf1b889dc23
L wb-input-add-B045GnIg.js 7842fa4321d5351bf6c822bbd0253e0d6d6e68cc9cfae3f2470b3d493c23daf4

市场召唤与会话目标

C.useExpertSummon$1 [3909983,3917240) 先调用 expert API 获取已准备的专家(createExpertApi [3560992,...] 明确 summonExpert => getExperts().summon),合并本地化名称、职业、默认提问、头像、plugin/agent/runtimeIdentity/source 等资料。随后按以下顺序分流:

目标 确切行为
合法 Office 返回目标 校验 conversation ID、window:office: window ID、returnConversationCreated 布尔值。新创建目标先 bind;通过 window message 投递带 suppressDefaultInitPrompt:true 的专家,再 focus 返回窗口。不会降级成任意其他会话。
targetConversationId returnExpertToConversation 绑定该 ID,导航回此会话并发布 prepare-input,preserveBlocks:true,expert 带 suppressDefaultInitPrompt:true。没有查找“以前和同专家聊过的会话”。
无 target session/conversation conversations.prepareInput({target:{type:'new'},contentBlocks,expert,...});SDK 导航 home、发布输入意图,不在此创建会话或发送。
targetSessionId 字符串 session.prepare({targetSessionId,context:{expert...}}),成功后导航指定 ID。C.useAdapterSession [4306763,4308361) 在 expert && targetSessionId 时调用 setSelectedExpertForSession,返回同一 ID。

C.bindExpertToConversation [3907799,3908569) 要求 conversations.get(id) 找到目标,否则抛错;经 UI config 或 core config 设置专家、来源与 runtime identity,然后将非 craft 模式改为 craft。returnExpertToConversation [3908569,3909521) 的普通返回带 expertSummonReturn,包含 expertId、defaultInitPrompt、inputSnapshotToken,之后以 preserveBlocks 发布输入选择。

C.readExpertSummonReturnIntent [3919686,3920377) 只读取导航 state 中明确给定的返回 ID;非法 Office 返回信息会丢弃 conversation target。C.useExpertSummonFlow [3920501,3924917) 将这些目标原样下传;没有“按 expertId 复用历史”的候选列表/匹配算法。

D.prepareInput [375609,378110) 对 target:new 导航 home。对当前 active conversation 的 submitMode:none 有专门就地更新输入分支;对非当前 conversation 携带 expert 的普通 prepareInput 则明确报“不支持”。市场返回现有会话通过独立 bind+input-intent 流程,不是绕过这项限制偷偷创建另一个会话。

H 首页创建回调 [43600,49000) 是后续按发送才调用 wb.conversations.create 的地方,携带 expert/tag/locale/mode 等;创建并配置成功后按发送前捕获的 revision 清 new-task expert,preparedContext 也只在对象仍是原对象时清除。不能把“已准备专家草稿”当成“已创建专家会话”。

新加号、旧加号与共享底层

新输入框 I.ExpertsSubmenu [1574199,1579192) 中普通列表和搜索结果都调用同一个 onSelect(expert,{position})。selected 只决定勾选显示,该函数没有相同 ID 点击短路。同步传输中/cloudStatus:checking 禁用行;搜索有 debounce、IME composition、失败重试;置顶与搜索列表都不改变召唤会话目标。

I.AddMenu 专家分支 [1603400,1604590):

  1. 保存旧 draft expert;需要 local/cloud transfer 时,先等待 provider.onChange 返回准备好的专家,才放入 draft。
  2. 普通选择先设置 {...expert,suppressAutoSummon:true} 并关闭菜单,再 await provider.onChange;失败恢复保存的旧 draft expert。
  3. “召唤更多”调用 provider.onSummonMore,不自动发消息。

连接路径是 C.useSharedWbInputProviders [4601705,...] → useWbExpertProvider [3957662,3961924) → useExpertSummon [3951518,3954856)。会话输入将 conversationId/onExpertChange 传给 provider;首页不传现有 conversationId,而由首页 selection owner 持有选择。useSharedWbInputProviders 还在非 silent 操作调用 shouldBlockExpertChange;被 team guard 拒绝会抛 Expert change blocked。

旧 L.ExpertsSubmenu [46657,约50700) 确实存在另一条路:普通最近项用 onSelect、点击已选 ID 只关菜单;搜索项用 onSummonSearchResult,返回 false 不关菜单。C.useExpertSearchSummon [4311266,4314603) 属于此类 legacy main-content 召唤流程,默认将当前 conversationId 作为 targetSessionId,失败按 summonTimestamp 恢复 ref/专家模式。

不能用旧菜单“已选项直接返回”规则替代新 cr 菜单;也不能用旧搜索独立入口推断新菜单搜索一定走市场 React flow。 本轮没有查看原版 UI,具体正在显示哪一个菜单实例需由运行时 REA 证据确定;源码中两套路径和新 home/conversation 接线均已明确。

默认提示词、用户草稿与清除

默认文本由输入 owner 的 onChange/applyPrefill 处理。I.ExpertChip [1693259,约1695700) 只负责显示/准备中状态、silent hydrate 和清除专家;它本身不写默认提示词。

场景 源码行为
市场默认进入首页 C.createExpertSummonInputSnapshot [3886918,3887267) 将文本判成 user / expert-default / empty。user 文本原 blocks 保留并加 suppressDefaultInitPrompt;其余情况保留非文本资源并置入新默认提示词。无新 default 时保留原 snapshot blocks。
首页直接选择 H.useHomeExpertSelection [61126,64079) 设置 expert/craft;允许 default 且没有非文本 block 时调用 applyPrefill(default)。存在非文本 block 时不会用该调用覆盖整组 blocks。
首页清除 chip 同一函数的 expert=null 分支设置 expert 空并 dismiss dependencies,没有 applyPrefill(null)。setExpert reducer 只修改 selection,因此不能宣称首页清专家必清默认文本。
会话直接选择 C.ConversationInputImpl 的 be [7073311,7074328) 将模式改 craft、写 config expertId;未 suppress 时 applyPrefill(default 或 null)。
会话清除 chip be 收到 null,清 config expertId、依赖 gate,并 applyPrefill(null);此调用仍受输入是否已由用户编辑保护。
已有会话/持久化恢复 suppressDefaultInitPrompt:true 用于避免恢复绑定时重新填默认文本;同一专家 hydrate 保留现有 suppression 标志。

I.createInputBoxApi [1298488,1300741) 的 applyPrefill 有具体保护:canAutoFill = draftEmpty || !userEditedFlag;用户 setText/appendText 或 origin:user 的 setBlocks 更新该标志。传 null 时只在可自动填情况下去除 text blocks,保留非文本资源;用户输入返回 skipped-not-autofilled。传新 blocks 时用户已编辑返回 skipped-user-input。这是输入来源状态机制,不能仅凭字符串等于 default 就认定可覆盖。

首页 scope 恢复则 suppressDefaultInitPrompt:true,suppressAutoSummon:true,不主动重新填默认文本。市场回现有 conversation 虽显式 suppress,另有一次性的 return-intent 消费流程负责决定该填什么,不能一概不填 default:C.ConversationInputImpl [7079170,7081100) 读取 expertSummonReturn,校验 snapshot.sourceSessionId(不匹配则消费 token/清导航 state、停止),按 conversationId:expertId:snapshotToken 防重复。用户文本 snapshot 原 blocks 恢复;empty/expert-default/无 snapshot 时有新 default 则保留 snapshot 非文本 blocks 并 applyPrefill 新 default;无 default 但有 snapshot 则恢复 snapshot blocks。最后消费 snapshot token、清 return state。绑定与输入恢复是两个步骤。

旧 main-content return-state 默认填充还检查当前文本是否已经是同一 default;不能将这一 legacy 检查当成新 ConversationInput be 或上述 return effect 的唯一规则。

同专家重复选择、并发与回滚

C.useExpertSummon [3951518,3954856) 的去重 key 是 conversationId 或 account scope : id : marketExpertId : updatedAt:

  • 相同 key 正在召唤:await 进行中的同一个 promise,不重复发起。
  • silent:true 且 lastSuccessfulKey 相同:跳过;silent 会加 suppressDefaultInitPrompt。
  • 普通用户再次选择同 ID,即使上一轮成功,也不按 ID 永久跳过;继续 summon。updatedAt、market ID、scope 改变同样影响 key。
  • silent && applicationId && !conversationId 有特殊仅更新当前选择、不做底层召唤的分支。
  • cloud transport 直接 await onChange 并写 cloud recents;local transport 乐观更新选择,再 summon、归一化数据、写 recents、await owner.onChange,最后提交。

每次选择/清除递增 generation;结果应用、loading 清理与 local failure 回滚先检查是否仍是最新 generation。clear 先使旧请求过期、清 preparing/last-success key,await owner.onChange(null),仅最新操作清 provider selection。没有看到此 hook 主动 AbortController 中断已在执行的 summon;它主要抑制旧结果应用,不等于取消下载/后台请求。

local 正常召唤 500ms 后仍未完才出准备 toast;成功后 preparing 状态延迟 400ms 清除,错误清 preparing、恢复调用前 selectedExpert 并 toast。相同 key 的并发请求复用、不同 key 的并发请求以最新 generation 为准;不是把整个产品所有状态做成原子事务。

边界必须如实保留:AddMenu catch 和 Chip clear catch 直接恢复捕获的旧 draft expert,没有在该组件层检查 generation;provider 有 generation 保护不证明任意 owner 切换/快速连点都绝对安全。召唤中 recents 写入也不属于选择回滚的事务。useHomeExpertSelection 的失败 snapshot 只覆盖 appExpert/storeExpert/storeMode,没有完整 blocks 快照。

首页另有独立 generation 防旧依赖结果覆盖新选择;依赖失败时仅当前操作恢复选择和模式,并显示警告。scope 切换会递增 generation、dismiss gate;team 选择在 cloud 首页会改 local task target 并保留 incoming expert。具体依赖授权由 gate 处理,不以“select 完成”冒充授权完成。

provider hydrate、持久化与发送前检查

新任务专家持久状态 C [613922,619317):按 account scope(user/enterprise/account type)隔离,并且 local/cloud 分槽。内存 cache+串行 storage write;无效存储项删除。加载请求带 load generation 和 mutation generation,晚到的旧读不能覆盖期间新选择。同 ID hydrate 合并元数据但保留 suppressDefaultInitPrompt/suppressAutoSummon,不增加 selection revision;普通 selection 改变增加 revision。clear-if-revision 同时核对 accountScope/revision,避免发送结束误清之后的新选择。

H.useRuntimeScopedExpertSelection [59422,60323):general scope 用上述持久状态;非 general scope 用按 selectionScopeKey 的内存 Map;切 scope 保存/恢复各自选择。isSameExpert 比较 id 和 localPath,不能据此宣称元数据、default 文本或活动 backend 都完全相同。

目录 hydrate C.useExpertDirectory [869768,874949) 同时处理 selected、recent、本地自定义与别名;selected official get 和 local custom get 通过 Promise.allSettled,official 成功优先、local fallback(resolveSelectedExpert [863201,863725))。异步目录 cleanup 标志防旧 effect 应用;按 id/marketExpertId 留住当前选中,不因部分列表加载暂时丢失。这个过程补 display/meta,与发起新召唤分开。

实际 runtime 另由 P 维护 selectedExpertBySession + __pending_expert__:

  • setSelectedExpertForSessionInternal [612975,614938) 保留同 ID 激活元数据,补齐可用指令;session 级 selection update 串行。不同注册插件先 disable 旧插件,再更新 craft/expert/runtimeIdentity;相同 craft/id/runtimeIdentity 可省 launchConfig 更新。这是 runtime 更新去重,不是新菜单普通点击去重。
  • setSelectedExpertForSessionIfEmpty [612787,612975) 在 hydration 前后都检查 session 是否已有 selection;expectedConfig 防覆盖。clear-if-current [615109,615331) 使用 expected expertId。
  • hydrateSelectedExpertForRuntime [431496,433326):my-experts 优先本地 get;否则按 source/enterprise/locale 拉 getExpert,补显示与 runtime identity。既无 legacy prompt、无 activation meta、也不是有效 custom root 时才 activatePlugin,最终仍无可用 instructions 抛错。
  • ensureUsableExpertForSelection [430965,431496) 的 hydration 错误在已有 name/profession 时有降级为该文本 prompt 的分支。因此仅 chip 显示成功不能证明原专家完整指令加载成功。
  • 加载已有会话 [388000,389700) 按持久化 expertId 恢复,2 次重试+初次,间隔500ms;isCurrentLoad 防旧 load 落地。失败记录 warning 并设 ID 文本 fallback,不能称原角色能力已完整恢复。
  • 发送方法在 [400400,...] / [406143,...] 先 await selection update,再 ensure launch config。ensureSessionLaunchConfigBeforePrompt [434623,...] hydrate 默认 3 次重试+初次,间隔500ms;失败时若已有 usable binding 保留,否则清无效 expertId/runtimeIdentity。该降级行为不同于直接在 UI 假显示绑定成功。

清除现有会话 P.clearSelectedExpertForSessionInternal [615331,615919) 删除选择、通知监听、disable session 插件、清 launch expert/locale/runtimeIdentity,随后 best-effort deactivate。模式仍写 craft;清 expert 不等于退回其他模式,也不清会话历史。

team、确认与取消

市场 useExpertSummonFlow 的 team/金融风险确认取消仅关闭 dialog、清 pending,不调用底层召唤。team 确认可按用户 scope 持久 suppress;金融确认是当天 suppress 或旧 forever 标志。原版确认逻辑包括 automation 的独立分支,不能默认所有入口均创建聊天。

C.useTeamExpertSwitchGuard [6065611,6066278) 检查该会话已使用 team 或有 team 历史;换到不同的非空 expertId 才阻止。clear(null) 不匹配这个换专家条件。召唤更多在已用 team 时同样要求确认。

确认后的 confirmTeamExpertSwitchAction [6064556,6064853) 清 preparedContext;有新 expert 则准备新首页专家 context/default,然后 goHome;召唤更多再导航 experts。它不在原 team 会话里直接替换专家。确认弹窗 closeOnEscape:false;取消不执行该动作。此处阻止“同一已使用团队会话任意换另一个专家”的行为,是与普通 agent 会话的重要差别。

对 YCode 对齐的具体约束与未验证项

  1. 市场默认准备新草稿、picker 的召唤更多显式带返回 owner、返回只绑定该 ID,与源码匹配;不做按 expertId 搜索历史复用。直接侧栏打开市场是否带 return target 取决于入口导航 state,不能一概处理。
  2. 如产品 store 将“同 ID”全部短路,尚缺原版普通重召唤和 updatedAt/runtimeIdentity 变化的恢复路径;至少区分 user/silent/inflight,以及内容版本和已激活状态。
  3. 提示词使用 owner/applyPrefill 状态处理;首页 clear 与会话 clear 源码不同。用户输入、附件、已自动填默认、无 default 的情况都要分别验收。
  4. 选择、依赖准备、backend runtime 激活、消息发送是不同状态。保留 source/runtimeIdentity 和逐会话绑定,旧请求晚到不能串 owner;不宣称原版所有组件回滚都已完全竞态安全。
  5. 本报告只静态核对已保存原版代码。未新增原版运行时证明:current UI 路由每个 legacy/new 分支、极速切换/账号切换、失败时默认文本最终画面、Office/云同步、team 确认后的全部 state 均仍需独立 REA 场景验收。未验证模型实际遵循专家角色。

建议以以下状态矩阵做自身产品回归,再用正式 REA 取得原版同场景行为;目前此矩阵仅验收建议,未声称全部运行:

操作 需核对的状态
侧栏直接进市场后召唤 首页草稿;0 次发送;不任意导航历史同专家会话
会话 picker 召唤更多再返回 指定原 conversationId;user blocks 保留;expert-default 换新 default;附件保留
user 点击当前专家 / silent hydrate user 可重召唤;silent 相同成功 key 去重;updatedAt 改变需要准备新内容
清 chip 首页/会话两种 owner;空文本/自动default/用户编辑/附件组合;clear 后晚到召唤不复选
A慢→B快 / A慢→clear / A失败→B 最新选择成立;不恢复过时owner;loading/toast/依赖gate和错误事实一致
首页 local/cloud 或账户切换 selection 槽隔离;旧load不覆盖新选择;发送后revision保护
重启恢复会话 source/runtimeIdentity/Agent/Skill绑定可恢复;不可只显示chip而失去原指令
已用 team 换专家/召唤更多 确认后新首页;取消不变;同专家和clear边界单独测

修改文件仅本报告。验证为:339+329 模块 SHA 全部匹配、相关具名函数 Babel AST 范围核对、调用点/参数/分支逐项阅读;未运行生产测试或修改原版/YCode 状态。