chainlesschain 0.166.47 → 0.166.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-CDpT0WXV.js → AIOps-BLGEffkh.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BNfUljWx.js → ActionButton-Cz1z2VGD.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C9323DEx.js → Analytics-C3j9cf6q.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-CRrjb2db.js → AppLayout-BR9ERuCA.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-Db-DGqJS.js → Artifacts-CozJCm4y.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CJdHRJ0W.js → Audit-DC5g9Ra2.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-_RSCnTYs.js → BackgroundAgents-B1zrQ0dW.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-8GFQi-dM.js → Backup-ChVnfjpm.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C87NtgUZ.js → BaseInput-8_vLQsLf.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DcTMWS8G.js → Chat-Dct9bm6L.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-5ZDPTW_H.js → ChatBubbleRenderer-BvoSsCKh.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DM9EmYee.js → Checkbox-DZQIsCIC.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-M2YqDRNN.js → Codegen-CdzHxmnq.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-oB1LLxyf.js → Col-DdAN8LeW.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-TcVZK5tF.js → Community-CNgjT9MA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-XKmq-3K_.js → Compact-BtQiX6G-.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-C6CBiDRy.js → Compliance-OGX5ma3j.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Dz9CGqXH.js → Cowork-CgugQnGM.js} +4 -4
  20. package/src/assets/web-panel/assets/{Cron-CxJDgDVL.js → Cron-Bpg7cPFD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMP0JsaQ.js → Crosschain-D1hsuswf.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-I5IQeogb.js → DID-miDRsMBw.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-BfLW2MXj.js → Dashboard-CGcl8htP.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BK8EjAlh.js → Dropdown-BFaxTdVE.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-C3iqSv5I.js → EmailListRenderer-BQdGLZIO.js} +1 -1
  26. package/src/assets/web-panel/assets/EvolutionSettings-bMvgBk6r.js +1 -0
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-T19rnqm-.js → FamilyGuardDashboard-c1WVC_--.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CozrbPMy.js → Federation-DyAcFrQY.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-k1dCLNNJ.js → FormItemContext-CfwpfD2B.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DbaW_Jvt.js → GenericCardRenderer-DkJcbBpS.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-CQHRyEIt.js → Git-DgYIrPJh.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BMJYZgMy.js → Governance-BF1tYPJ6.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-B7mpNU7V.js → Inference-BFkwRIrU.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Br0Blg6B.js → KnowledgeGraph-DpCoqhBa.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-HIdk0S6M.js → Logs-DU2qAJmD.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-HxeocBHy.js → MarkdownRenderer-QWv7UJLq.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-Bkfq69UP.js → Marketplace-Dz9NUrfo.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-dtmx0bHO.js → McpTools-B67I3d5v.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-CrRlOuEf.js → Memory-crkElJSy.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-D4ZIZiAH.js → MobileBridge-BO20VbdX.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-C90Rw7GE.js → MobileProjects-Ba-3gZZd.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CC6k_uh4.js → Mtc-BAHZM084.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-T_CUIYUy.js → MtcAudit-CL3DzajL.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-N_e0RRWk.js → Multisig-BSWWzvWH.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-Iq3VzEv0.js → NLProgramming-fnxqXM_3.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-B5sKJUJt.js → Notes-qUZPIQXT.js} +4 -4
  47. package/src/assets/web-panel/assets/{NotificationSettings-K8UVp8qQ.js → NotificationSettings-x9Xrixqu.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-BWNyM-6m.js → OrderTableRenderer-BtMtQUt7.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-DMO1NJxI.js → Organization-C946Hzna.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-BBY6b31Z.js → Overflow-BYf8KIts.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DmsL0y0v.js → P2P-Cbqf5dnv.js} +2 -2
  52. package/src/assets/web-panel/assets/PdhVaultBrowser-rmxp0cuF.js +7 -0
  53. package/src/assets/web-panel/assets/{Permissions-CWcpRtWa.js → Permissions-CZBw45EW.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub--ZNDIdsn.js → PersonalDataHub-BKwUAt43.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-DCdj5NGb.js → Pipeline-DmFD2QW4.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BRKu7Q3E.js → Privacy-KpuwWvt5.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BJ7ozIAb.js → ProjectInit-Bt8UheX6.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-B2cVLiXW.js → ProjectSettings-2ZeAn9CP.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-Ce9r_W-p.js → Projects-zBuet8Zs.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-BDespvGZ.js → Providers-DcEAnw6L.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BUNO4fDU.js → QrScannerModal-qF7liYG9.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-D2TUqeBV.js → QuickAsk-qiESlVBJ.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BNNQPHBH.js → Recommend-Bk-cQyi9.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-D2t1V_Cm.js → RemoteSession-CwrekErS.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-DVK5hEqI.js → Reputation-Bmw_UQtW.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-BB9RTwmE.js → Row-CPdqZIUX.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-DmEO7liZ.js → RssFeed-D_W-xxvS.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-UwEYjnDB.js → Search-Hu0oDFj_.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-DebqSsMp.js → Security-DeRxUwmq.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CuuPts3C.js → Services-CsNmHFkf.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DbWpmbgf.js → Skeleton-gv_Vz0tN.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-BaK7SxzT.js → Skills-Dgg-_rCB.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-BFHv51Jb.js → Sla-Bpu-bsEJ.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-B9b1R6uK.js → SpeechSettings-CrhleHuF.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-BnO0CN02.js → SyncSettings-JHjUN6I9.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Dy01Vq49.js → Tasks-DKRWKkl9.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BCy2AMHc.js → Templates-leiWntLq.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-4Tx9y_Y1.js → Tenant--1bYulvn.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CPOoUH8J.js → Terminal-C0y6FvSS.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C85GlJzu.js → TimelineRenderer-CQK4PVv4.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-_xneFBGF.js → Tokens-DbIvF-DV.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-BizG4rTr.js → Trigger-CkXJ2vuq.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-Dh8EIn3n.js → Trust-B02WXJPw.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-C5NWoHWQ.js → UkeySign-VPMkfzmw.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-0YfrySL6.js → VideoEditing-C5ezUnah.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-BZVH8Ags.js → Wallet-CYfvYRrY.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-kNofa6vm.js → WebAuthn-DtKl-kpr.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-BNO34_j0.js → WorkflowEditor-DACITF-u.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-ifQ2FlwD.js → chat-qnygfnLi.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-1TL8H6EH.js → colors-D4mISLBD.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DBfRjs_T.js → compact-item-BBPfxYGL.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-Bs6V4iBf.js → createContext-Byx4ndHp.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DBy6gsuI.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-Bz6LMY_0.js → hasIn-CGjLkkn3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index--WvT2Il3.js → index-B8UpadHC.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DMVktlxG.js → index-B9jH-h4X.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BiZ7Nz5-.js → index-BN7_TSgU.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CtB45Hok.js → index-BNe2lCk6.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CNKHoXWI.js → index-B_fHPZ8E.js} +1 -1
  100. package/src/assets/web-panel/assets/index-BfEDXk_E.js +1 -0
  101. package/src/assets/web-panel/assets/{index-B5SJCUZ7.js → index-BklGH3YI.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-pOUFfRdN.js → index-BmKYaaGP.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BB1Af7Fv.js → index-BxQF1wCU.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-EK3XtMxA.js → index-ByA1NyFP.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-cNwsev6n.js → index-BzeBBAl5.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Cykt9BbN.js → index-C3abGsSW.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-f_7L9GvP.js → index-C9tcHs46.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BxqC3LiO.js → index-CD5Btq6p.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DOww5Qny.js → index-CFMWjuav.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-rGhS02f8.js → index-CMj8qmpX.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BQL_tldd.js → index-CVUJjQAT.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C7d6QlfR.js → index-CWhI8G14.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CXtiLagi.js → index-CZjNamSU.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C6c0j-Ki.js → index-CmHE-lY_.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DWPp-d07.js → index-CoA8WsVr.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DEPPzM8I.js → index-Cpg3AWRy.js} +1 -1
  117. package/src/assets/web-panel/assets/index-CrxUwNM8.js +1 -0
  118. package/src/assets/web-panel/assets/{index-DDhy4eCS.js → index-CsVuQsY1.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-vb1XIxEB.js → index-CydHMjwc.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CBh48_Mh.js → index-DH6iC5b2.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-84mi5rsv.js → index-DZXhKAk9.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BoCTDcn6.js → index-D_g9mA1N.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-B6RClMZl.js → index-DboTeuPF.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-XJIV7HEw.js → index-DfgpKd64.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C9QYxq61.js → index-Dmfe0L2Z.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-lUL3bD2z.js → index-Dzni_5Qw.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BXJXMaZe.js → index-J5JkStvY.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CXVBQ82k.js → index-Ne8UGllV.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-BhPDhmvN.js → index-O09jXbir.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DvM2xBUA.js → index-Y7el_3WS.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cu_V6ZJH.js → index-c_tBkIlU.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-vHtnQsnV.js → index-lKJWAmd4.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-Cn7EIWmI.js → index-xzRKTy0R.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DpRIjwTt.js → initDefaultProps-XuhfEIhB.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-CG4_9o-Z.js → motion-Cbxh6AxO.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Dku1fXtf.js → move-C7IEnFly.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-BvAVeX-3.js → mtc-parser-lLADx4LL.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DFu0x78D.js → omit-mIbic_k-.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-CmI-FNcK.js → pickAttrs-_xIYEqu3.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-CGERYewB.js → placementArrow-ClB-E0ny.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-D7G8bF5J.js → responsiveObserve-D36Bg2ut.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-B2sXwWXy.js → slide-CUuO08Bl.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-DI5yh0n3.js → statusUtils-BKahwLbv.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-deGJ5dp_.js → styleChecker-2pT-T8vy.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-OqHBXEmi.js → useFlexGapSupport-DyH5g7Rg.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-Dqgx0Qv_.js → useFs-CTOauB3X.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-Be-lveh2.js → usePersonalDataHub-B02JYIMe.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CFxB876W.js → vnode-Do22wQrc.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-BFOYrl4e.js → zoom-BfnL7rit.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/plugin.js +36 -5
  152. package/src/data/changelog.json +13 -0
  153. package/src/generated/cc-agent-protocol.schema.json +44 -1
  154. package/src/lib/background-agent-list-index.js +222 -0
  155. package/src/lib/background-agent-supervisor.js +38 -0
  156. package/src/lib/codex-app-server-adapter.js +1 -0
  157. package/src/lib/context-memory-kernel/provider-context.js +69 -4
  158. package/src/lib/deferred-question-context.js +160 -4
  159. package/src/lib/eval/plugin-suite.js +286 -22
  160. package/src/lib/session-host-snapshot.js +16 -0
  161. package/src/runtime/headless-stream.js +162 -13
  162. package/src/assets/web-panel/assets/EvolutionSettings-BJmBzpW6.js +0 -1
  163. package/src/assets/web-panel/assets/PdhVaultBrowser-C65AX_RY.js +0 -7
  164. package/src/assets/web-panel/assets/devWarning-BHnbnuYL.js +0 -1
  165. package/src/assets/web-panel/assets/index-CsRz9-ZH.js +0 -1
  166. package/src/assets/web-panel/assets/index-DnjMFf3m.js +0 -1
@@ -1,6 +1,17 @@
1
+ import { normalizeInteractionBinding } from "./interaction-binding.js";
2
+
1
3
  const MAX_DEFERRED_ANSWERS_PER_CALL = 8;
2
4
  const MAX_DEFERRED_ANSWER_CHARS = 16_384;
3
5
  const MAX_DEFERRED_QUESTION_CHARS = 2_048;
6
+ const MAX_DEFERRED_QUESTIONS = 32;
7
+ const MAX_DEFERRED_OPTIONS = 32;
8
+
9
+ export const DEFERRED_QUESTION_EVENTS = Object.freeze({
10
+ REQUESTED: "deferred_question_requested",
11
+ RESOLVED: "deferred_question_resolved",
12
+ CONSUMED: "deferred_question_consumed",
13
+ EXPIRED: "deferred_question_expired",
14
+ });
4
15
 
5
16
  function boundedText(value, maxChars) {
6
17
  let text;
@@ -18,6 +29,145 @@ function boundedText(value, maxChars) {
18
29
  return `${text.slice(0, maxChars)}\n[truncated]`;
19
30
  }
20
31
 
32
+ function cloneJson(value, fallback = null) {
33
+ try {
34
+ return JSON.parse(JSON.stringify(value));
35
+ } catch {
36
+ return fallback;
37
+ }
38
+ }
39
+
40
+ function normalizeBinding(value, sessionId = null) {
41
+ const binding = normalizeInteractionBinding(value);
42
+ if (
43
+ !Number.isSafeInteger(binding.sequence) ||
44
+ binding.sequence <= 0 ||
45
+ binding.sessionId == null ||
46
+ (sessionId != null && String(binding.sessionId) !== String(sessionId))
47
+ ) {
48
+ return null;
49
+ }
50
+ return binding;
51
+ }
52
+
53
+ function sameBinding(left, right) {
54
+ return JSON.stringify(left) === JSON.stringify(right);
55
+ }
56
+
57
+ export function normalizeDeferredQuestionRequest(value, sessionId = null) {
58
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
59
+ const questionId = String(value.questionId || value.requestId || "").trim();
60
+ const binding = normalizeBinding(value.binding, sessionId);
61
+ if (!questionId || questionId.length > 160 || !binding) return null;
62
+ return Object.freeze({
63
+ questionId,
64
+ question: boundedText(value.question || "", MAX_DEFERRED_QUESTION_CHARS),
65
+ options: Array.isArray(value.options)
66
+ ? cloneJson(value.options.slice(0, MAX_DEFERRED_OPTIONS), [])
67
+ : null,
68
+ multiSelect: value.multiSelect === true,
69
+ purpose: value.purpose === "preference" ? "preference" : "information",
70
+ binding,
71
+ requestedRevision: Number.isSafeInteger(
72
+ value.requestedRevision ?? value.contextRevision,
73
+ )
74
+ ? (value.requestedRevision ?? value.contextRevision)
75
+ : null,
76
+ });
77
+ }
78
+
79
+ export function normalizeDeferredQuestionAnswer(value, sessionId = null) {
80
+ const request = normalizeDeferredQuestionRequest(value, sessionId);
81
+ if (!request) return null;
82
+ return Object.freeze({
83
+ ...request,
84
+ answer: boundedText(value.answer, MAX_DEFERRED_ANSWER_CHARS),
85
+ resolvedRevision: Number.isSafeInteger(value.resolvedRevision)
86
+ ? value.resolvedRevision
87
+ : null,
88
+ });
89
+ }
90
+
91
+ function upsertBounded(map, key, value) {
92
+ map.delete(key);
93
+ map.set(key, value);
94
+ while (map.size > MAX_DEFERRED_QUESTIONS) {
95
+ map.delete(map.keys().next().value);
96
+ }
97
+ }
98
+
99
+ export function createDeferredQuestionEventReducer({ sessionId = null } = {}) {
100
+ const pending = new Map();
101
+ const answers = new Map();
102
+ let maxSequence = 0;
103
+ return {
104
+ accept(event) {
105
+ const data = event?.data || {};
106
+ if (event?.type === DEFERRED_QUESTION_EVENTS.REQUESTED) {
107
+ const record = normalizeDeferredQuestionRequest(data, sessionId);
108
+ if (
109
+ !record ||
110
+ pending.has(record.questionId) ||
111
+ answers.has(record.questionId) ||
112
+ record.binding.sequence <= maxSequence
113
+ ) {
114
+ return;
115
+ }
116
+ maxSequence = Math.max(maxSequence, record.binding.sequence);
117
+ upsertBounded(pending, record.questionId, record);
118
+ answers.delete(record.questionId);
119
+ return;
120
+ }
121
+ if (event?.type === DEFERRED_QUESTION_EVENTS.RESOLVED) {
122
+ const record = normalizeDeferredQuestionAnswer(data, sessionId);
123
+ const requested = record && pending.get(record.questionId);
124
+ if (
125
+ !record ||
126
+ !requested ||
127
+ !sameBinding(requested.binding, record.binding)
128
+ ) {
129
+ return;
130
+ }
131
+ pending.delete(record.questionId);
132
+ upsertBounded(answers, record.questionId, record);
133
+ return;
134
+ }
135
+ if (
136
+ event?.type === DEFERRED_QUESTION_EVENTS.CONSUMED ||
137
+ event?.type === DEFERRED_QUESTION_EVENTS.EXPIRED
138
+ ) {
139
+ const ids = Array.isArray(data.questionIds)
140
+ ? data.questionIds
141
+ : [data.questionId || data.requestId];
142
+ for (const value of ids) {
143
+ const questionId = String(value || "").trim();
144
+ if (!questionId) continue;
145
+ pending.delete(questionId);
146
+ answers.delete(questionId);
147
+ }
148
+ }
149
+ },
150
+ finish() {
151
+ return Object.freeze({
152
+ pendingQuestions: Object.freeze(
153
+ [...pending.values()].map((entry) => Object.freeze({ ...entry })),
154
+ ),
155
+ answers: Object.freeze(
156
+ [...answers.values()].map((entry) => Object.freeze({ ...entry })),
157
+ ),
158
+ maxSequence,
159
+ });
160
+ },
161
+ };
162
+ }
163
+
164
+ export function reduceDeferredQuestionEvents(events, options = {}) {
165
+ const reducer = createDeferredQuestionEventReducer(options);
166
+ for (const event of Array.isArray(events) ? events : [])
167
+ reducer.accept(event);
168
+ return reducer.finish();
169
+ }
170
+
21
171
  /**
22
172
  * Session-scoped inbox for answers to non-blocking, informational questions.
23
173
  * Answers are consumed once as transient user context; they are never persisted
@@ -75,15 +225,21 @@ export class DeferredQuestionContext {
75
225
 
76
226
  prepareCall({ currentRevision = null } = {}) {
77
227
  if (this._answers.length === 0) return null;
78
- const answers = this._answers.splice(0, MAX_DEFERRED_ANSWERS_PER_CALL);
228
+ const answers = this._answers.slice(0, MAX_DEFERRED_ANSWERS_PER_CALL);
79
229
  if (this._onConsumed) {
80
230
  try {
81
- this._onConsumed(answers.map((entry) => entry.questionId));
231
+ if (
232
+ this._onConsumed(answers.map((entry) => entry.questionId)) === false
233
+ ) {
234
+ return null;
235
+ }
82
236
  } catch {
83
- // Context delivery is authoritative for this process. A persistence
84
- // callback failure must not duplicate user context in the same run.
237
+ // Do not inject context unless its one-shot consumption is durably
238
+ // acknowledged. A later call can retry without duplicating the answer.
239
+ return null;
85
240
  }
86
241
  }
242
+ this._answers.splice(0, answers.length);
87
243
  const revision = Number.isSafeInteger(currentRevision)
88
244
  ? currentRevision
89
245
  : null;
@@ -7,6 +7,8 @@ import { encodeName } from "../plugin-runtime/scopes.js";
7
7
 
8
8
  export const PLUGIN_EVAL_SUITE_SCHEMA = "chainlesschain.plugin-eval-suite/v1";
9
9
  export const PLUGIN_EVAL_REPORT_SCHEMA = "chainlesschain.plugin-eval-report/v1";
10
+ export const PLUGIN_EVAL_HOLDOUT_SCHEMA =
11
+ "chainlesschain.plugin-eval-holdout/v1";
10
12
 
11
13
  const DEFAULT_SUITE_PATH = "evals/suite.json";
12
14
  const MAX_SUITE_BYTES = 1024 * 1024;
@@ -22,6 +24,7 @@ const MAX_ASSERTION_FILE_BYTES = 4 * 1024 * 1024;
22
24
  const MAX_RUNTIME_OUTPUT_BYTES = 16 * 1024 * 1024;
23
25
  const MAX_RUNTIME_TOOL_CALLS = 10_000;
24
26
  const MAX_TRIGGER_SIGNALS = 256;
27
+ const MAX_SAMPLE_RUNS = 20;
25
28
  const TASK_ID = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u;
26
29
  const EXPECTATIONS = new Set([
27
30
  "should_trigger",
@@ -35,6 +38,7 @@ const ASSERTION_TYPES = new Set([
35
38
  "file_contains",
36
39
  "json_equals",
37
40
  ]);
41
+ const ARM_ORDERS = new Set(["balanced", "control-first", "candidate-first"]);
38
42
 
39
43
  function digest(bytes, domain) {
40
44
  return `sha256:${crypto
@@ -122,6 +126,25 @@ function suiteRelativePath(root, suiteFile) {
122
126
  return relation.split(path.sep).join("/");
123
127
  }
124
128
 
129
+ function readBoundedJsonFile(file, label) {
130
+ const target = path.resolve(file);
131
+ const stat = fs.lstatSync(target);
132
+ if (!stat.isFile() || stat.isSymbolicLink()) {
133
+ throw new TypeError(`${label} must be a regular file, not a symlink`);
134
+ }
135
+ if (stat.size === 0 || stat.size > MAX_SUITE_BYTES) {
136
+ throw new RangeError(`${label} must be under ${MAX_SUITE_BYTES} bytes`);
137
+ }
138
+ const bytes = fs.readFileSync(target);
139
+ let value;
140
+ try {
141
+ value = JSON.parse(bytes.toString("utf8"));
142
+ } catch {
143
+ throw new TypeError(`${label} is not valid JSON`);
144
+ }
145
+ return { target, bytes, value: plainObject(value, label) };
146
+ }
147
+
125
148
  function collectPluginSnapshot(root) {
126
149
  const rootStat = fs.lstatSync(root);
127
150
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
@@ -214,6 +237,24 @@ function parseThreshold(value, label, { min = 0, max = 1 } = {}) {
214
237
  return value;
215
238
  }
216
239
 
240
+ function parseSampleRuns(value, label = "sampling.runs") {
241
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_SAMPLE_RUNS) {
242
+ throw new RangeError(
243
+ `${label} must be an integer between 1 and ${MAX_SAMPLE_RUNS}`,
244
+ );
245
+ }
246
+ return value;
247
+ }
248
+
249
+ function parseArmOrder(value, label = "sampling.armOrder") {
250
+ if (!ARM_ORDERS.has(value)) {
251
+ throw new TypeError(
252
+ `${label} must be balanced, control-first, or candidate-first`,
253
+ );
254
+ }
255
+ return value;
256
+ }
257
+
217
258
  function normalizeAssertion(value, taskId, index) {
218
259
  const assertion = plainObject(value, `task ${taskId} assertion ${index}`);
219
260
  if (!ASSERTION_TYPES.has(assertion.type)) {
@@ -414,6 +455,8 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
414
455
  }
415
456
  const thresholds = raw.thresholds || {};
416
457
  plainObject(thresholds, "plugin eval suite.thresholds");
458
+ const sampling = raw.sampling || {};
459
+ plainObject(sampling, "plugin eval suite.sampling");
417
460
  const maxCandidateCostUsd = parseThreshold(
418
461
  thresholds.maxCandidateCostUsd,
419
462
  "thresholds.maxCandidateCostUsd",
@@ -421,6 +464,7 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
421
464
  );
422
465
  return {
423
466
  schema: PLUGIN_EVAL_SUITE_SCHEMA,
467
+ pluginRoot: fs.realpathSync(root),
424
468
  identity: {
425
469
  name: manifest.metadata.name,
426
470
  version: manifest.metadata.version,
@@ -433,6 +477,10 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
433
477
  ),
434
478
  payload: snapshot,
435
479
  tasks,
480
+ sampling: {
481
+ runs: parseSampleRuns(sampling.runs ?? 1),
482
+ armOrder: parseArmOrder(sampling.armOrder || "balanced"),
483
+ },
436
484
  thresholds: {
437
485
  minPassRateDelta:
438
486
  parseThreshold(
@@ -450,6 +498,87 @@ export function loadPluginEvalDefinition(pluginRoot, { suiteFile } = {}) {
450
498
  };
451
499
  }
452
500
 
501
+ /**
502
+ * Attach reviewer-owned tasks from a file outside the plugin payload. The
503
+ * digest is reported separately so an author-controlled payload cannot mutate
504
+ * or silently absorb the holdout corpus.
505
+ */
506
+ export function attachPluginEvalHoldout(definition, holdoutFile) {
507
+ if (!holdoutFile) return definition;
508
+ const { target, bytes, value } = readBoundedJsonFile(
509
+ holdoutFile,
510
+ "plugin eval holdout",
511
+ );
512
+ const holdoutReal = fs.realpathSync(target);
513
+ const relation = path.relative(definition.pluginRoot, holdoutReal);
514
+ if (
515
+ relation === "" ||
516
+ (!relation.startsWith(`..${path.sep}`) &&
517
+ relation !== ".." &&
518
+ !path.isAbsolute(relation))
519
+ ) {
520
+ throw new TypeError(
521
+ "plugin eval holdout must be outside the plugin payload root",
522
+ );
523
+ }
524
+ if (value.schema !== PLUGIN_EVAL_HOLDOUT_SCHEMA) {
525
+ throw new TypeError(
526
+ `plugin eval holdout must use ${PLUGIN_EVAL_HOLDOUT_SCHEMA}`,
527
+ );
528
+ }
529
+ const identity = plainObject(value.plugin, "plugin eval holdout.plugin");
530
+ if (
531
+ identity.name !== definition.identity.name ||
532
+ identity.version !== definition.identity.version
533
+ ) {
534
+ throw new TypeError(
535
+ "plugin eval holdout identity must match the parsed plugin manifest",
536
+ );
537
+ }
538
+ if (
539
+ !Array.isArray(value.tasks) ||
540
+ value.tasks.length === 0 ||
541
+ value.tasks.length > MAX_TASKS
542
+ ) {
543
+ throw new RangeError(`plugin eval holdout requires 1-${MAX_TASKS} tasks`);
544
+ }
545
+ const holdoutTasks = value.tasks.map((task, index) => ({
546
+ ...normalizeTask(task, index),
547
+ source: "holdout",
548
+ }));
549
+ if (definition.tasks.length + holdoutTasks.length > MAX_TASKS) {
550
+ throw new RangeError(
551
+ `plugin eval author and holdout tasks may not exceed ${MAX_TASKS}`,
552
+ );
553
+ }
554
+ const ids = new Set(definition.tasks.map((task) => task.id));
555
+ for (const task of holdoutTasks) {
556
+ if (ids.has(task.id)) {
557
+ throw new TypeError(`duplicate plugin eval task id: ${task.id}`);
558
+ }
559
+ ids.add(task.id);
560
+ }
561
+ return {
562
+ ...definition,
563
+ tasks: [
564
+ ...definition.tasks.map((task) => ({
565
+ ...task,
566
+ source: task.source || "author",
567
+ })),
568
+ ...holdoutTasks,
569
+ ],
570
+ holdout: {
571
+ schema: PLUGIN_EVAL_HOLDOUT_SCHEMA,
572
+ // Reports may be shared as CI artifacts; do not disclose the reviewer's
573
+ // absolute workstation path. The digest is the authoritative binding.
574
+ path: path.basename(target),
575
+ digest: digest(bytes, "chainlesschain.plugin-eval-holdout-bytes/v1"),
576
+ tasks: holdoutTasks.length,
577
+ independentFromPayload: true,
578
+ },
579
+ };
580
+ }
581
+
453
582
  function materializePlugin(definition, workspace) {
454
583
  const nameDir = path.join(
455
584
  workspace,
@@ -555,6 +684,7 @@ function buildTasks(definition, { candidate }) {
555
684
  return definition.tasks.map((task) => ({
556
685
  id: task.id,
557
686
  description: task.description,
687
+ source: task.source || "author",
558
688
  prompt: task.prompt,
559
689
  expectedFiles: task.expectedFiles,
560
690
  ...(task.timeoutMs === undefined ? {} : { timeoutMs: task.timeoutMs }),
@@ -708,6 +838,7 @@ function projectArm(summary, definition, { candidate }) {
708
838
  metrics.pluginTriggered === false));
709
839
  return {
710
840
  ...result,
841
+ source: task.source || "author",
711
842
  expectation,
712
843
  pluginTriggered: metrics.pluginTriggered,
713
844
  activationEvidenceAvailable: metrics.available === true,
@@ -753,10 +884,110 @@ function reportArm(arm) {
753
884
  usage: arm.usage,
754
885
  totalCostUsd: arm.totalCostUsd,
755
886
  ms: arm.ms,
887
+ sampleRuns: arm.sampleRuns,
756
888
  results: arm.results,
757
889
  };
758
890
  }
759
891
 
892
+ function aggregateArms(arms) {
893
+ const results = arms.flatMap((arm, sampleIndex) =>
894
+ arm.results.map((result) => ({ ...result, sample: sampleIndex + 1 })),
895
+ );
896
+ const total = results.length;
897
+ const measured = results.filter((result) =>
898
+ Array.isArray(result.unrelatedChanges),
899
+ );
900
+ const tasksWithUnrelatedChanges = measured.filter(
901
+ (result) => result.unrelatedChanges.length > 0,
902
+ ).length;
903
+ const passed = results.filter((result) => result.pass).length;
904
+ const effectivePassed = results.filter(
905
+ (result) => result.effectivePass,
906
+ ).length;
907
+ const triggered = results.filter(
908
+ (result) => result.pluginTriggered === true,
909
+ ).length;
910
+ return {
911
+ results,
912
+ passed,
913
+ failed: total - passed,
914
+ total,
915
+ passRate: total > 0 ? passed / total : 0,
916
+ effectivePassed,
917
+ effectiveFailed: total - effectivePassed,
918
+ effectivePassRate: total > 0 ? effectivePassed / total : 0,
919
+ artifactChecksPassed: results.filter((result) => result.artifactCheckPassed)
920
+ .length,
921
+ executionsSucceeded: results.filter((result) => result.executionSucceeded)
922
+ .length,
923
+ tasksWithUnrelatedChanges,
924
+ unrelatedChangeRate: measured.length
925
+ ? tasksWithUnrelatedChanges / measured.length
926
+ : 0,
927
+ triggerRate: total > 0 ? triggered / total : 0,
928
+ activationEvidenceComplete: results.every(
929
+ (result) => result.activationEvidenceAvailable,
930
+ ),
931
+ ...sumUsage(results),
932
+ ms: arms.reduce((totalMs, arm) => totalMs + arm.ms, 0),
933
+ sampleRuns: arms.length,
934
+ };
935
+ }
936
+
937
+ // Wilson score intervals stay well behaved for the small and extreme samples
938
+ // common in plugin author evals (unlike p +/- z*sqrt(p(1-p)/n)).
939
+ function wilson95(successes, total) {
940
+ if (!Number.isSafeInteger(total) || total <= 0) return null;
941
+ const z = 1.959963984540054;
942
+ const z2 = z * z;
943
+ const p = successes / total;
944
+ const denominator = 1 + z2 / total;
945
+ const center = (p + z2 / (2 * total)) / denominator;
946
+ const radius =
947
+ (z * Math.sqrt((p * (1 - p) + z2 / (4 * total)) / total)) / denominator;
948
+ return {
949
+ lower: Math.max(0, center - radius),
950
+ upper: Math.min(1, center + radius),
951
+ };
952
+ }
953
+
954
+ function confidenceComparison(control, candidate) {
955
+ const controlInterval = wilson95(control.effectivePassed, control.total);
956
+ const candidateInterval = wilson95(
957
+ candidate.effectivePassed,
958
+ candidate.total,
959
+ );
960
+ if (!controlInterval || !candidateInterval) return null;
961
+ return {
962
+ method: "wilson-95-newcombe-difference",
963
+ control: controlInterval,
964
+ candidate: candidateInterval,
965
+ passRateDelta: {
966
+ lower: candidateInterval.lower - controlInterval.upper,
967
+ upper: candidateInterval.upper - controlInterval.lower,
968
+ },
969
+ };
970
+ }
971
+
972
+ function sampleArmSequence(sampleRuns, armOrder, suiteDigest) {
973
+ if (armOrder === "control-first") {
974
+ return Array.from({ length: sampleRuns }, () => ["control", "candidate"]);
975
+ }
976
+ if (armOrder === "candidate-first") {
977
+ return Array.from({ length: sampleRuns }, () => ["candidate", "control"]);
978
+ }
979
+ // Bind the first arm to the suite digest, then alternate. This balances
980
+ // provider drift without introducing an unrecorded random source.
981
+ const startsWithCandidate =
982
+ Number.parseInt(String(suiteDigest).slice(-2), 16) % 2 === 1;
983
+ return Array.from({ length: sampleRuns }, (_, index) => {
984
+ const candidateFirst = startsWithCandidate
985
+ ? index % 2 === 0
986
+ : index % 2 === 1;
987
+ return candidateFirst ? ["candidate", "control"] : ["control", "candidate"];
988
+ });
989
+ }
990
+
760
991
  export async function runPluginEval(
761
992
  definition,
762
993
  {
@@ -768,6 +999,8 @@ export async function runPluginEval(
768
999
  model = null,
769
1000
  dryRun = false,
770
1001
  minPassRateDelta,
1002
+ sampleRuns,
1003
+ armOrder,
771
1004
  onResult,
772
1005
  } = {},
773
1006
  ) {
@@ -777,30 +1010,49 @@ export async function runPluginEval(
777
1010
  ) {
778
1011
  throw new TypeError("controlRunAgent and candidateRunAgent are required");
779
1012
  }
780
- const runArm = async (arm, tasks, runAgent) =>
781
- runSuite(tasks, {
782
- runAgent: instrumentRunAgent(runAgent, definition.identity),
783
- keepWorkspaces,
784
- onResult: onResult ? (result) => onResult({ arm, result }) : undefined,
785
- });
786
- const control = projectArm(
787
- await runArm(
788
- "control",
789
- buildTasks(definition, { candidate: false }),
790
- controlRunAgent,
791
- ),
792
- definition,
793
- { candidate: false },
1013
+ const resolvedSampleRuns = parseSampleRuns(
1014
+ sampleRuns === undefined ? definition.sampling?.runs || 1 : sampleRuns,
1015
+ "sampleRuns",
794
1016
  );
795
- const candidate = projectArm(
796
- await runArm(
797
- "candidate",
798
- buildTasks(definition, { candidate: true }),
799
- candidateRunAgent,
800
- ),
801
- definition,
802
- { candidate: true },
1017
+ const resolvedArmOrder = parseArmOrder(
1018
+ armOrder === undefined
1019
+ ? definition.sampling?.armOrder || "balanced"
1020
+ : armOrder,
1021
+ "armOrder",
1022
+ );
1023
+ const runArm = async (arm, tasks, runAgent, sample) =>
1024
+ projectArm(
1025
+ await runSuite(tasks, {
1026
+ runAgent: instrumentRunAgent(runAgent, definition.identity),
1027
+ keepWorkspaces,
1028
+ onResult: onResult
1029
+ ? (result) => onResult({ arm, sample, result })
1030
+ : undefined,
1031
+ }),
1032
+ definition,
1033
+ { candidate: arm === "candidate" },
1034
+ );
1035
+ const armRuns = { control: [], candidate: [] };
1036
+ const sequence = sampleArmSequence(
1037
+ resolvedSampleRuns,
1038
+ resolvedArmOrder,
1039
+ definition.suiteDigest,
803
1040
  );
1041
+ for (const [sampleIndex, orderedArms] of sequence.entries()) {
1042
+ for (const arm of orderedArms) {
1043
+ const candidate = arm === "candidate";
1044
+ armRuns[arm].push(
1045
+ await runArm(
1046
+ arm,
1047
+ buildTasks(definition, { candidate }),
1048
+ candidate ? candidateRunAgent : controlRunAgent,
1049
+ sampleIndex + 1,
1050
+ ),
1051
+ );
1052
+ }
1053
+ }
1054
+ const control = aggregateArms(armRuns.control);
1055
+ const candidate = aggregateArms(armRuns.candidate);
804
1056
  const requiredDelta =
805
1057
  minPassRateDelta === undefined
806
1058
  ? definition.thresholds.minPassRateDelta
@@ -859,10 +1111,19 @@ export async function runPluginEval(
859
1111
  digest: definition.suiteDigest,
860
1112
  tasks: definition.tasks.length,
861
1113
  },
1114
+ holdout: definition.holdout || null,
862
1115
  selection: {
863
1116
  provider,
864
1117
  model,
865
1118
  dryRun: dryRun === true,
1119
+ sampleRuns: resolvedSampleRuns,
1120
+ armOrder: resolvedArmOrder,
1121
+ },
1122
+ sampling: {
1123
+ runs: resolvedSampleRuns,
1124
+ armOrder: resolvedArmOrder,
1125
+ sequence,
1126
+ deterministic: true,
866
1127
  },
867
1128
  thresholds: {
868
1129
  ...definition.thresholds,
@@ -877,6 +1138,9 @@ export async function runPluginEval(
877
1138
  ? "regression"
878
1139
  : "neutral",
879
1140
  },
1141
+ statistics: {
1142
+ confidence95: confidenceComparison(control, candidate),
1143
+ },
880
1144
  arms: {
881
1145
  control: reportArm(control),
882
1146
  candidate: reportArm(candidate),
@@ -28,6 +28,10 @@ import {
28
28
  } from "./session-message-provenance.js";
29
29
  import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
30
30
  import { normalizeSessionBudgetRootConfig } from "./session-budget-production-root.js";
31
+ import {
32
+ createDeferredQuestionEventReducer,
33
+ reduceDeferredQuestionEvents,
34
+ } from "./deferred-question-context.js";
31
35
 
32
36
  export const SESSION_HOST_SNAPSHOT_SCHEMA =
33
37
  "chainlesschain.session-host-snapshot/v1";
@@ -292,6 +296,7 @@ export function projectVerifiedSessionHostSnapshot(sessionId, events) {
292
296
  sessionId,
293
297
  verified: true,
294
298
  });
299
+ const deferred = reduceDeferredQuestionEvents(safeEvents, { sessionId });
295
300
  return Object.freeze({
296
301
  snapshot: projectSessionHostObservation({
297
302
  sessionId,
@@ -301,6 +306,9 @@ export function projectVerifiedSessionHostSnapshot(sessionId, events) {
301
306
  }),
302
307
  messages,
303
308
  recovery,
309
+ deferredQuestions: deferred.pendingQuestions,
310
+ deferredAnswers: deferred.answers,
311
+ deferredQuestionSequence: deferred.maxSequence,
304
312
  sessionBudgetRoot: sessionBudgetRootFromEvents(safeEvents),
305
313
  });
306
314
  }
@@ -316,6 +324,9 @@ function createStreamingSessionHostProjection(sessionId) {
316
324
  sessionId,
317
325
  verified: true,
318
326
  });
327
+ const deferredQuestionReducer = createDeferredQuestionEventReducer({
328
+ sessionId,
329
+ });
319
330
 
320
331
  return {
321
332
  accept(event) {
@@ -337,6 +348,7 @@ function createStreamingSessionHostProjection(sessionId) {
337
348
  lastEventType =
338
349
  typeof safeEvent.type === "string" ? safeEvent.type : null;
339
350
  mcpReducer.accept(safeEvent);
351
+ deferredQuestionReducer.accept(safeEvent);
340
352
  },
341
353
  finish({ headHash, eventCount, readMessages }) {
342
354
  structure.finish();
@@ -351,6 +363,7 @@ function createStreamingSessionHostProjection(sessionId) {
351
363
  projectCanonicalResumeMessages(recoveredMessages, { strict: true }),
352
364
  );
353
365
  const recovery = mcpReducer.finish();
366
+ const deferred = deferredQuestionReducer.finish();
354
367
  return Object.freeze({
355
368
  snapshot: projectSessionHostProjection({
356
369
  sessionId,
@@ -363,6 +376,9 @@ function createStreamingSessionHostProjection(sessionId) {
363
376
  }),
364
377
  messages,
365
378
  recovery,
379
+ deferredQuestions: deferred.pendingQuestions,
380
+ deferredAnswers: deferred.answers,
381
+ deferredQuestionSequence: deferred.maxSequence,
366
382
  sessionBudgetRoot,
367
383
  });
368
384
  },