chainlesschain 0.165.1 → 0.165.2

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 (202) hide show
  1. package/README.md +248 -2
  2. package/package.json +4 -1
  3. package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-lS1R0Cza.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-BmlgBPsj.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-CNKczv1S.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-BuZGJARQ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-Dm73Sl-O.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-BGMW7A5e.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-DBzGP0bR.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-D1mLCoOV.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-BUfcN7p8.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-Du0Yg5Pi.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-B2RXziHW.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-DWhekOrR.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-HQhrkon4.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-izHNb4uz.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BDwLy41-.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-DFJhLa7K.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-DYY9RG7-.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-rr7tJTtv.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-D2sfHRLi.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-q_eGdhha.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-Cufrh2Bz.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-B-kdv10f.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-CAwRTLZH.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-vx3daNDF.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-Q6SwauyD.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-BJ1q4IQZ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-CSWjROgD.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-DVF8Mnns.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-BQP7LOA4.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-BNlJCmNc.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-DLLg5WQ4.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-CR2skNAF.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-Tyd3o2A0.js → MobileProjects-BxVavObl.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B17pgqMI.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-4UmV5cIL.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-DoMEXEcU.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-h9DMXFX4.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-Cihn-xX8.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-B2vpwmjb.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-C4z28R0-.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-BVlFkwhW.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-C28xs4lp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-Dgl_rBjM.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-3QUIhrKI.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-8Gh33OZ5.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-BNmg4tZi.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-vgMfeCf9.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CUnHbNf-.js → Providers-CXoOa8eX.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-BhfTF0oS.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-C746HEAj.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-BSej8Eo0.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-f3gmnlG3.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-CyvybpkN.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-DwV2PeYn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-11GrzjZy.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-BjKXkrAY.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-eWiFb9Yk.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-CdCsMAtt.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-4Q2UTKQ5.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-v4rtYrRj.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-CRhsFvd_.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-D16zonl7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-Xxozz0i_.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-CXJpPtCT.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-XRV0L1ZF.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-CYuO1i_w.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-BXqitWsu.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BvPrDHIy.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-DhCrSTf5.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-DmsuZD6K.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-D_yrts5C.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-B01ihPrm.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-DpUjKa-N.js} +3 -3
  87. package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-CL07yON6.js} +3 -3
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CLMflnRr.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-DtUqe8h_.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-DPKrgVfy.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-BTt2GtOm.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-D_VeUc1J.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-1kndiylj.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-2eyAXE3d.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-4bna2_SR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-8nz_fmlj.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-B9KvcS7R.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-BAXfRKfG.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BHpBMWCh.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-BaZAbw9A.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-BepLZwgB.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-Bhudc_Ez.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-BlDNVgBt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-By5n8up9.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-CHG-KcS4.js} +1 -1
  107. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  108. package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-CX81qSM4.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-CauoBQkH.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-CcO04eDn.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-CtF7EN49.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-CzjkKUmR.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-D95OFht7.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DFIZ_1Sf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-DG2MZQ3f.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-DJ8vXGOa.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-Dbbymxpj.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-DdkplL9R.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-DfGl8CqG.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-DfKBRdSS.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-DsJCDUJG.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-Dwqmtk65.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-DxdTdBix.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-FzdwcSy2.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-GsoUQ2M7.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-brCInrzq.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-dVc-CXY4.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-fk2Rjacw.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-l1BETrDb.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-m31Ka8hZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-ocf06EVQ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-sDoMHTNA.js} +1 -1
  133. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  134. package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-CvyYJd7z.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-DYZkhUdz.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-slnJRVT1.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-DVc0CKmy.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-BCXAtHCq.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-DBsemz_K.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-BRtkJ-nv.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cebq7EZn.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-DT7qvId2.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-pQiITLkG.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-c-19BVKQ.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-CxGiMe5n.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-D9btVID5.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-CUYFsFEN.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-CXmn_35j.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/cowork.js +280 -5
  153. package/src/commands/plugin.js +819 -64
  154. package/src/commands/session-location.js +318 -7
  155. package/src/constants.js +2 -2
  156. package/src/data/changelog.json +14 -1
  157. package/src/gateways/ws/action-protocol.js +1 -1
  158. package/src/harness/jsonl-session-store.js +638 -2
  159. package/src/harness/prompt-compressor.js +4 -0
  160. package/src/lib/background-agent-keeper-client.js +29 -0
  161. package/src/lib/background-agent-keeper-protocol.js +4 -0
  162. package/src/lib/background-agent-supervisor.js +59 -29
  163. package/src/lib/cowork-task-runner.js +70 -0
  164. package/src/lib/cowork-workflow.js +42 -9
  165. package/src/lib/doctor-checkup.js +34 -10
  166. package/src/lib/dynamic-workflow-draft.js +535 -0
  167. package/src/lib/dynamic-workflow-facade.js +11 -11
  168. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  169. package/src/lib/execution-location-contract.js +111 -7
  170. package/src/lib/execution-location-runtime.js +3 -1
  171. package/src/lib/execution-location-target.js +910 -0
  172. package/src/lib/llm-providers.js +1 -0
  173. package/src/lib/mcp-call-ledger-store.js +35 -0
  174. package/src/lib/mcp-call-ledger.js +30 -0
  175. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  176. package/src/lib/plugin-runtime/install.js +4956 -657
  177. package/src/lib/plugin-runtime/manifest.js +3 -1
  178. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  179. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  180. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  181. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  182. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  183. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  184. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  185. package/src/lib/plugin-runtime/policy.js +21 -6
  186. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  187. package/src/lib/plugin-runtime/remote-source.js +323 -27
  188. package/src/lib/plugin-runtime/scopes.js +317 -41
  189. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  190. package/src/lib/public-ide-capability-manifest.js +1 -1
  191. package/src/lib/remote-membership-coordinator.js +8 -6
  192. package/src/lib/sub-agent-context.js +268 -2
  193. package/src/lib/task-model-selector.js +1 -1
  194. package/src/lib/with-file-lock.js +95 -5
  195. package/src/runtime/agent-core.js +595 -41
  196. package/src/workers/background-agent-keeper.js +40 -10
  197. package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +0 -1
  198. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
  199. package/src/assets/web-panel/assets/SpeechSettings-B5pLcXUw.js +0 -1
  200. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
  201. package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
  202. package/src/assets/web-panel/assets/index-xYybvEic.js +0 -1
