chainlesschain 0.162.160 → 0.162.162

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 (199) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CkRHjW8e.js → AIOps-ClI2ALgv.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BP3MeNi2.js → ActionButton-BDjRcJaK.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CjbjQsOr.js → Analytics-DikSRsaT.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-i0adH1M8.js → AppLayout-9mLhMFmz.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CCCG2GOU.js → Artifacts-BSr88BrH.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-DrsgvwHC.js → Audit-CdvyimOF.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DkXNkbZM.js → BackgroundAgents-D3_hCedh.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DarTWCex.js → Backup-Cyvv9RHf.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DaDzhcto.js → BaseInput-BZM14kt8.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-C4EzGw6V.js → Chat-DrQSokVz.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-lkavIWMS.js → ChatBubbleRenderer-HwJR-C6B.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-gddNgaQi.js → Checkbox-bFLYeWMV.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BKldFWf9.js → Codegen-7mN7xJXT.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-ChR-brDs.js → Col-DAB5ZWm7.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DlHtZX86.js → Community-B_1TymaK.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BAhfcBpC.js → Compact-Bq1uSWLJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-gIrKkm6m.js → Compliance-CyiE1jEe.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BYnzLBO3.js → Cowork-Cpk0D7vm.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-ByUDIljI.js → Cron-d_Lv3rdB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DOdNbHFp.js → Crosschain-DMqhHjMR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BJHaNWD2.js → DID-Br-oPuBw.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Drm_tClz.js → Dashboard-CfLW_RaP.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-CA1WLzGN.js → Dropdown-DZnTOExV.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Di1SKjNO.js → EmailListRenderer-DHIUHDv1.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-44Ve7ZFe.js → FamilyGuardDashboard-Bw3t3myu.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CubPwv_N.js → Federation-D9cpQPAY.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CsUFe5bU.js → FormItemContext-Jdczr9Mr.js} +1 -1
  29. package/src/assets/web-panel/assets/GenericCardRenderer-BT8ZMpC5.js +1 -0
  30. package/src/assets/web-panel/assets/{Git-D-Qx9dhO.js → Git-DjARip2b.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-_YMYaU4n.js → Governance-BgWbSRcj.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-J7Yzh-K_.js → Inference-CfouJciM.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-D37iI_Mo.js → KnowledgeGraph-50l7mrAr.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-oMTSYfGY.js → Logs-rq50H84B.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BqaPmC0W.js → MarkdownRenderer-DUPIEdQH.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DKUqYDaF.js → Marketplace-BYgPDmHU.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BEv3Gx61.js → McpTools-5DVgcFdx.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-CtA8XD2y.js → Memory-B70LPk7f.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-ug7c1mMj.js → MobileBridge-De8ssA5Y.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-WJaMs4i2.js → Mtc-RJsOTsIA.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-DXnK_-ru.js → MtcAudit-Crh-6BEK.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-C4Ev7726.js → Multisig-wuoR5l3x.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-BMFb9_zk.js → NLProgramming-U2Mji4fZ.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Bl93nvBX.js → Notes-CdTZAyDH.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BN2W5cl6.js → NotificationSettings-C-FucWE7.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-Dxc9oN4o.js → OrderTableRenderer-DCYFTeaI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-CAs48637.js → Organization-an3OF85L.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bhq27Zoc.js → Overflow-C-1vmYFe.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-D61-2542.js → P2P-Cwh47xKg.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C1j4iUxz.js → PdhVaultBrowser-Dm38GT0k.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-rJ2-fScM.js → Permissions-DS2Y8awD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DFIlTok6.js → PersonalDataHub-BQNziA0I.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-BjIgEWoZ.js → Pipeline-DF85L1ue.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BQoH5wcX.js → Privacy-BuLornF-.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CYR_-_Ub.js → ProjectInit-C3Y31IwX.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-nkatCq-9.js → ProjectSettings-BCPiI8aD.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BZ6s4k6g.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-BnKM9fsF.js → Providers-D3Wp022X.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Cfe82dbz.js → QrScannerModal-CUJfQkH4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-r1RbPufV.js → QuickAsk-UAc0EO8l.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CjCbM2WE.js → Recommend-BaxKupcs.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BuMGPH6V.js → RemoteSession-C75NNnXZ.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-Bmj3NY1F.js → Reputation-BVXynZAk.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-BIsNcE_1.js → Row-C5MRZ0bi.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BObmojD2.js → RssFeed-DAKkEYka.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BRmbkQNS.js → Search-DvWv9YOA.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-_rF3DRpY.js → Security-B5peafW1.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-D0If9ZV4.js → Services-UQmvPEaG.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-o7zIksUg.js → Skeleton-C3bhWpuF.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-oUR7zi9u.js → Skills-DfkKf9_H.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-fqKyGi03.js → Sla-DqMSjlBM.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-B1wZ_gxx.js → SpeechSettings-C-WbxWUX.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-B3qJWYXq.js → SyncSettings-CYuHZ4N2.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-BH5_dUfr.js → Tasks-CyeAyB9d.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CCIVpjRP.js → Templates-BTCeQnpq.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-2GZtTGnB.js → Tenant-BifQuA9Y.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BlMkfs_u.js → Terminal-Bx_9Bocf.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-U6hi5OMc.js → TimelineRenderer-BX0RFCOm.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C-_BerGJ.js → Tokens-CGHrCmUS.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DaKzBgN3.js → Trigger-DlqaY4SO.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLvcsco3.js → Trust-DfOmj-ay.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BFn_Y1zZ.js → UkeySign-B72jH5fE.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-B0V18dLT.js → VideoEditing-BT-63fqv.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BbmP7Imu.js → Wallet-Dsul3sHr.js} +3 -3
  86. package/src/assets/web-panel/assets/{WebAuthn-Dewj_0kt.js → WebAuthn-Izkwwdgy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-DOxz3Lwa.js → WorkflowEditor-D3FHSiH1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-B1OIpZnh.js → chat-CmVz5z3U.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CSGNVdri.js → colors-CuLgyJBp.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-8S0vcg0u.js → compact-item-Ml1YkXpw.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-vZyc27TN.js → createContext-BvjJ48bs.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DWw3-UhA.js → hasIn-DVC0157C.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DLAvUtOH.js → index-4SBta3d7.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BZnauCqN.js → index-4wcgvPoF.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-t-q3rkVm.js → index-B0S8NzO6.js} +1 -1
  97. package/src/assets/web-panel/assets/index-B7a1TrU6.js +1 -0
  98. package/src/assets/web-panel/assets/{index-BwKrTVV0.js → index-BUt8kTIB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BQssf0EQ.js → index-B_EvG_GW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-xx5M5Rbj.js → index-BbPtzB8M.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DKwl1AkQ.js → index-BbepULSc.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-B4pP2ycr.js → index-BfuKP4qe.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D1i_8oop.js → index-BiC6dQZG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dc40pX3F.js → index-BjHBpRYZ.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DeCS12Mh.js → index-BpFJHiTv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BjC-TrOb.js → index-BydjdZnl.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DcfLXAW7.js → index-BzG-x9p4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CfEvacz7.js → index-Bzr0rB5T.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BoIPHP4R.js → index-C229ABfy.js} +1 -1
  110. package/src/assets/web-panel/assets/index-C2B1KRja.js +1 -0
  111. package/src/assets/web-panel/assets/{index-BdyokEau.js → index-CFYVQMvu.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CLPr2vC_.js → index-CXkJH-lK.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DtHWHJnj.js → index-C_H_S-DY.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Q6oyLnKC.js → index-C_XuRgGn.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BeBv896m.js → index-C_vRgTl9.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DLgkAhEE.js → index-Ck_52dz_.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-tr1x96W9.js → index-CmFNvNLZ.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CB2MOS_8.js → index-CudmwNBV.js} +3 -3
  119. package/src/assets/web-panel/assets/{index-CFQWZvak.js → index-Cw0Rjy87.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BuB5P0TR.js → index-D3lD5qap.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-D8qXVfoV.js → index-DC_OI9H2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C6r27A-o.js → index-DDGaOWBV.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CbZqw7pg.js → index-DJyN28Et.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJgKP2cm.js → index-DYXHFr4r.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CI3slBKI.js → index-Dt5KMjwJ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CwXBblm-.js → index-DuGszaWY.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BKEmQQvR.js → index-DukDVXCv.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dg0Izipr.js → index-KMqhcF7l.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BkWd6YL7.js → index-KjgKdZe-.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DPgY5GLy.js → index-QXzlT9KD.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BDHyovnK.js → index-vG4XSJ1d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-dxocDVD_.js → index-wvb_FtaV.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Di5YFId0.js → initDefaultProps-BDgd2YZ8.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BVSN0aeL.js → motion-ErtrXdYe.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DHNiaP2l.js → move-_GPPNCgs.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-xJPf3gQD.js → mtc-parser-DRLL-8V2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C7qzUdkN.js → omit-p86sl023.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-pSpjg3Le.js → pickAttrs-BTu7cMLH.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-cs_jL_UC.js → placementArrow-BEKz6Isr.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-DkS_Zs4r.js → responsiveObserve-Ck42c9MP.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-B5O1Kpxo.js → slide-1CEvuPsB.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX02GnZp.js → statusUtils-BDfdx1GJ.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-EmBYRftd.js → styleChecker-B4K46m5w.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-ZGzN1cU8.js → useFlexGapSupport-DoIVpJNq.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CfP7M1m7.js → useFs-DdAWi-ln.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-CbIPHgNh.js → usePersonalDataHub-iUu6k8dx.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-7xh0If_O.js → vnode-DZz4R6Tw.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DmitvTR2.js → zoom-DXlf9sA3.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +15 -1
  151. package/src/commands/agent.js +7 -0
  152. package/src/commands/background-session.js +16 -2
  153. package/src/commands/review.js +6 -1
  154. package/src/data/changelog.json +12 -2
  155. package/src/gateways/ws/background-agent-protocol.js +88 -14
  156. package/src/lib/agent-authority.js +251 -0
  157. package/src/lib/agents.js +19 -0
  158. package/src/lib/background-agent-phase.js +100 -0
  159. package/src/lib/background-agent-supervisor.js +322 -15
  160. package/src/lib/backpressure-policy.js +105 -0
  161. package/src/lib/capability-negotiation.js +212 -0
  162. package/src/lib/credential-guard.js +11 -0
  163. package/src/lib/credential-proxy.js +182 -0
  164. package/src/lib/docs-drift.js +166 -0
  165. package/src/lib/doctor-checkup.js +76 -0
  166. package/src/lib/event-seq-replay.js +167 -0
  167. package/src/lib/goal-condition-engine.js +369 -0
  168. package/src/lib/headless-manifest.js +13 -0
  169. package/src/lib/hook-event-bus.cjs +0 -0
  170. package/src/lib/ide-context.js +53 -14
  171. package/src/lib/json-schema-output.js +30 -1
  172. package/src/lib/json-schema-validate.js +691 -0
  173. package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
  174. package/src/lib/lsp/diagnostics-scheduler.js +127 -0
  175. package/src/lib/operation-fingerprint.js +95 -0
  176. package/src/lib/plugin-runtime/capabilities.js +371 -0
  177. package/src/lib/plugin-runtime/manifest.js +30 -0
  178. package/src/lib/project-instructions.js +98 -1
  179. package/src/lib/remote-approval-bridge.js +16 -0
  180. package/src/lib/remote-path-mapping.js +229 -0
  181. package/src/lib/review-pipeline.js +227 -0
  182. package/src/lib/runtime-checkup.js +318 -0
  183. package/src/lib/schedule-planner.js +136 -0
  184. package/src/lib/session-lifecycle.js +305 -0
  185. package/src/lib/settings-hook-events.cjs +39 -13
  186. package/src/lib/subagent-contract.js +383 -0
  187. package/src/lib/telemetry-ids.js +190 -0
  188. package/src/lib/turn-binding.js +397 -0
  189. package/src/repl/bg-dashboard.js +13 -12
  190. package/src/runtime/agent-core.js +24 -10
  191. package/src/runtime/headless-stream.js +195 -11
  192. package/src/runtime/system-prompt.js +3 -0
  193. package/src/workers/background-agent-worker.js +37 -2
  194. package/src/assets/web-panel/assets/GenericCardRenderer-D7xRjPjZ.js +0 -1
  195. package/src/assets/web-panel/assets/MobileProjects-BpX5g9It.js +0 -1
  196. package/src/assets/web-panel/assets/Projects-SS9uZKCl.js +0 -1
  197. package/src/assets/web-panel/assets/devWarning-DYEZcY55.js +0 -1
  198. package/src/assets/web-panel/assets/index-DmVFfcNR.js +0 -1
  199. package/src/assets/web-panel/assets/index-x7YyhMUa.js +0 -1
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Turn binding table — explicit `turn → checkpoint` binding + restore planning
3
+ * (P1 "显式绑定 Turn、Checkpoint 和恢复").
4
+ *
5
+ * Today the rewind path binds a turn to its file checkpoint IMPLICITLY: a flat
6
+ * `{atMessageCount, id, tool}` marks array plus the heuristic
7
+ * `pickCheckpointForTurn` in [[repl-rewind.js]]. This module upgrades that to
8
+ * the EXPLICIT table the gap analysis calls for — one record per turn:
9
+ *
10
+ * turnId
11
+ * -> conversationOffset (message index to truncate back to)
12
+ * -> fileCheckpointId (checkpoint-store cp id; see checkpoint-store.js)
13
+ * -> toolCallIds[] (tool_use_ids this turn issued)
14
+ * -> permissionDecisionIds[] (approval decisions made this turn)
15
+ * -> childAgentIds[] (sub-agents spawned this turn)
16
+ * -> worktreeId (isolation worktree, if any)
17
+ * -> coverage: full | partial | none
18
+ *
19
+ * Everything here is PURE (no timers, no I/O, no git): a caller feeds it events
20
+ * from the agent loop (checkpoint, tool-executing, permission decision,
21
+ * spawn_sub_agent, worktree create) and reads back a table + a restore plan.
22
+ *
23
+ * `coverage` is computed HONESTLY — a turn that ran a shell command, spawned an
24
+ * external process, or saw a user edit can never be `full`, because those
25
+ * effects live outside the file tree a checkpoint captures and cannot be
26
+ * promised back. This is the "不能承诺完全恢复" invariant from the gap doc.
27
+ */
28
+
29
+ export const TURN_COVERAGE = Object.freeze({
30
+ FULL: "full",
31
+ PARTIAL: "partial",
32
+ NONE: "none",
33
+ });
34
+
35
+ export const RESTORE_SCOPE = Object.freeze({
36
+ CONVERSATION: "conversation", // conversation only
37
+ FILES: "files", // files only
38
+ BOTH: "both", // conversation and files
39
+ });
40
+
41
+ /**
42
+ * Classify a tool by how restorable its effects are:
43
+ * - "shell" external side-effects (shell / run_code / spawn) — irreversible.
44
+ * - "edit" in-tree file mutation — reversible via a file checkpoint.
45
+ * - "other" read-only / inert.
46
+ * Substring heuristics keep this robust across tool-name spellings.
47
+ */
48
+ export function classifyToolKind(name) {
49
+ const n = String(name || "").toLowerCase();
50
+ if (/shell|bash|(^|_)exec|run_code|spawn|process|subprocess/.test(n)) {
51
+ return "shell";
52
+ }
53
+ if (
54
+ /write|edit|patch|replace|create_file|delete_file|apply|mkdir|move_file/.test(
55
+ n,
56
+ )
57
+ ) {
58
+ return "edit";
59
+ }
60
+ return "other";
61
+ }
62
+
63
+ /**
64
+ * Deterministic coverage from a turn's side-effect flags.
65
+ * side-effects (shell / external process / user edit) → PARTIAL (can't promise)
66
+ * no file mutation → FULL (nothing to undo)
67
+ * mutation WITH a checkpoint → FULL (fully restorable)
68
+ * mutation WITHOUT a checkpoint → NONE (files unrecoverable)
69
+ */
70
+ export function computeCoverage(flags = {}) {
71
+ const sideEffects = Boolean(
72
+ flags.ranShell ||
73
+ flags.spawnedExternalProcess ||
74
+ flags.userEditedDuringTurn,
75
+ );
76
+ if (sideEffects) return TURN_COVERAGE.PARTIAL;
77
+ if (!flags.mutatedFiles) return TURN_COVERAGE.FULL;
78
+ if (flags.hasFileCheckpoint) return TURN_COVERAGE.FULL;
79
+ return TURN_COVERAGE.NONE;
80
+ }
81
+
82
+ function emptyRecord(turnId, conversationOffset) {
83
+ return {
84
+ turnId,
85
+ conversationOffset:
86
+ Number.isFinite(Number(conversationOffset)) && conversationOffset != null
87
+ ? Number(conversationOffset)
88
+ : null,
89
+ fileCheckpointId: null,
90
+ toolCallIds: [],
91
+ permissionDecisionIds: [],
92
+ childAgentIds: [],
93
+ worktreeId: null,
94
+ // side-effect flags → coverage (kept internal; surfaced via `coverage`)
95
+ _flags: {
96
+ ranShell: false,
97
+ spawnedExternalProcess: false,
98
+ userEditedDuringTurn: false,
99
+ mutatedFiles: false,
100
+ hasFileCheckpoint: false,
101
+ },
102
+ };
103
+ }
104
+
105
+ function pushUnique(list, value) {
106
+ const v = value == null ? null : String(value);
107
+ if (v == null || list.includes(v)) return;
108
+ list.push(v);
109
+ }
110
+
111
+ /**
112
+ * The explicit turn-binding table. In-memory + serializable (toJSON/fromJSON)
113
+ * so a caller can persist it alongside the session record — no I/O here.
114
+ */
115
+ export class TurnBindingLog {
116
+ constructor() {
117
+ this._turns = new Map();
118
+ this._order = [];
119
+ }
120
+
121
+ /** Begin (or re-open) a turn record. Idempotent on turnId. */
122
+ startTurn(turnId, { conversationOffset } = {}) {
123
+ const id = String(turnId);
124
+ if (!this._turns.has(id)) {
125
+ this._turns.set(id, emptyRecord(id, conversationOffset));
126
+ this._order.push(id);
127
+ } else if (conversationOffset != null) {
128
+ const rec = this._turns.get(id);
129
+ if (rec.conversationOffset == null) {
130
+ rec.conversationOffset = Number(conversationOffset);
131
+ }
132
+ }
133
+ return this;
134
+ }
135
+
136
+ _rec(turnId) {
137
+ const id = String(turnId);
138
+ if (!this._turns.has(id)) this.startTurn(id);
139
+ return this._turns.get(id);
140
+ }
141
+
142
+ /** Record a tool call; `kind` (or the tool `name`) drives coverage. */
143
+ recordToolCall(turnId, toolCallId, { kind, name } = {}) {
144
+ const rec = this._rec(turnId);
145
+ pushUnique(rec.toolCallIds, toolCallId);
146
+ const k = kind || classifyToolKind(name);
147
+ if (k === "shell") rec._flags.ranShell = true;
148
+ else if (k === "external-process") rec._flags.spawnedExternalProcess = true;
149
+ else if (k === "edit") rec._flags.mutatedFiles = true;
150
+ return this;
151
+ }
152
+
153
+ recordPermissionDecision(turnId, decisionId) {
154
+ pushUnique(this._rec(turnId).permissionDecisionIds, decisionId);
155
+ return this;
156
+ }
157
+
158
+ recordChildAgent(turnId, childAgentId) {
159
+ pushUnique(this._rec(turnId).childAgentIds, childAgentId);
160
+ return this;
161
+ }
162
+
163
+ /** Bind (or re-bind to the earliest) file checkpoint for this turn. */
164
+ bindCheckpoint(turnId, checkpointId) {
165
+ const rec = this._rec(turnId);
166
+ if (checkpointId != null) {
167
+ if (rec.fileCheckpointId == null)
168
+ rec.fileCheckpointId = String(checkpointId);
169
+ rec._flags.hasFileCheckpoint = true;
170
+ }
171
+ return this;
172
+ }
173
+
174
+ setWorktree(turnId, worktreeId) {
175
+ this._rec(turnId).worktreeId =
176
+ worktreeId == null ? null : String(worktreeId);
177
+ return this;
178
+ }
179
+
180
+ /** Flag an out-of-band user edit during this turn → forces PARTIAL coverage. */
181
+ markUserEdit(turnId) {
182
+ this._rec(turnId)._flags.userEditedDuringTurn = true;
183
+ return this;
184
+ }
185
+
186
+ /** One turn record with its computed coverage (or null). */
187
+ get(turnId) {
188
+ const rec = this._turns.get(String(turnId));
189
+ return rec ? this._view(rec) : null;
190
+ }
191
+
192
+ /** All turns in insertion order, each with computed coverage. */
193
+ list() {
194
+ return this._order.map((id) => this._view(this._turns.get(id)));
195
+ }
196
+
197
+ _view(rec) {
198
+ return {
199
+ turnId: rec.turnId,
200
+ conversationOffset: rec.conversationOffset,
201
+ fileCheckpointId: rec.fileCheckpointId,
202
+ toolCallIds: [...rec.toolCallIds],
203
+ permissionDecisionIds: [...rec.permissionDecisionIds],
204
+ childAgentIds: [...rec.childAgentIds],
205
+ worktreeId: rec.worktreeId,
206
+ coverage: computeCoverage(rec._flags),
207
+ };
208
+ }
209
+
210
+ toJSON() {
211
+ return {
212
+ turns: this._order.map((id) => {
213
+ const r = this._turns.get(id);
214
+ return { ...r, _flags: { ...r._flags } };
215
+ }),
216
+ };
217
+ }
218
+
219
+ static fromJSON(json) {
220
+ const log = new TurnBindingLog();
221
+ for (const r of json?.turns || []) {
222
+ if (!r || r.turnId == null) continue;
223
+ const id = String(r.turnId);
224
+ log._turns.set(id, {
225
+ ...emptyRecord(id, r.conversationOffset),
226
+ fileCheckpointId: r.fileCheckpointId ?? null,
227
+ toolCallIds: Array.isArray(r.toolCallIds) ? [...r.toolCallIds] : [],
228
+ permissionDecisionIds: Array.isArray(r.permissionDecisionIds)
229
+ ? [...r.permissionDecisionIds]
230
+ : [],
231
+ childAgentIds: Array.isArray(r.childAgentIds)
232
+ ? [...r.childAgentIds]
233
+ : [],
234
+ worktreeId: r.worktreeId ?? null,
235
+ _flags: { ...emptyRecord(id)._flags, ...(r._flags || {}) },
236
+ });
237
+ log._order.push(id);
238
+ }
239
+ return log;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Plan a restore of one turn under a chosen scope, with HONEST warnings — the
245
+ * restore UI ("Conversation only / Files only / Both") consumes this. It never
246
+ * over-promises: a `partial`/`none` coverage turn, or a scope that would leave
247
+ * files and conversation out of sync, surfaces a warning instead of silently
248
+ * doing a lossy restore.
249
+ *
250
+ * @param {object} turn a record from TurnBindingLog.get()/list()
251
+ * @param {string} scope one of RESTORE_SCOPE
252
+ * @returns {{scope, coverage, conversation:{truncateTo:number}|null,
253
+ * files:{rewindTo:string}|null, warnings:string[]}}
254
+ */
255
+ export function resolveRestorePlan(turn, scope = RESTORE_SCOPE.BOTH) {
256
+ const warnings = [];
257
+ const s = Object.values(RESTORE_SCOPE).includes(scope)
258
+ ? scope
259
+ : RESTORE_SCOPE.BOTH;
260
+ if (!turn) {
261
+ return {
262
+ scope: s,
263
+ coverage: TURN_COVERAGE.NONE,
264
+ conversation: null,
265
+ files: null,
266
+ warnings: ["unknown turn — nothing to restore"],
267
+ };
268
+ }
269
+ const wantConversation =
270
+ s === RESTORE_SCOPE.CONVERSATION || s === RESTORE_SCOPE.BOTH;
271
+ const wantFiles = s === RESTORE_SCOPE.FILES || s === RESTORE_SCOPE.BOTH;
272
+
273
+ let conversation = null;
274
+ if (wantConversation) {
275
+ if (turn.conversationOffset == null) {
276
+ warnings.push(
277
+ "no conversation offset recorded — cannot rewind the conversation",
278
+ );
279
+ } else {
280
+ conversation = { truncateTo: turn.conversationOffset };
281
+ }
282
+ }
283
+
284
+ let files = null;
285
+ if (wantFiles) {
286
+ if (!turn.fileCheckpointId) {
287
+ warnings.push(
288
+ "no file checkpoint for this turn — files cannot be restored",
289
+ );
290
+ } else {
291
+ files = { rewindTo: turn.fileCheckpointId };
292
+ if (turn.coverage === TURN_COVERAGE.PARTIAL) {
293
+ warnings.push(
294
+ "irreversible side-effects (shell / external process / user edits) can't be undone — file restore is best-effort",
295
+ );
296
+ }
297
+ }
298
+ }
299
+
300
+ // Cross-scope drift: restoring one side but not the other leaves them out of
301
+ // sync when this turn touched files.
302
+ const mutatedFiles =
303
+ turn.coverage !== TURN_COVERAGE.FULL || Boolean(turn.fileCheckpointId);
304
+ if (s === RESTORE_SCOPE.CONVERSATION && turn.fileCheckpointId) {
305
+ warnings.push(
306
+ "conversation-only: files will NOT be reverted — the working tree stays ahead of the conversation",
307
+ );
308
+ } else if (
309
+ s === RESTORE_SCOPE.FILES &&
310
+ turn.conversationOffset != null &&
311
+ mutatedFiles
312
+ ) {
313
+ warnings.push(
314
+ "files-only: the conversation is NOT rewound — it will reference a working tree that no longer matches",
315
+ );
316
+ }
317
+
318
+ return { scope: s, coverage: turn.coverage, conversation, files, warnings };
319
+ }
320
+
321
+ /**
322
+ * Select an inclusive turn range for "Summarize from here / up to here". Pure
323
+ * selection over the log's ordered turns; returns the slice plus the
324
+ * conversation offsets that bound it (nulls preserved when a turn lacks one).
325
+ *
326
+ * @returns {{turns:Array, fromOffset:number|null, toOffset:number|null}}
327
+ */
328
+ export function selectTurnRange(
329
+ log,
330
+ { fromTurnId = null, toTurnId = null } = {},
331
+ ) {
332
+ const all =
333
+ log instanceof TurnBindingLog ? log.list() : Array.isArray(log) ? log : [];
334
+ const idxOf = (id) =>
335
+ id == null ? -1 : all.findIndex((t) => t.turnId === String(id));
336
+ let start = fromTurnId == null ? 0 : idxOf(fromTurnId);
337
+ let end = toTurnId == null ? all.length - 1 : idxOf(toTurnId);
338
+ if (start < 0) start = 0;
339
+ if (end < 0) end = all.length - 1;
340
+ if (start > end) [start, end] = [end, start];
341
+ const turns = all.slice(start, end + 1);
342
+ return {
343
+ turns,
344
+ fromOffset: turns.length ? turns[0].conversationOffset : null,
345
+ toOffset: turns.length ? turns[turns.length - 1].conversationOffset : null,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Bridge: build an explicit TurnBindingLog from the REPL's existing implicit
351
+ * state — `listUserTurns()` output (`{n, index, preview}`, newest-first) and the
352
+ * `{atMessageCount, id, tool}` checkpoint marks. Each mark is attributed to the
353
+ * turn during which it was taken (the turn with the greatest message index still
354
+ * below the mark's `atMessageCount`); the turn's `fileCheckpointId` is its
355
+ * EARLIEST such mark — the pre-mutation snapshot, matching pickCheckpointForTurn.
356
+ *
357
+ * This lets a coverage-aware restore be derived today from what the REPL already
358
+ * tracks, without threading new per-turn events through the agent loop.
359
+ *
360
+ * @param {Array<{n:number,index:number}>} turns
361
+ * @param {Array<{atMessageCount:number,id:string,tool?:string}>} marks
362
+ * @returns {TurnBindingLog}
363
+ */
364
+ export function buildTurnBindingFromMarks(turns = [], marks = []) {
365
+ const log = new TurnBindingLog();
366
+ // Ascending by message index so "owning turn" = greatest index below a mark.
367
+ const ordered = [...(turns || [])]
368
+ .filter((t) => t && Number.isFinite(Number(t.index)))
369
+ .sort((a, b) => Number(a.index) - Number(b.index));
370
+ for (const t of ordered) {
371
+ log.startTurn(`turn-${t.index}`, { conversationOffset: Number(t.index) });
372
+ }
373
+ // Group marks by owning turn, then bind earliest-first so bindCheckpoint keeps
374
+ // the pre-mutation snapshot.
375
+ const owned = new Map(); // turnId -> marks[]
376
+ for (const m of marks || []) {
377
+ const c = Number(m?.atMessageCount);
378
+ if (!Number.isFinite(c) || m?.id == null) continue;
379
+ let owner = null;
380
+ for (const t of ordered) {
381
+ if (Number(t.index) < c) owner = t;
382
+ else break;
383
+ }
384
+ if (!owner) continue;
385
+ const key = `turn-${owner.index}`;
386
+ if (!owned.has(key)) owned.set(key, []);
387
+ owned.get(key).push({ ...m, _c: c });
388
+ }
389
+ for (const [turnId, list] of owned) {
390
+ list.sort((a, b) => a._c - b._c);
391
+ for (const m of list) {
392
+ log.recordToolCall(turnId, m.id, { name: m.tool });
393
+ log.bindCheckpoint(turnId, m.id);
394
+ }
395
+ }
396
+ return log;
397
+ }
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Background-agent dashboard — the interactive agent view behind
3
3
  * `cc daemon view` (no id). Claude-Code `claude agents` parity: one live panel
4
- * over every background session with grouping (Needs input / Working /
4
+ * over every background session with grouping (Needs input / Working / Idle /
5
5
  * Completed / Failed / Stopped), peek + reply, attach hand-off, stop, rename,
6
- * pin, filter, and new-task dispatch.
6
+ * pin, filter, and new-task dispatch. Phase→group classification is the shared
7
+ * `background-agent-phase.js` contract, so `idle` (parked, nothing blocking)
8
+ * is kept distinct from a genuine `needs-input` (a human decision blocks it).
7
9
  *
8
10
  * Design:
9
11
  * - Pure model/render layer (`groupKey`, `buildDashboardModel`,
@@ -17,10 +19,12 @@
17
19
  */
18
20
 
19
21
  import chalk from "chalk";
22
+ import { phaseGroupKey } from "../lib/background-agent-phase.js";
20
23
 
21
24
  export const GROUP_ORDER = [
22
25
  "needs-input",
23
26
  "working",
27
+ "idle",
24
28
  "completed",
25
29
  "failed",
26
30
  "stopped",
@@ -29,24 +33,21 @@ export const GROUP_ORDER = [
29
33
  export const GROUP_TITLES = {
30
34
  "needs-input": "Needs input",
31
35
  working: "Working",
36
+ idle: "Idle",
32
37
  completed: "Completed",
33
38
  failed: "Failed",
34
39
  stopped: "Stopped",
35
40
  };
36
41
 
37
42
  /**
38
- * Group a session: running+idle phase = waiting for a follow-up (the closest
39
- * analog of "needs input" — the worker finished a turn and a client can
40
- * reply); running+turn = working; lost workers count as failed.
43
+ * Group a session into a dashboard bucket. Delegates to the shared
44
+ * `background-agent-phase.js` contract so the worker/supervisor and this panel
45
+ * agree on phase semantics: running + pending approval / blocking phase →
46
+ * "needs-input"; running + idle (parked, nothing blocking) → "idle"; running +
47
+ * working/turn/starting → "working"; lost/unknown → "failed".
41
48
  */
42
49
  export function groupKey(session) {
43
- const status = session?.status;
44
- if (status === "running") {
45
- return session.phase === "idle" ? "needs-input" : "working";
46
- }
47
- if (status === "completed") return "completed";
48
- if (status === "stopped") return "stopped";
49
- return "failed"; // failed | lost | unknown
50
+ return phaseGroupKey(session);
50
51
  }
51
52
 
52
53
  function formatAge(ms) {
@@ -25,6 +25,8 @@ import sharedSettingsHooks from "../lib/settings-hooks.cjs";
25
25
  import sharedHookRunner from "../lib/hook-runner.cjs";
26
26
  import sharedHookEvents from "../lib/settings-hook-events.cjs";
27
27
  import { mergeProviderOptions } from "../lib/provider-options.js";
28
+ import { applyCredentialProxy } from "../lib/credential-proxy.js";
29
+ import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
28
30
  import { getPlanModeManager } from "../lib/plan-mode.js";
29
31
  import { CLISkillLoader } from "../lib/skill-loader.js";
30
32
  import { executeHooks, HookEvents } from "../lib/hook-manager.js";
@@ -2540,7 +2542,10 @@ async function executeToolInner(
2540
2542
  // Same agent-identity env as the foreground path: CLAUDECODE marks
2541
2543
  // "running under the agent"; the session id correlates work to the
2542
2544
  // run (CC_SESSION_ID + CLAUDE_CODE_SESSION_ID for Claude-Code parity).
2543
- env: {
2545
+ // Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
2546
+ // real long-lived secrets out of the spawned command's env — a
2547
+ // no-op (same object) when disabled. See credential-proxy.js.
2548
+ env: applyCredentialProxy({
2544
2549
  ...process.env,
2545
2550
  CLAUDECODE: "1",
2546
2551
  ...(sessionId
@@ -2549,7 +2554,7 @@ async function executeToolInner(
2549
2554
  CLAUDE_CODE_SESSION_ID: String(sessionId),
2550
2555
  }
2551
2556
  : {}),
2552
- },
2557
+ }).env,
2553
2558
  // POSIX: own process group so check_shell{kill}/teardown can signal
2554
2559
  // the whole tree (shell + its grandchild command). No-op on Windows
2555
2560
  // where the tree is killed via taskkill /T instead.
@@ -2715,7 +2720,10 @@ async function executeToolInner(
2715
2720
  // parity): CLAUDECODE=1 marks "running under the agent"; CC_SESSION_ID
2716
2721
  // + its CLAUDE_CODE_SESSION_ID mirror let scripts/hooks correlate work
2717
2722
  // to the agent session (the mirror is what CC-targeting tools expect).
2718
- env: {
2723
+ // Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
2724
+ // real long-lived secrets out of the shell's env — a no-op (same
2725
+ // object) when disabled. See credential-proxy.js.
2726
+ env: applyCredentialProxy({
2719
2727
  ...process.env,
2720
2728
  CLAUDECODE: "1",
2721
2729
  ...(sessionId
@@ -2724,7 +2732,7 @@ async function executeToolInner(
2724
2732
  CLAUDE_CODE_SESSION_ID: String(sessionId),
2725
2733
  }
2726
2734
  : {}),
2727
- },
2735
+ }).env,
2728
2736
  };
2729
2737
  let output;
2730
2738
  if (shellInv.useDefaultShell) {
@@ -6227,12 +6235,18 @@ export async function* agentLoop(messages, options) {
6227
6235
  // an optional caller-provided workflow name) onto EVERY span this run emits,
6228
6236
  // so a collector can group one run's model/tool spans into one workflow.
6229
6237
  if (recorder && typeof recorder.setDefaultAttribute === "function") {
6230
- recorder.setDefaultAttribute("workflow.run_id", runId);
6231
- if (options.workflowName) {
6232
- recorder.setDefaultAttribute(
6233
- "workflow.name",
6234
- String(options.workflowName),
6235
- );
6238
+ // Normalize the unified id set (P2 observability): every id is charset-
6239
+ // sanitized + length-capped, and only these allow-listed keys are stamped,
6240
+ // so span cardinality stays bounded and no content leaks in as an id.
6241
+ const idAttrs = buildTelemetryAttributes({
6242
+ "workflow.run_id": runId,
6243
+ "workflow.name": options.workflowName || undefined,
6244
+ "session.id": options.sessionId || undefined,
6245
+ "agent.id": options.agentId || undefined,
6246
+ "parent_agent.id": options.parentAgentId || undefined,
6247
+ });
6248
+ for (const [k, v] of Object.entries(idAttrs)) {
6249
+ recorder.setDefaultAttribute(k, v);
6236
6250
  }
6237
6251
  }
6238
6252
  yield {