chainlesschain 0.162.177 → 0.162.178

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 (207) hide show
  1. package/README.md +10 -0
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DT-VPWuN.js → AIOps-DRftcSue.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-DyOreOVN.js → ActionButton-BFvUaMKR.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DslNsKux.js → Analytics-C0-Rru4P.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-bx-BCljj.js +9 -0
  8. package/src/assets/web-panel/assets/{Artifacts-DoaAtEcY.js → Artifacts-DAJ8B3Un.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DPG3WdJ9.js → Audit-hOk4L1lr.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-C_JO6R5G.js → BackgroundAgents-3pbDpM9z.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-Cv3BooyX.js → Backup-BtziGDn9.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-D2uQXWu-.js → BaseInput-kmshphJ3.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-B4Vczyf3.js → Chat-d7tGSosA.js} +5 -5
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-p-_PBpX_.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-qTgk4VeP.js → Checkbox-C4Eh4Tz5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DlLWcIzj.js → Codegen-r7gmhZyA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-DNO73qp7.js → Col-U7mDaGlL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-vRDNpc4j.js → Community-Bpu8aH1T.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-CEWtIXni.js → Compact-CHb2XBJe.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-KgYC9Fxi.js → Compliance-Cn_r4dey.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-zj5wvkpT.js → Cowork-CdMWfDcz.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-BLDoCozU.js → Cron-BtnREHWy.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BYnff4VY.js → Crosschain-BKI9TSzr.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-C75VFsVm.js → DID-pkNubzzJ.js} +2 -2
  25. package/src/assets/web-panel/assets/Dashboard-1rrz1snN.js +3 -0
  26. package/src/assets/web-panel/assets/{Dropdown-B5dbO4Kx.js → Dropdown-DLyFVxDc.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-BDipZtHJ.js → EmailListRenderer-CtUWfYVq.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DmIzt8jj.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-D9QOS-uW.js → Federation-D8lsDGus.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-B47HRwAm.js → FormItemContext-CRYt_aIw.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-3R4mXmgV.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DrNXZf1C.js → Git-BXj5Dney.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-BGd7JR98.js → Governance-Cm-I8RMz.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-D-2he1ed.js → Inference-CPvbhvgt.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-C0sHIbK5.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DiBQO-jL.js → Logs-BZY_3CRZ.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DL_5eV2D.js → MarkdownRenderer-up0BW6pv.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-VR59OdrC.js → Marketplace-C5MEJ2Qe.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DrPzwSd3.js → McpTools-ihdrn43I.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-CBgKALwQ.js → Memory-Br8bPMlh.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-pQ9Tn-Mn.js → MobileBridge-BNntgrYW.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-mASB6jyj.js → MobileProjects-BdV915mw.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-DTMgE7HC.js → Mtc-A3wDmlqL.js} +2 -2
  44. package/src/assets/web-panel/assets/{MtcAudit-CwTnCtiL.js → MtcAudit-CF-mBLWM.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-ngm6WU8f.js → Multisig-Iy2k_MSo.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-4YO4Qww9.js → NLProgramming-kk0W7HUb.js} +1 -1
  47. package/src/assets/web-panel/assets/Notes-B5u5t_4S.js +7 -0
  48. package/src/assets/web-panel/assets/{NotificationSettings-r2H8yPKb.js → NotificationSettings-BN0POf6_.js} +1 -1
  49. package/src/assets/web-panel/assets/OrderTableRenderer-CHkSUrXB.js +1 -0
  50. package/src/assets/web-panel/assets/{Organization-DrO7jRWS.js → Organization-DwWQESuN.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-Cnwu4i4t.js → Overflow-DG6klrcC.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-zi-cDwA_.js → P2P-BwJcKkgZ.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-DBEryDv7.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
  54. package/src/assets/web-panel/assets/{Permissions-lqXK2C3x.js → Permissions-CGMXe4TX.js} +4 -4
  55. package/src/assets/web-panel/assets/PersonalDataHub-ziYqg7-A.js +8 -0
  56. package/src/assets/web-panel/assets/{Pipeline-Kep5KAGW.js → Pipeline-BzJfeoGt.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CHdvOAh1.js → Privacy-DLh3Bblx.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-BhKfdwe9.js → ProjectInit-USVWW36c.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Yavk9K8A.js → ProjectSettings-CiiOn62h.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-6V-PNmcA.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-Bfym6SQn.js → Providers-CBPWWzpu.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-CLSQcrlR.js → QrScannerModal-QVwoRUq3.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-VPa2WKQN.js → QuickAsk-CXnR-2Vn.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-CGXR8jVU.js → Recommend-ClaNzjmF.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-C_20YfMB.js → RemoteSession-C3rGw8vJ.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-awZh68UV.js → Reputation-Ci-OWAoP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Ctb4oiYN.js → Row-DRhAwXOS.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-D3sdjvUV.js → RssFeed-BQLJQ2_y.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-C-zfA4kT.js → Search-QQUpkfFd.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-D9XlmHA3.js → Security-DdXKzuKT.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-C3UKnr41.js → Services-x2Zh62Qd.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-BQxagZfF.js → Skeleton-GdY4KwRz.js} +1 -1
  73. package/src/assets/web-panel/assets/Skills-Bv4tY1Fd.js +1 -0
  74. package/src/assets/web-panel/assets/{Sla-DveENvek.js → Sla-C3QnM-11.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BFjXLNgF.js → SpeechSettings-Bl3ytvmI.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-BpHgbaP9.js → SyncSettings-Wp9IwTsP.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-CF3rIrDe.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-DEiaoyve.js → Templates-CGfvVVM9.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-ITg774nY.js → Tenant-CVXmwQ3U.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-D3ZBcvnb.js → Terminal-bvwrt03E.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BsLU6JjU.js → TimelineRenderer-D0g74KhH.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Cb5Pquc9.js → Tokens-BH4IwzlT.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-Dwfy-m9N.js → Trigger-DJOpM533.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CnlSRGG0.js → Trust-B43EPRLr.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-Y4TlvKVo.js → UkeySign-BmBFG-em.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-BpKLtTTO.js → VideoEditing-n9oJwB8k.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-LLBzIq8x.js → Wallet-D5QeivHp.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BeSRiusP.js → WebAuthn-DcNy7igO.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-BK_l1tXO.js → WorkflowEditor-BVjc7-5-.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-DYd-X7Wg.js → chat-Dy5Tfj3m.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-C9uip71j.js → colors-C4c2CNZU.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-JBrfjUP6.js → compact-item-DPa3O5vN.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-D03fbuiq.js → createContext-TLcfNA6_.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-BAwdDA_5.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DLQOR6l-.js → hasIn-D41LVFkp.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-rIxxfdTN.js → index-0oevYjfT.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CChaISMb.js → index-388pLGJ1.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BfOs_bmg.js → index-B8nLBZbv.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-D-1SqEpQ.js → index-BPTGAjee.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BYv-LWdy.js → index-BPtrOBmW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cpn19c9k.js → index-BQiIxHBy.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BpYKLrUT.js → index-BSM1cHVK.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CS63r8h3.js → index-BYPr9fkz.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Q7mmL6w4.js → index-BsQ7Ev6i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-SO6lgMg0.js → index-Btk9qLL2.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-iahNhZwm.js → index-BvmziZSG.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BnQgRjsm.js → index-BvopLC_T.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ba-Ps_AJ.js → index-C7xhTVyP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BuNFCZCx.js → index-CNk_ZooP.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CL0ixCxk.js → index-CQrEHoOG.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CChQBM1d.js → index-CSxex23J.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-5YN7F5bO.js → index-CVjgm992.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CwoDpKzW.js → index-CWK4zOPe.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BcBZwlnG.js → index-CX0VsoP4.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B46YcgsV.js → index-CXl0YPNG.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-l08afIbV.js → index-Cip7LWml.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-6GuLRpBQ.js → index-Cq_hlWNt.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DYLZY4uh.js → index-CzZ49YZL.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B0X76LjX.js → index-D2Nkivlm.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BdH9l8wl.js → index-DBUgRs4D.js} +1 -1
  121. package/src/assets/web-panel/assets/index-DEjVkdzq.js +1 -0
  122. package/src/assets/web-panel/assets/{index-D4UY5Z60.js → index-DNsBj2Ob.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Ce0Bf1YF.js → index-Di4X8cfI.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-ByHO3SqZ.js → index-DljFsVGI.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C2zmOsyD.js → index-Korc3QIe.js} +1 -1
  126. package/src/assets/web-panel/assets/index-LMOc5xrS.js +1 -0
  127. package/src/assets/web-panel/assets/{index-BNCdRmWf.js → index-MKZDje0H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D8KvuDz0.js → index-OHAfRu_3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DaokIHt3.js → index-WEJr8PL5.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CILVyK3b.js → index-WinCp6du.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CVZWiUE_.js → index-Yjr6cqSK.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CqWk1FLG.js → index-iQ6gtFih.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMAAUVk.js → index-pt3s7yvV.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Ck17i0OD.js → index-tQ3igERQ.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-CG9z1c6w.js → initDefaultProps-BFZu87WZ.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-B1Ky7Zff.js → motion-DJIo7qvt.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-DME8X2P1.js → move-Cfr5qknL.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cuJYSWB7.js → mtc-parser-Bgc-OadH.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-UI_EE0uZ.js → omit-X3WI-TNp.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-BIoE_Z_V.js → pickAttrs-pJhU9IxK.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-B48-9HKN.js → placementArrow-BNg_Vj3T.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-D7x_GDgD.js → responsiveObserve-TeSEGQ5-.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-DjzC5_7m.js → slide-D_t_P0bv.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-C6BIymhh.js → statusUtils-DrrIMwAT.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-B4dGrdD9.js → styleChecker-BjZCuFRm.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-Bken4aNE.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-BMlqECqv.js → useFs-BidCpfLX.js} +1 -1
  148. package/src/assets/web-panel/assets/usePersonalDataHub-C5mHttA9.js +1 -0
  149. package/src/assets/web-panel/assets/{vnode-CKJQmxa0.js → vnode-BrbcRiHZ.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CaZunuau.js → zoom-PISX7AkR.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/__tests__/hub-wechat.test.js +45 -0
  153. package/src/commands/background-session.js +5 -2
  154. package/src/commands/context.js +33 -1
  155. package/src/commands/hub.js +473 -39
  156. package/src/commands/init.js +212 -110
  157. package/src/commands/llm.js +22 -6
  158. package/src/commands/skill.js +11 -1
  159. package/src/data/changelog.json +76 -270
  160. package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
  161. package/src/harness/golden-transcript.js +3 -0
  162. package/src/index.js +18 -7
  163. package/src/lib/approval-authority-store.js +891 -0
  164. package/src/lib/background-interaction-resolver.js +399 -54
  165. package/src/lib/background-session-transport.js +3 -1
  166. package/src/lib/context-breakdown.js +82 -3
  167. package/src/lib/event-runtime-host.js +225 -0
  168. package/src/lib/event-runtime-store.js +102 -21
  169. package/src/lib/event-runtime-worker.js +70 -2
  170. package/src/lib/hooks-v2-producers.js +33 -8
  171. package/src/lib/hooks-v2-runtime.js +360 -40
  172. package/src/lib/interaction-adapter.js +66 -0
  173. package/src/lib/operation-fingerprint.js +107 -2
  174. package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
  175. package/src/lib/personal-data-hub-wiring.js +327 -52
  176. package/src/lib/process-execution-broker/credential-agent.js +954 -111
  177. package/src/lib/process-execution-broker/index.js +369 -85
  178. package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
  179. package/src/lib/provider-http-error.js +94 -0
  180. package/src/lib/remote-approval-bridge.js +222 -26
  181. package/src/lib/side-effect-ledger-store.js +65 -14
  182. package/src/lib/skill-loader.js +215 -12
  183. package/src/lib/skill-process-broker.js +15 -1
  184. package/src/lib/sub-agent-context.js +42 -0
  185. package/src/lib/turn-binding-store.js +64 -15
  186. package/src/lib/turn-binding.js +85 -16
  187. package/src/repl/agent-repl.js +40 -6
  188. package/src/repl/remote-approval.js +13 -13
  189. package/src/repl/repl-turn-binding.js +46 -16
  190. package/src/runtime/agent-core.js +286 -35
  191. package/src/runtime/coding-agent-contract-shared.cjs +16 -0
  192. package/src/runtime/headless-runner.js +147 -34
  193. package/src/runtime/headless-stream.js +147 -7
  194. package/src/workers/background-agent-worker.js +70 -38
  195. package/src/assets/web-panel/assets/AppLayout-BNJJAYME.js +0 -9
  196. package/src/assets/web-panel/assets/ChatBubbleRenderer-DE6reP99.js +0 -1
  197. package/src/assets/web-panel/assets/Dashboard-C4Gutni2.js +0 -3
  198. package/src/assets/web-panel/assets/Notes-DP1k45mF.js +0 -7
  199. package/src/assets/web-panel/assets/OrderTableRenderer-CoM_Zw0J.js +0 -1
  200. package/src/assets/web-panel/assets/PersonalDataHub-b63T3HJM.js +0 -9
  201. package/src/assets/web-panel/assets/Projects-CN8E_5Mq.js +0 -1
  202. package/src/assets/web-panel/assets/Skills-q6eb6KPZ.js +0 -1
  203. package/src/assets/web-panel/assets/Tasks-DvepRb8-.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-B8LQ8leS.js +0 -1
  205. package/src/assets/web-panel/assets/index-Bh8BPtqR.js +0 -1
  206. package/src/assets/web-panel/assets/index-CeVrHLzZ.js +0 -1
  207. package/src/assets/web-panel/assets/usePersonalDataHub-DLdrfmMa.js +0 -1
@@ -1,71 +1,416 @@
1
1
  /**
2
- * background-interaction-resolver — 监听 agent 子进程发来的交互请求,
3
- * 转发给后台会话 transport 并通过 pendingInteractions 追踪回复。
2
+ * Background turn interaction protocol.
4
3
  *
5
- * 当 agent 调用 onPrompt/onApprove 等需要用户交互的方法时,子进程通过 IPC
6
- * 发送 { type: "interaction-request", intId, requestType, prompt, options } 消息。
7
- * 本模块监听这些消息,加入本地 pending map,并广播给所有 attach 客户端。
4
+ * A background worker starts the headless turn with a Node IPC channel. The
5
+ * turn child uses `createBackgroundInteractionClient()` to suspend the active
6
+ * tool call until the worker resolves it. The worker uses
7
+ * `attachInteractionRequestHandler()` to validate the request binding, route
8
+ * it to attach/Desktop/IDE, and return the answer to the same child process.
9
+ *
10
+ * Every response is bound to the original background session, agent, turn,
11
+ * tool call, and monotonically increasing sequence. A stale or cross-session
12
+ * response is ignored by the child and can never resolve another request.
8
13
  */
9
14
 
10
- const INTERACTION_TIMEOUT_MS = 5 * 60 * 1000; // 5 分钟超时
15
+ import { randomUUID } from "node:crypto";
16
+
17
+ export const BACKGROUND_INTERACTION_PROTOCOL_VERSION = 1;
18
+ export const DEFAULT_BACKGROUND_INTERACTION_TIMEOUT_MS = 60_000;
19
+
20
+ const MAX_COMPLETED_RESPONSES = 256;
21
+
22
+ function protocolError(message, code = "INTERACTION_PROTOCOL_ERROR") {
23
+ const error = new Error(message);
24
+ error.code = code;
25
+ return error;
26
+ }
27
+
28
+ function asNullableString(value) {
29
+ if (value === undefined || value === null || value === "") return null;
30
+ return String(value);
31
+ }
32
+
33
+ function normalizeBinding(binding = {}) {
34
+ return {
35
+ backgroundAgentId: asNullableString(binding.backgroundAgentId),
36
+ sessionId: asNullableString(binding.sessionId),
37
+ turnId: asNullableString(binding.turnId),
38
+ toolUseId: asNullableString(binding.toolUseId),
39
+ sequence:
40
+ Number.isSafeInteger(binding.sequence) && binding.sequence > 0
41
+ ? binding.sequence
42
+ : null,
43
+ };
44
+ }
45
+
46
+ function sameBinding(left, right) {
47
+ const a = normalizeBinding(left);
48
+ const b = normalizeBinding(right);
49
+ return (
50
+ a.backgroundAgentId === b.backgroundAgentId &&
51
+ a.sessionId === b.sessionId &&
52
+ a.turnId === b.turnId &&
53
+ a.toolUseId === b.toolUseId &&
54
+ a.sequence === b.sequence
55
+ );
56
+ }
57
+
58
+ function validRequest(message) {
59
+ return (
60
+ message &&
61
+ message.type === "interaction-request" &&
62
+ message.protocolVersion === BACKGROUND_INTERACTION_PROTOCOL_VERSION &&
63
+ typeof message.requestId === "string" &&
64
+ message.requestId.length > 0 &&
65
+ message.requestId.length <= 256 &&
66
+ message.payload &&
67
+ typeof message.payload === "object" &&
68
+ normalizeBinding(message.binding).sequence !== null
69
+ );
70
+ }
71
+
72
+ function sendIpc(target, message, onError = () => {}) {
73
+ if (!target || typeof target.send !== "function" || target.connected === false)
74
+ return false;
75
+ try {
76
+ target.send(message, (error) => {
77
+ if (error) onError(error);
78
+ });
79
+ return true;
80
+ } catch (error) {
81
+ onError(error);
82
+ return false;
83
+ }
84
+ }
11
85
 
12
86
  /**
13
- * 挂载交互请求监听到 agent 子进程。
87
+ * Create the turn-child side of the interaction channel.
14
88
  *
15
- * @param {import('node:child_process').ChildProcess} child - agent 子进程实例
16
- * @param {Map<string, { resolve: Function, reject: Function, timer: NodeJS.Timeout }>} pendingInteractions - 待处理交互映射
17
- * @param {Function} broadcast - 广播消息给所有 attach 客户端的函数
18
- * @returns {Function} 卸载函数,调用后停止监听
89
+ * @param {object} [options]
90
+ * @param {NodeJS.Process|object} [options.processLike]
91
+ * @param {string} [options.backgroundAgentId]
92
+ * @param {string|null} [options.sessionId]
93
+ * @param {string|null} [options.turnId]
94
+ * @param {number} [options.defaultTimeoutMs]
95
+ * @param {()=>string} [options.idFactory]
96
+ */
97
+ export function createBackgroundInteractionClient(options = {}) {
98
+ const processLike = options.processLike || process;
99
+ const backgroundAgentId =
100
+ options.backgroundAgentId ?? processLike?.env?.CC_BACKGROUND_AGENT_ID;
101
+ const sessionId = options.sessionId ?? null;
102
+ const turnId = options.turnId ?? null;
103
+ const defaultTimeoutMs =
104
+ Number.isFinite(options.defaultTimeoutMs) && options.defaultTimeoutMs > 0
105
+ ? options.defaultTimeoutMs
106
+ : DEFAULT_BACKGROUND_INTERACTION_TIMEOUT_MS;
107
+ const idFactory = options.idFactory || randomUUID;
108
+ const enabled =
109
+ typeof backgroundAgentId === "string" &&
110
+ backgroundAgentId.trim() !== "" &&
111
+ typeof processLike?.send === "function";
112
+ const pending = new Map();
113
+ let sequence = 0;
114
+ let closed = false;
115
+
116
+ const removeMessageListener = () => {
117
+ if (typeof processLike?.off === "function") {
118
+ processLike.off("message", onMessage);
119
+ } else if (typeof processLike?.removeListener === "function") {
120
+ processLike.removeListener("message", onMessage);
121
+ }
122
+ };
123
+
124
+ function settle(entry, action, value) {
125
+ if (!entry || entry.settled) return;
126
+ entry.settled = true;
127
+ clearTimeout(entry.timer);
128
+ pending.delete(entry.requestId);
129
+ action(value);
130
+ }
131
+
132
+ function onMessage(message) {
133
+ if (
134
+ !message ||
135
+ message.type !== "interaction-response" ||
136
+ message.protocolVersion !== BACKGROUND_INTERACTION_PROTOCOL_VERSION
137
+ ) {
138
+ return;
139
+ }
140
+ const entry = pending.get(message.requestId);
141
+ if (!entry || !sameBinding(entry.binding, message.binding)) return;
142
+
143
+ if (message.status === "resolved") {
144
+ settle(entry, entry.resolve, message.answer);
145
+ return;
146
+ }
147
+ const error = protocolError(
148
+ message.error?.message || "Background interaction was rejected",
149
+ message.error?.code || "INTERACTION_REJECTED",
150
+ );
151
+ settle(entry, entry.reject, error);
152
+ }
153
+
154
+ if (enabled && typeof processLike.on === "function") {
155
+ processLike.on("message", onMessage);
156
+ }
157
+
158
+ return {
159
+ enabled,
160
+ pendingCount() {
161
+ return pending.size;
162
+ },
163
+ async request(payload = {}) {
164
+ if (!enabled || closed) {
165
+ throw protocolError(
166
+ "Background interaction IPC channel is unavailable",
167
+ "USER_NOT_REACHABLE",
168
+ );
169
+ }
170
+
171
+ sequence += 1;
172
+ const requestId = `${backgroundAgentId}:${idFactory()}`;
173
+ const binding = normalizeBinding({
174
+ backgroundAgentId,
175
+ sessionId: payload.sessionId ?? sessionId,
176
+ turnId: payload.turnId ?? turnId,
177
+ toolUseId: payload.toolUseId,
178
+ sequence,
179
+ });
180
+ const timeoutMs =
181
+ Number.isFinite(payload.timeoutMs) && payload.timeoutMs > 0
182
+ ? payload.timeoutMs
183
+ : defaultTimeoutMs;
184
+
185
+ return new Promise((resolve, reject) => {
186
+ const entry = {
187
+ requestId,
188
+ binding,
189
+ resolve,
190
+ reject,
191
+ timer: null,
192
+ settled: false,
193
+ };
194
+ entry.timer = setTimeout(() => {
195
+ const error = protocolError(
196
+ "User did not respond before the interaction timed out",
197
+ "USER_TIMEOUT",
198
+ );
199
+ sendIpc(processLike, {
200
+ type: "interaction-cancel",
201
+ protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
202
+ requestId,
203
+ binding,
204
+ reason: "timeout",
205
+ });
206
+ settle(entry, reject, error);
207
+ }, timeoutMs);
208
+ entry.timer.unref?.();
209
+ pending.set(requestId, entry);
210
+
211
+ const sent = sendIpc(
212
+ processLike,
213
+ {
214
+ type: "interaction-request",
215
+ protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
216
+ requestId,
217
+ binding,
218
+ payload: {
219
+ kind: payload.kind || "question",
220
+ question: String(payload.question || payload.prompt || ""),
221
+ options: Array.isArray(payload.options) ? payload.options : null,
222
+ multiSelect: payload.multiSelect === true,
223
+ defaultValue: payload.defaultValue,
224
+ timeoutMs,
225
+ policyDigest: asNullableString(payload.policyDigest),
226
+ },
227
+ },
228
+ (error) => {
229
+ settle(
230
+ entry,
231
+ reject,
232
+ protocolError(
233
+ `Failed to send background interaction: ${error.message}`,
234
+ "INTERACTION_CHANNEL_ERROR",
235
+ ),
236
+ );
237
+ },
238
+ );
239
+ if (!sent) {
240
+ settle(
241
+ entry,
242
+ reject,
243
+ protocolError(
244
+ "Background interaction IPC channel is unavailable",
245
+ "USER_NOT_REACHABLE",
246
+ ),
247
+ );
248
+ }
249
+ });
250
+ },
251
+ close(reason = "Background interaction channel closed") {
252
+ if (closed) return;
253
+ closed = true;
254
+ removeMessageListener();
255
+ for (const entry of [...pending.values()]) {
256
+ settle(
257
+ entry,
258
+ entry.reject,
259
+ protocolError(reason, "INTERACTION_CHANNEL_CLOSED"),
260
+ );
261
+ }
262
+ },
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Attach the worker side of the interaction channel to one turn child.
268
+ *
269
+ * @param {import("node:child_process").ChildProcess|object} child
270
+ * @param {(payload:object, request:object, context:{signal:AbortSignal})=>Promise<any>|any} resolveRequest
271
+ * @param {object} [options]
272
+ * @param {string|null} [options.backgroundAgentId]
273
+ * @param {string|null} [options.sessionId]
274
+ * @returns {()=>void} detach function
19
275
  */
20
276
  export function attachInteractionRequestHandler(
21
277
  child,
22
- pendingInteractions,
23
- broadcast,
278
+ resolveRequest,
279
+ options = {},
24
280
  ) {
25
- if (!child) {
26
- return () => {};
27
- }
281
+ if (!child || typeof child.on !== "function") return () => {};
28
282
 
29
- const handler = (message) => {
30
- if (!message || typeof message !== "object") return;
31
- if (message.type !== "interaction-request") return;
32
-
33
- const { intId, requestType, prompt, options = {} } = message;
34
- if (!intId) return;
35
-
36
- // 加入 pending 映射,设置超时
37
- const timer = setTimeout(() => {
38
- const entry = pendingInteractions.get(intId);
39
- if (!entry) return;
40
- pendingInteractions.delete(intId);
41
- // 超时返回默认值:confirm 返回 false,其他返回 null
42
- const defaultAnswer = requestType === "confirm" ? false : null;
43
- entry.resolve(defaultAnswer);
44
- broadcast?.({
45
- type: "interaction-timeout",
46
- intId,
47
- });
48
- }, INTERACTION_TIMEOUT_MS);
49
- // 不保持事件循环存活
50
- timer.unref?.();
51
-
52
- // 这里不存 resolve/reject — 调用方 (worker) 已经在 agentAskUser 中存了
53
- // 我们只负责广播给 attach 客户端
54
- // 但为了超时能触发 worker 的 resolve,需要把 entry 透传?不,worker 自己存 entry
55
- // 只需要广播消息即可
56
- broadcast?.({
57
- type: "interaction_request",
58
- intId,
59
- requestType,
60
- prompt,
61
- options,
62
- });
283
+ const inflight = new Map();
284
+ const completed = new Map();
285
+ let detached = false;
286
+
287
+ const rememberCompleted = (requestId, response) => {
288
+ completed.set(requestId, response);
289
+ if (completed.size > MAX_COMPLETED_RESPONSES) {
290
+ completed.delete(completed.keys().next().value);
291
+ }
292
+ };
293
+
294
+ const sendResponse = (response) => {
295
+ if (!detached) sendIpc(child, response);
63
296
  };
64
297
 
65
- child.on("message", handler);
298
+ const rejectRequest = (message, code, detail) => {
299
+ const response = {
300
+ type: "interaction-response",
301
+ protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
302
+ requestId:
303
+ typeof message?.requestId === "string" ? message.requestId : "",
304
+ binding: normalizeBinding(message?.binding),
305
+ status: "rejected",
306
+ error: { code, message: detail },
307
+ };
308
+ if (response.requestId) rememberCompleted(response.requestId, response);
309
+ sendResponse(response);
310
+ };
311
+
312
+ const onMessage = (message) => {
313
+ if (
314
+ message?.type === "interaction-cancel" &&
315
+ message.protocolVersion === BACKGROUND_INTERACTION_PROTOCOL_VERSION
316
+ ) {
317
+ const active = inflight.get(message.requestId);
318
+ if (active && sameBinding(active.binding, message.binding)) {
319
+ inflight.delete(message.requestId);
320
+ active.controller.abort(message.reason || "cancelled");
321
+ }
322
+ return;
323
+ }
324
+ if (message?.type !== "interaction-request") return;
325
+ if (!validRequest(message)) {
326
+ rejectRequest(
327
+ message,
328
+ "INVALID_INTERACTION_REQUEST",
329
+ "Malformed background interaction request",
330
+ );
331
+ return;
332
+ }
333
+
334
+ const binding = normalizeBinding(message.binding);
335
+ const expectedAgentId = asNullableString(options.backgroundAgentId);
336
+ const expectedSessionId = asNullableString(options.sessionId);
337
+ if (
338
+ (expectedAgentId &&
339
+ binding.backgroundAgentId !== expectedAgentId) ||
340
+ (expectedSessionId && binding.sessionId !== expectedSessionId)
341
+ ) {
342
+ rejectRequest(
343
+ message,
344
+ "INTERACTION_BINDING_MISMATCH",
345
+ "Background interaction does not belong to this worker/session",
346
+ );
347
+ return;
348
+ }
349
+
350
+ const prior = completed.get(message.requestId);
351
+ if (prior) {
352
+ sendResponse(prior);
353
+ return;
354
+ }
355
+ if (inflight.has(message.requestId)) return;
356
+
357
+ const controller = new AbortController();
358
+ inflight.set(message.requestId, { binding, controller });
359
+ Promise.resolve()
360
+ .then(() =>
361
+ resolveRequest(message.payload, message, {
362
+ signal: controller.signal,
363
+ }),
364
+ )
365
+ .then(
366
+ (answer) => {
367
+ const active = inflight.get(message.requestId);
368
+ if (!active || controller.signal.aborted) return;
369
+ inflight.delete(message.requestId);
370
+ const response = {
371
+ type: "interaction-response",
372
+ protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
373
+ requestId: message.requestId,
374
+ binding,
375
+ status: "resolved",
376
+ answer,
377
+ };
378
+ rememberCompleted(message.requestId, response);
379
+ sendResponse(response);
380
+ },
381
+ (error) => {
382
+ const active = inflight.get(message.requestId);
383
+ if (!active || controller.signal.aborted) return;
384
+ inflight.delete(message.requestId);
385
+ const response = {
386
+ type: "interaction-response",
387
+ protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
388
+ requestId: message.requestId,
389
+ binding,
390
+ status: "rejected",
391
+ error: {
392
+ code: error?.code || "INTERACTION_REJECTED",
393
+ message: error?.message || String(error),
394
+ },
395
+ };
396
+ rememberCompleted(message.requestId, response);
397
+ sendResponse(response);
398
+ },
399
+ );
400
+ };
66
401
 
67
- // 返回卸载函数
68
- return () => {
69
- child.off("message", handler);
402
+ child.on("message", onMessage);
403
+ const detach = () => {
404
+ if (detached) return;
405
+ detached = true;
406
+ if (typeof child.off === "function") child.off("message", onMessage);
407
+ else child.removeListener?.("message", onMessage);
408
+ for (const active of inflight.values()) {
409
+ active.controller.abort("child detached");
410
+ }
411
+ inflight.clear();
70
412
  };
413
+ child.once?.("exit", detach);
414
+ child.once?.("disconnect", detach);
415
+ return detach;
71
416
  }
@@ -261,12 +261,13 @@ export function startBackgroundSessionServer(opts) {
261
261
  broadcast: (message) => {
262
262
  for (const socket of clients) writeMessage(socket, message);
263
263
  },
264
- broadcastInteractionRequest: (intId, payload) => {
264
+ broadcastInteractionRequest: (intId, payload, binding = null) => {
265
265
  for (const socket of clients)
266
266
  writeMessage(socket, {
267
267
  type: "interaction_request",
268
268
  intId,
269
269
  requestId: intId,
270
+ binding,
270
271
  question:
271
272
  payload.question || payload.prompt || "Agent needs your input",
272
273
  prompt: payload.question || payload.prompt || "",
@@ -274,6 +275,7 @@ export function startBackgroundSessionServer(opts) {
274
275
  options: payload.options || null,
275
276
  multiSelect: payload.multiSelect || false,
276
277
  defaultValue: payload.defaultValue,
278
+ policyDigest: payload.policyDigest || null,
277
279
  });
278
280
  },
279
281
  close: () =>
@@ -84,14 +84,93 @@ export function breakdownSkillSources(skills, estimateTokens) {
84
84
  .map((skill) => ({
85
85
  source: skill?.source || "skill",
86
86
  skill: String(skill?.id || skill?.displayName || "skill"),
87
- tokens: est(
88
- `## Persona: ${skill?.displayName || skill?.id || "skill"}\n${skill?.body || ""}`,
89
- ),
87
+ tokens: Number.isFinite(skill?.tokens)
88
+ ? Math.max(0, skill.tokens)
89
+ : est(
90
+ `## Persona: ${skill?.displayName || skill?.id || "skill"}\n${skill?.body || ""}`,
91
+ ),
90
92
  }))
91
93
  .filter((row) => row.tokens > 0);
92
94
  return { sources, total: sources.reduce((sum, row) => sum + row.tokens, 0) };
93
95
  }
94
96
 
97
+ /** Normalize the content-free two-tier Skill cache snapshot. */
98
+ export function breakdownSkillCache(cache) {
99
+ const descriptors = cache?.descriptors || {};
100
+ const bodies = cache?.bodies || {};
101
+ const savings = cache?.savings || {};
102
+ return {
103
+ descriptors: {
104
+ resident: Number(descriptors.resident) || 0,
105
+ scans: Number(descriptors.scans) || 0,
106
+ cacheHits: Number(descriptors.cacheHits) || 0,
107
+ estimatedTokens: Number(descriptors.estimatedTokens) || 0,
108
+ },
109
+ bodies: {
110
+ resident: Number(bodies.resident) || 0,
111
+ cacheHits: Number(bodies.cacheHits) || 0,
112
+ cacheMisses: Number(bodies.cacheMisses) || 0,
113
+ entries: Array.isArray(bodies.entries)
114
+ ? bodies.entries.map((entry) => ({
115
+ id: String(entry?.id || "skill"),
116
+ source: String(entry?.source || "skill"),
117
+ estimatedTokens: Number(entry?.estimatedTokens) || 0,
118
+ loads: Number(entry?.loads) || 0,
119
+ cacheHits: Number(entry?.cacheHits) || 0,
120
+ loadedBecause: Array.isArray(entry?.loadedBecause)
121
+ ? entry.loadedBecause.map(String)
122
+ : [],
123
+ }))
124
+ : [],
125
+ },
126
+ savings: {
127
+ lazyFileBytes: Number(savings.lazyFileBytes) || 0,
128
+ estimatedTokensAvoided: Number(savings.estimatedTokensAvoided) || 0,
129
+ },
130
+ };
131
+ }
132
+
133
+ /** Actionable, content-free suggestions derived from the measured sources. */
134
+ export function buildContextOptimizations({
135
+ instructions = [],
136
+ mcpSchemas = [],
137
+ skillCache = null,
138
+ } = {}) {
139
+ const suggestions = [];
140
+ if (skillCache?.savings?.estimatedTokensAvoided > 0) {
141
+ suggestions.push({
142
+ kind: "skill_lazy_loading",
143
+ estimatedTokens: skillCache.savings.estimatedTokensAvoided,
144
+ message:
145
+ `${skillCache.descriptors.resident} Skill descriptors stayed discoverable while ` +
146
+ `${skillCache.savings.estimatedTokensAvoided} estimated body tokens remained lazy.`,
147
+ });
148
+ }
149
+ const largestMcp = [...(mcpSchemas || [])].sort(
150
+ (a, b) => (b.tokens || 0) - (a.tokens || 0),
151
+ )[0];
152
+ if (largestMcp?.tokens > 0) {
153
+ suggestions.push({
154
+ kind: "mcp_tool_search",
155
+ estimatedTokens: largestMcp.tokens,
156
+ source: largestMcp.tool || largestMcp.source,
157
+ message: `Consider Tool Search or a narrower MCP allow-list for ${largestMcp.tool || largestMcp.source}.`,
158
+ });
159
+ }
160
+ const largestInstruction = [...(instructions || [])].sort(
161
+ (a, b) => (b.tokens || 0) - (a.tokens || 0),
162
+ )[0];
163
+ if (largestInstruction?.tokens > 0) {
164
+ suggestions.push({
165
+ kind: "instruction_scope",
166
+ estimatedTokens: largestInstruction.tokens,
167
+ source: largestInstruction.source,
168
+ message: `Scope or split ${largestInstruction.source} if it is not needed for every turn.`,
169
+ });
170
+ }
171
+ return suggestions.slice(0, 5);
172
+ }
173
+
95
174
  // role bucket key → { label, kind } for the ranked source list.
96
175
  const MESSAGE_SOURCE_ROWS = [
97
176
  ["system", "system messages", "message"],