@@ -5,6 +5,8 @@ import {
5
5
  BACKGROUND_AGENT_KEEPER_ARM,
6
6
  BACKGROUND_AGENT_KEEPER_ARMED,
7
7
  BACKGROUND_AGENT_KEEPER_HELLO,
8
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT,
9
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS,
8
10
  BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
9
11
  BACKGROUND_AGENT_KEEPER_READY,
10
12
  BACKGROUND_AGENT_KEEPER_RETIRE,
@@ -76,11 +78,17 @@ export async function connectBackgroundAgentKeeper(options = {}) {
76
78
  const retireTimeoutMs = resolveBackgroundAgentKeeperRetireTimeoutMs(
77
79
  options.retireTimeoutMs,
78
80
  );
81
+ const heartbeatIntervalMs = Math.max(
82
+ 10,
83
+ Number(options.heartbeatIntervalMs) ||
84
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS,
85
+ );
79
86
  const socket = await connectWithDeadline(options.pipePath, connectTimeoutMs);
80
87
  const pending = new Map();
81
88
  let ready = false;
82
89
  let closing = false;
83
90
  let activeTurn = null;
91
+ let heartbeatTimer = null;
84
92
  let readyResolve;
85
93
  let readyReject;
86
94
  const readyPromise = new Promise((resolve, reject) => {
@@ -89,6 +97,8 @@ export async function connectBackgroundAgentKeeper(options = {}) {
89
97
  });
90
98
 
91
99
  const failPending = (error) => {
100
+ clearInterval(heartbeatTimer);
101
+ heartbeatTimer = null;
92
102
  readyReject(error);
93
103
  for (const entry of pending.values()) {
94
104
  clearTimeout(entry.timer);
@@ -171,6 +181,23 @@ export async function connectBackgroundAgentKeeper(options = {}) {
171
181
  );
172
182
  helloTimer.unref?.();
173
183
  await readyPromise.finally(() => clearTimeout(helloTimer));
184
+ const sendHeartbeat = () => {
185
+ try {
186
+ writeMessage(
187
+ socket,
188
+ createBackgroundAgentKeeperMessage(BACKGROUND_AGENT_KEEPER_HEARTBEAT, {
189
+ id: hello.id,
190
+ workerGeneration: hello.workerGeneration,
191
+ workerPid: hello.workerPid,
192
+ }),
193
+ );
194
+ } catch (error) {
195
+ socket.destroy(error);
196
+ }
197
+ };
198
+ sendHeartbeat();
199
+ heartbeatTimer = setInterval(sendHeartbeat, heartbeatIntervalMs);
200
+ heartbeatTimer.unref?.();
174
201
 
175
202
  const request = (
176
203
  type,
@@ -241,6 +268,8 @@ export async function connectBackgroundAgentKeeper(options = {}) {
241
268
  close() {
242
269
  if (closing) return;
243
270
  closing = true;
271
+ clearInterval(heartbeatTimer);
272
+ heartbeatTimer = null;
244
273
  socket.end();
245
274
  },
246
275
  activeTurn: () => activeTurn,
@@ -5,11 +5,15 @@ import { posix } from "node:path";
5
5
  export const BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION = 1;
6
6
  export const BACKGROUND_AGENT_KEEPER_HELLO = "background-agent-keeper-hello";
7
7
  export const BACKGROUND_AGENT_KEEPER_READY = "background-agent-keeper-ready";
8
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT =
9
+ "background-agent-keeper-heartbeat";
8
10
  export const BACKGROUND_AGENT_KEEPER_ARM = "background-agent-keeper-arm";
9
11
  export const BACKGROUND_AGENT_KEEPER_ARMED = "background-agent-keeper-armed";
10
12
  export const BACKGROUND_AGENT_KEEPER_RETIRE = "background-agent-keeper-retire";
11
13
  export const BACKGROUND_AGENT_KEEPER_RETIRED =
12
14
  "background-agent-keeper-retired";
15
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS = 1_000;
16
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS = 15_000;
13
17
 
14
18
  // RETIRE is the only keeper request that performs destructive OS cleanup. Its
15
19
  // deadline is deliberately independent from the short HELLO/ARM request
@@ -3051,22 +3051,10 @@ function isDestructiveIdentityRetired(identity) {
3051
3051
  return ["dead", "reused"].includes(identity?.status);
3052
3052
  }
3053
3053
 
3054
- function isRecoverableCreationTimeIdentityProbe(identity) {
3055
- // A one-shot `ps -o lstart=` failure is observational: it says nothing about
3056
- // whether the exact target is still present. Other unverifiable outcomes can
3057
- // mean a missing durable anchor, a vanished group owner, PID reuse with a
3058
- // surviving group, or an untrusted group snapshot; those remain immediately
3059
- // fail-closed rather than being promoted into the resend path.
3060
- return (
3061
- identity?.status === "unverifiable" &&
3062
- identity?.reason === "creation-time-probe-failed"
3063
- );
3064
- }
3065
-
3066
- function isRecoverableInitialDestructiveIdentityProbe(identity) {
3054
+ function isRecoverableDestructiveIdentityProbe(identity) {
3067
3055
  // These outcomes come only from read-only OS observations. Retrying them
3068
3056
  // cannot address a process, and the caller still requires two consecutive
3069
- // fresh exact matches before it may issue the first signal.
3057
+ // fresh exact matches before it may issue or reissue a signal.
3070
3058
  return (
3071
3059
  identity?.status === "unverifiable" &&
3072
3060
  ["creation-time-probe-failed", "process-group-state-probe-failed"].includes(
@@ -3137,7 +3125,7 @@ function retryRecoverableInitialPosixIdentity({
3137
3125
  }
3138
3126
  if (
3139
3127
  lastIdentity?.status !== "match" &&
3140
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3128
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3141
3129
  ) {
3142
3130
  return outcome("failed", "preflight-identity");
3143
3131
  }
@@ -3154,7 +3142,7 @@ function retryRecoverableInitialPosixIdentity({
3154
3142
  }
3155
3143
  if (
3156
3144
  lastIdentity?.status !== "match" &&
3157
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3145
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3158
3146
  ) {
3159
3147
  return outcome("failed", "preflight-identity");
3160
3148
  }
@@ -3172,8 +3160,9 @@ function retryRecoverableInitialPosixIdentity({
3172
3160
 
3173
3161
  /**
3174
3162
  * Recover from a transient POSIX EPERM without ever signalling from unknown
3175
- * identity evidence. Probe-only waits may bridge a transient creation-time
3176
- * lookup failure, but a resend requires two consecutive fresh exact matches.
3163
+ * identity evidence. Probe-only waits may bridge a transient creation-time or
3164
+ * process-group-state lookup failure, but a resend requires two consecutive
3165
+ * fresh exact matches.
3177
3166
  */
3178
3167
  function retryExactPosixSignalAfterPermissionError({
3179
3168
  inspectIdentity,
@@ -3203,7 +3192,7 @@ function retryExactPosixSignalAfterPermissionError({
3203
3192
  }
3204
3193
  if (
3205
3194
  lastIdentity?.status !== "match" &&
3206
- !isRecoverableCreationTimeIdentityProbe(lastIdentity)
3195
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3207
3196
  ) {
3208
3197
  return outcome("failed", "identity");
3209
3198
  }
@@ -3226,7 +3215,7 @@ function retryExactPosixSignalAfterPermissionError({
3226
3215
  }
3227
3216
  if (
3228
3217
  lastIdentity?.status !== "match" &&
3229
- !isRecoverableCreationTimeIdentityProbe(lastIdentity)
3218
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3230
3219
  ) {
3231
3220
  return outcome("failed", "identity");
3232
3221
  }
@@ -3306,7 +3295,7 @@ function signalExactBackgroundProcessTree(
3306
3295
  if (lastIdentity.status !== "match") {
3307
3296
  if (
3308
3297
  platform === "win32" ||
3309
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3298
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3310
3299
  ) {
3311
3300
  retryStoppedBy = "preflight-identity";
3312
3301
  throw failure(
@@ -3663,7 +3652,7 @@ export function stopBackgroundAgent(id) {
3663
3652
  identityProbeRetries: 0,
3664
3653
  };
3665
3654
  try {
3666
- const preflightIdentity = inspectIdentity();
3655
+ let preflightIdentity = inspectIdentity();
3667
3656
  activeTerminationContext.lastIdentityStatus =
3668
3657
  preflightIdentity.status;
3669
3658
  activeTerminationContext.lastIdentityReason =
@@ -3681,14 +3670,55 @@ export function stopBackgroundAgent(id) {
3681
3670
  continue;
3682
3671
  }
3683
3672
  if (preflightIdentity.status !== "match") {
3684
- activeTerminationContext.retryStoppedBy = "preflight-identity";
3685
- throw new Error(
3686
- `destructive process identity is ${preflightIdentity.status}: ${preflightIdentity.reason || "unknown"}`,
3687
- );
3673
+ if (
3674
+ process.platform === "win32" ||
3675
+ !isRecoverableDestructiveIdentityProbe(preflightIdentity)
3676
+ ) {
3677
+ activeTerminationContext.retryStoppedBy = "preflight-identity";
3678
+ throw new Error(
3679
+ `destructive process identity is ${preflightIdentity.status}: ${preflightIdentity.reason || "unknown"}`,
3680
+ );
3681
+ }
3682
+ permissionRetryDeadline =
3683
+ _deps.posixPermissionRetryNowMs() +
3684
+ Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3685
+ const retry = retryRecoverableInitialPosixIdentity({
3686
+ inspectIdentity,
3687
+ initialIdentity: preflightIdentity,
3688
+ deadline: permissionRetryDeadline,
3689
+ });
3690
+ activeTerminationContext.identityProbeRetries +=
3691
+ retry.identityProbeRetries;
3692
+ preflightIdentity = retry.lastIdentity;
3693
+ activeTerminationContext.lastIdentityStatus =
3694
+ preflightIdentity?.status;
3695
+ activeTerminationContext.lastIdentityReason =
3696
+ preflightIdentity?.reason || null;
3697
+ activeTerminationContext.retryStoppedBy = retry.retryStoppedBy;
3698
+ if (retry.status === "retired") {
3699
+ if (
3700
+ preflightIdentity?.status === "reused" &&
3701
+ primaryWorker?.pid === target.pid &&
3702
+ !signalledPids.has(target.pid)
3703
+ ) {
3704
+ workerPidReused = true;
3705
+ }
3706
+ target.retiredBeforeWait = true;
3707
+ signalledPids.add(target.pid);
3708
+ continue;
3709
+ }
3710
+ if (retry.status !== "matched") {
3711
+ throw new Error(
3712
+ `destructive process identity is ${preflightIdentity?.status || "unknown"}: ${preflightIdentity?.reason || "unknown"}`,
3713
+ );
3714
+ }
3715
+ }
3716
+ if (!(permissionRetryDeadline > 0)) {
3717
+ permissionRetryDeadline =
3718
+ _deps.posixPermissionRetryNowMs() +
3719
+ Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3688
3720
  }
3689
- permissionRetryDeadline =
3690
- _deps.posixPermissionRetryNowMs() +
3691
- Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3721
+ activeTerminationContext.retryStoppedBy = "initial-signal";
3692
3722
  target.signalledAsGroup = sendInitialSignal() === "process-group";
3693
3723
  signalledPids.add(target.pid);
3694
3724
  } catch (error) {
@@ -58,6 +58,7 @@ export const _deps = {
58
58
 
59
59
  const DEFAULT_MAX_ITERATIONS = 50;
60
60
  const DEFAULT_TOKEN_BUDGET = 100_000;
61
+ const WORKFLOW_EFFECT_ID_RE = /^sha256:[a-f0-9]{64}$/;
61
62
  const COWORK_MCP_CALL_LEDGER_UNSUPPORTED =
62
63
  "CC_COWORK_MCP_CALL_LEDGER_UNSUPPORTED";
63
64
  const COWORK_MCP_SESSION_UNVERIFIED = "CC_COWORK_MCP_SESSION_UNVERIFIED";
@@ -74,6 +75,44 @@ function coworkMcpAdmissionError(code, message, sessionId, cause = null) {
74
75
  return error;
75
76
  }
76
77
 
78
+ function assertWorkflowEffectId(value) {
79
+ if (value == null) return;
80
+ if (typeof value !== "string" || !WORKFLOW_EFFECT_ID_RE.test(value)) {
81
+ const error = new TypeError(
82
+ "workflowEffectId must be a canonical sha256 identity",
83
+ );
84
+ error.code = "CC_COWORK_WORKFLOW_EFFECT_ID_INVALID";
85
+ throw error;
86
+ }
87
+ }
88
+
89
+ function workflowEffectEvidence(subAgent, workflowEffectId) {
90
+ if (!workflowEffectId) return {};
91
+ const attempts =
92
+ typeof subAgent?.providerRequestAttempts === "function"
93
+ ? subAgent.providerRequestAttempts()
94
+ : [];
95
+ const receipts =
96
+ typeof subAgent?.providerRequestReceipts === "function"
97
+ ? subAgent.providerRequestReceipts()
98
+ : [];
99
+ const nestedEffectAttempts =
100
+ typeof subAgent?.nestedEffectAttempts === "function"
101
+ ? subAgent.nestedEffectAttempts()
102
+ : [];
103
+ const nestedEffectSettlements =
104
+ typeof subAgent?.nestedEffectSettlements === "function"
105
+ ? subAgent.nestedEffectSettlements()
106
+ : [];
107
+ return {
108
+ workflowEffectId,
109
+ providerRequestAttempts: attempts,
110
+ providerRequestReceipts: receipts,
111
+ nestedEffectAttempts,
112
+ nestedEffectSettlements,
113
+ };
114
+ }
115
+
77
116
  function assertCoworkMcpEventUsageAdmission(event, sessionId) {
78
117
  if (event?.type !== "session_start") return;
79
118
  const data =
@@ -477,6 +516,12 @@ export function prepareCoworkMcpRuntime(mcp, options = {}) {
477
516
  * @param {number} [options.maxIterations] - Override iteration limit
478
517
  * @param {number} [options.tokenBudget] - Override token budget
479
518
  * @param {string} [options.mcpSessionId] - Stable MCP ledger session for resume
519
+ * @param {string} [options.workflowEffectId] - Canonical durable workflow effect
520
+ * @param {boolean} [options.strictUsageTelemetry] - Require synchronous durable call observers
521
+ * @param {function} [options.onUsageBoundary] - Provider-call durable start observer
522
+ * @param {function} [options.onUsageSettlement] - Provider-call durable settlement observer
523
+ * @param {function} [options.onToolCallBoundary] - Tool-call durable start observer
524
+ * @param {function} [options.onToolCallSettlement] - Tool-call durable settlement observer
480
525
  * @param {(request: object) => boolean|Promise<boolean>} [options.approveMcpLocalCodeExecution]
481
526
  * @returns {Promise<{ taskId: string, status: string, result: object }>}
482
527
  */
@@ -493,12 +538,19 @@ export async function runCoworkTask(options = {}) {
493
538
  onProgress = null,
494
539
  signal = null,
495
540
  mcpSessionId = null,
541
+ workflowEffectId = null,
542
+ strictUsageTelemetry = false,
543
+ onUsageBoundary = null,
544
+ onUsageSettlement = null,
545
+ onToolCallBoundary = null,
546
+ onToolCallSettlement = null,
496
547
  approveMcpLocalCodeExecution = null,
497
548
  } = options;
498
549
 
499
550
  if (!userMessage || typeof userMessage !== "string") {
500
551
  throw new Error("userMessage is required");
501
552
  }
553
+ assertWorkflowEffectId(workflowEffectId);
502
554
 
503
555
  // An explicit canonical authority must be admitted before mounting local MCP
504
556
  // servers, constructing a child context, or making any provider call.
@@ -582,6 +634,16 @@ export async function runCoworkTask(options = {}) {
582
634
  cwd,
583
635
  onProgress,
584
636
  signal,
637
+ ...(workflowEffectId ? { workflowEffectId } : {}),
638
+ ...(strictUsageTelemetry
639
+ ? {
640
+ strictUsageTelemetry: true,
641
+ onUsageBoundary,
642
+ onUsageSettlement,
643
+ onToolCallBoundary,
644
+ onToolCallSettlement,
645
+ }
646
+ : {}),
585
647
  extraToolDefinitions: mcp.extraToolDefinitions,
586
648
  externalToolDescriptors: mcp.externalToolDescriptors,
587
649
  externalToolExecutors: mcp.externalToolExecutors,
@@ -632,17 +694,25 @@ export async function runCoworkTask(options = {}) {
632
694
  templateId: template.id,
633
695
  templateName: template.name,
634
696
  ...(mcpRuntime.sessionId ? { mcpSessionId: mcpRuntime.sessionId } : {}),
697
+ ...workflowEffectEvidence(subAgent, workflowEffectId),
635
698
  result,
636
699
  };
637
700
  _appendHistory(cwd, entry, userMessage);
638
701
  return entry;
639
702
  } catch (err) {
703
+ // A durable workflow already marked this outer effect as dispatched.
704
+ // Converting a child/provider exception into an ordinary failed result
705
+ // would authorize the step retry path even though the physical provider
706
+ // outcome may be unknown. Propagate so the runtime keeps it pending and
707
+ // requires reconciliation.
708
+ if (workflowEffectId) throw err;
640
709
  const entry = {
641
710
  taskId,
642
711
  status: "failed",
643
712
  templateId: template.id,
644
713
  templateName: template.name,
645
714
  ...(mcpRuntime.sessionId ? { mcpSessionId: mcpRuntime.sessionId } : {}),
715
+ ...workflowEffectEvidence(subAgent, workflowEffectId),
646
716
  result: {
647
717
  summary: `Task failed: ${err.message}`,
648
718
  artifacts: [],
@@ -66,6 +66,8 @@ export const COWORK_WORKFLOW_RUN_ADMISSION_INVALID_CODE =
66
66
  "COWORK_WORKFLOW_RUN_ADMISSION_INVALID";
67
67
  export const COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE =
68
68
  "COWORK_WORKFLOW_RUN_RESULT_INVALID";
69
+ export const COWORK_WORKFLOW_CONTROL_SIGNAL_CODE =
70
+ "COWORK_WORKFLOW_CONTROL_SIGNAL";
69
71
 
70
72
  const DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA =
71
73
  "cc-dynamic-workflow-run-admission/v1";
@@ -484,7 +486,9 @@ function normalizeWorkflowRunAdmission(
484
486
  authority.definitionDigest !== definitionDigest ||
485
487
  value.executionLocation.authoritySchema !==
486
488
  EXECUTION_LOCATION_AUTHORITY_SCHEMA ||
487
- value.executionLocation.authority !== "verified-session-start" ||
489
+ !["verified-session-start", "verified-session-location-handoff"].includes(
490
+ value.executionLocation.authority,
491
+ ) ||
488
492
  value.executionLocation.bindingSchema !==
489
493
  EXECUTION_LOCATION_BINDING_SCHEMA ||
490
494
  typeof value.executionLocation.location !== "string" ||
@@ -534,7 +538,7 @@ function normalizeWorkflowRunAdmission(
534
538
  }),
535
539
  executionLocation: Object.freeze({
536
540
  authoritySchema: EXECUTION_LOCATION_AUTHORITY_SCHEMA,
537
- authority: "verified-session-start",
541
+ authority: value.executionLocation.authority,
538
542
  session: Object.freeze({
539
543
  sessionId: session.sessionId,
540
544
  headHash: session.headHash,
@@ -1307,6 +1311,7 @@ export async function runStepWithRetry({
1307
1311
  taskSemaphore,
1308
1312
  runTask = _deps.runTask,
1309
1313
  recordId = step.id,
1314
+ iteration = 1,
1310
1315
  onTaskStart,
1311
1316
  admitted = false,
1312
1317
  }) {
@@ -1336,6 +1341,12 @@ export async function runStepWithRetry({
1336
1341
  files: step.files || [],
1337
1342
  cwd,
1338
1343
  llmOptions,
1344
+ workflowEffect: Object.freeze({
1345
+ stepId: recordId,
1346
+ iteration,
1347
+ attempt,
1348
+ timeoutMs,
1349
+ }),
1339
1350
  ...(abortController ? { signal: abortController.signal } : {}),
1340
1351
  }),
1341
1352
  );
@@ -1353,20 +1364,38 @@ export async function runStepWithRetry({
1353
1364
  lastErr = null;
1354
1365
  } catch (err) {
1355
1366
  if (release) release();
1367
+ if (err?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE) throw err;
1356
1368
  if (err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE) throw err;
1357
1369
  if (admitted && err?.code === STEP_TIMEOUT_CODE && taskPromise != null) {
1358
1370
  abortController.abort(err);
1359
1371
  // A terminal admitted record must never be written while the physical
1360
1372
  // task can still produce side effects. Confirm settlement before a
1361
1373
  // retry or final failure; a runner that ignores abort safely blocks.
1374
+ // A late completed result is authoritative and must not be replayed.
1362
1375
  try {
1363
- await taskPromise;
1364
- } catch {
1365
- // The timeout remains the attempt's externally visible failure.
1376
+ const lateRawEntry = await taskPromise;
1377
+ const lateEntry = normalizeAdmittedTaskEntry(lateRawEntry);
1378
+ if (lateEntry.status === "completed") {
1379
+ return { ok: true, entry: lateEntry, attempts: attempt };
1380
+ }
1381
+ lastEntry = lateEntry;
1382
+ lastErr = null;
1383
+ } catch (lateError) {
1384
+ if (
1385
+ lateError?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE ||
1386
+ lateError?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE
1387
+ ) {
1388
+ throw lateError;
1389
+ }
1390
+ // The timeout remains the attempt's externally visible failure when
1391
+ // a non-authoritative runner error arrives after the deadline.
1392
+ lastErr = err;
1393
+ lastEntry = null;
1366
1394
  }
1395
+ } else {
1396
+ lastErr = err; // threw or timed out → retry-eligible
1397
+ lastEntry = null;
1367
1398
  }
1368
- lastErr = err; // threw or timed out → retry-eligible
1369
- lastEntry = null;
1370
1399
  }
1371
1400
  if (attempt <= maxRetries) {
1372
1401
  const delay = retryDelayFor(step, attempt);
@@ -1503,6 +1532,7 @@ export async function runLoopStep({
1503
1532
  taskSemaphore,
1504
1533
  runTask,
1505
1534
  recordId,
1535
+ iteration: iter,
1506
1536
  onTaskStart: (event) => {
1507
1537
  if (startAnnounced) return;
1508
1538
  startAnnounced = true;
@@ -1760,8 +1790,11 @@ export async function runPipeline({
1760
1790
  admitted,
1761
1791
  });
1762
1792
  } catch (err) {
1763
- if (err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE) {
1764
- fatalError = err;
1793
+ if (
1794
+ err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE ||
1795
+ err?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE
1796
+ ) {
1797
+ fatalError ||= err;
1765
1798
  halted = true;
1766
1799
  active--;
1767
1800
  pump();
@@ -462,7 +462,6 @@ async function pluginSection(opts, _deps2) {
462
462
  const checks = [];
463
463
  try {
464
464
  const { listInstalled } = await import("./plugin-runtime/install.js");
465
- const { readPluginLock } = await import("./plugin-runtime/signature.js");
466
465
  const plugins = listInstalled({ cwd: opts.cwd || process.cwd() }) || [];
467
466
  if (plugins.length === 0) {
468
467
  checks.push(
@@ -475,22 +474,47 @@ async function pluginSection(opts, _deps2) {
475
474
  );
476
475
  } else {
477
476
  let unsigned = 0;
477
+ const runtimeBlocked = [];
478
+ const retainedRecovery = [];
478
479
  for (const p of plugins) {
479
- try {
480
- const lock = p.dir ? readPluginLock(p.dir) : null;
481
- if (!lock || !lock.signature) unsigned++;
482
- } catch {
483
- unsigned++;
480
+ if (p.activePointer?.recoveryPath) {
481
+ retainedRecovery.push(`${p.name} (${p.activePointer.recoveryPath})`);
482
+ }
483
+ if (p.runtimeBlocked === true) {
484
+ runtimeBlocked.push(
485
+ `${p.name} (${p.activePointer?.status || "unknown pointer state"})`,
486
+ );
487
+ continue;
484
488
  }
489
+ if (p.integrity?.signature?.present !== true) unsigned++;
485
490
  }
486
491
  checks.push(
487
492
  check(
488
493
  "plugins",
489
494
  `Installed plugins: ${plugins.length}`,
490
- unsigned > 0 ? CHECK_LEVELS.WARN : CHECK_LEVELS.OK,
491
- unsigned > 0
492
- ? `${unsigned} without a signature lock — they will be refused if plugins.requireSignedPlugins is enabled`
493
- : "all have signature locks",
495
+ unsigned > 0 ||
496
+ runtimeBlocked.length > 0 ||
497
+ retainedRecovery.length > 0
498
+ ? CHECK_LEVELS.WARN
499
+ : CHECK_LEVELS.OK,
500
+ [
501
+ runtimeBlocked.length > 0
502
+ ? `runtime blocked: ${runtimeBlocked.join(", ")}`
503
+ : null,
504
+ retainedRecovery.length > 0
505
+ ? `retained install recovery: ${retainedRecovery.join(", ")}; remove the blocked plugin before another mutation`
506
+ : null,
507
+ unsigned > 0
508
+ ? `${unsigned} active without a signature lock — they will be refused if plugins.requireSignedPlugins is enabled`
509
+ : null,
510
+ runtimeBlocked.length === 0 &&
511
+ retainedRecovery.length === 0 &&
512
+ unsigned === 0
513
+ ? "all active plugins have signature locks"
514
+ : null,
515
+ ]
516
+ .filter(Boolean)
517
+ .join("; "),
494
518
  ),
495
519
  );
496
520
  }