chainlesschain 0.162.163 → 0.162.164

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 (213) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-BQ0kgfA1.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-C5oaVezM.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-DVXGmA56.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDPd9qT0.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BqYwEYou.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-BV2NA742.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Cb7G9oL3.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-CEmQm9n8.js} +2 -2
  49. package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-DZbeN613.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-CxlWrtNj.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-4D8tNBO5.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-Bikj5cmt.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-DmiPBfip.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-KRHroryI.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BliaT02i.js → index-DF6Q8l2h.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
  123. package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-B300RPnT.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-dKx57io7.js → index-tFHy5Wol.js} +1 -1
  132. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-MCB7-raZ.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +71 -14
  151. package/src/commands/doctor.js +85 -1
  152. package/src/commands/plugin.js +226 -9
  153. package/src/commands/review.js +17 -1
  154. package/src/commands/session.js +191 -0
  155. package/src/commands/team.js +19 -1
  156. package/src/data/changelog.json +5 -4
  157. package/src/gateways/ws/session-protocol.js +50 -0
  158. package/src/gateways/ws/ws-agent-handler.js +50 -0
  159. package/src/harness/jsonl-session-store.js +60 -0
  160. package/src/harness/worktree-isolator.js +140 -5
  161. package/src/lib/agent-schedule-store.js +30 -1
  162. package/src/lib/agent-team/team-worktree.js +37 -2
  163. package/src/lib/agent-tool-admission.js +125 -0
  164. package/src/lib/app-preview.js +360 -0
  165. package/src/lib/async-hook-supervisor.cjs +54 -0
  166. package/src/lib/capability-manifest.js +319 -0
  167. package/src/lib/capability-negotiation.js +23 -12
  168. package/src/lib/diagnostic-bundle.js +230 -0
  169. package/src/lib/doctor-checkup.js +223 -4
  170. package/src/lib/execution-location.js +286 -0
  171. package/src/lib/headless-manifest.js +13 -40
  172. package/src/lib/hook-stats-store.cjs +197 -0
  173. package/src/lib/idempotency.js +109 -0
  174. package/src/lib/jsonl-session-store.js +1 -0
  175. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  176. package/src/lib/lsp/lsp-manager.js +56 -0
  177. package/src/lib/monitor-event.js +290 -0
  178. package/src/lib/operation-fingerprint.js +217 -0
  179. package/src/lib/plugin-runtime/bin.js +37 -0
  180. package/src/lib/plugin-runtime/capabilities.js +19 -0
  181. package/src/lib/plugin-runtime/capability-consent.js +28 -0
  182. package/src/lib/plugin-runtime/hooks.js +37 -0
  183. package/src/lib/plugin-runtime/lsp.js +38 -0
  184. package/src/lib/plugin-runtime/mcp.js +40 -0
  185. package/src/lib/plugin-runtime/monitors.js +37 -0
  186. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  187. package/src/lib/pr-automation-policy.js +224 -0
  188. package/src/lib/remote-approval-bridge.js +66 -13
  189. package/src/lib/repl-rewind.js +101 -0
  190. package/src/lib/review-comment-anchor.js +241 -0
  191. package/src/lib/review-pipeline.js +46 -7
  192. package/src/lib/runtime-checkup.js +221 -0
  193. package/src/lib/secret-scan.js +409 -0
  194. package/src/lib/session-branch.js +0 -0
  195. package/src/lib/side-effect-ledger.js +105 -0
  196. package/src/lib/skill-loader.js +32 -0
  197. package/src/lib/skill-path-scope.js +63 -0
  198. package/src/lib/sub-agent-context.js +36 -4
  199. package/src/lib/sub-agent-registry.js +19 -0
  200. package/src/lib/telemetry/span-recorder.js +17 -4
  201. package/src/lib/terminal-context.js +195 -0
  202. package/src/lib/unattended-action-policy.js +182 -0
  203. package/src/lib/worktree-cleanup-safety.js +138 -0
  204. package/src/lib/worktree-isolator.js +8 -0
  205. package/src/lib/worktree-sparse.js +131 -0
  206. package/src/repl/agent-repl.js +152 -38
  207. package/src/runtime/agent-core.js +191 -13
  208. package/src/runtime/diagnostics.js +60 -0
  209. package/src/runtime/headless-runner.js +126 -1
  210. package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +0 -1
  211. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
  212. package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
  213. package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
@@ -2188,7 +2188,7 @@ export async function startAgentRepl(options = {}) {
2188
2188
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
2189
2189
  );
2190
2190
  logger.log(
2191
- ` ${chalk.cyan("/rewind")} Rewind to an earlier turn (double-Esc lists); ${chalk.cyan("/rewind clear")} restores a /clear`,
2191
+ ` ${chalk.cyan("/rewind")} Rewind to an earlier turn (double-Esc lists); ${chalk.cyan("/rewind <n> --files|--conversation")} scopes it; ${chalk.cyan("/rewind <n> --branch")} forks a session; ${chalk.cyan("/rewind clear")} restores a /clear`,
2192
2192
  );
2193
2193
  logger.log(
2194
2194
  ` ${chalk.cyan("/goal <cond>")} Set a session goal checked after each turn (exit-zero:/file-exists:/contains:/regex:/text); ${chalk.cyan("/goal")} shows, ${chalk.cyan("/goal clear")} drops`,
@@ -3020,9 +3020,17 @@ export async function startAgentRepl(options = {}) {
3020
3020
  restoreClearedConversation,
3021
3021
  buildRewindPlan,
3022
3022
  renderRewindWarnings,
3023
+ buildBranchPlan,
3024
+ renderBranchPlan,
3025
+ parseRewindArg,
3026
+ RESTORE_SCOPE,
3023
3027
  } = await import("../lib/repl-rewind.js");
3024
3028
  const arg = trimmed.slice("/rewind".length).trim();
3025
- if (arg === "clear" || arg === "undo-clear") {
3029
+ // Parse the turn number + optional restore scope (--conversation /
3030
+ // --files / --both). Claude-Code parity: a rewind can restore the
3031
+ // conversation only, the files only, or both.
3032
+ const parsed = parseRewindArg(arg);
3033
+ if (parsed.command === "clear") {
3026
3034
  // Restore the conversation stashed by /clear (Claude-Code 2.1.191).
3027
3035
  const r = restoreClearedConversation(
3028
3036
  messages,
@@ -3045,13 +3053,86 @@ export async function startAgentRepl(options = {}) {
3045
3053
  prompt();
3046
3054
  return;
3047
3055
  }
3048
- if (!arg) {
3056
+ if (parsed.command === "branch") {
3057
+ // "从这里分支" (P0-3's fourth restore action): fork a NEW independent
3058
+ // session that keeps history up to the chosen turn and diverges from
3059
+ // there. The origin session is NEVER truncated (preservesParent), so
3060
+ // the live conversation continues unchanged.
3061
+ const targetTurn = listUserTurns(messages, { limit: 1000 }).find(
3062
+ (t) => t.n === parsed.n,
3063
+ );
3064
+ if (!targetTurn) {
3065
+ logger.error(`No such turn: ${parsed.n} — run /rewind to list.`);
3066
+ } else if (!sessionId) {
3067
+ logger.error(
3068
+ "Branch needs a persisted session — none is active yet.",
3069
+ );
3070
+ } else {
3071
+ const turnIndex = targetTurn.index;
3072
+ const plan = buildBranchPlan(
3073
+ messages,
3074
+ _checkpointMarks,
3075
+ turnIndex,
3076
+ {
3077
+ parentSessionId: sessionId,
3078
+ writeIntent: parsed.writeIntent,
3079
+ },
3080
+ );
3081
+ if (!plan || !plan.branchSessionId) {
3082
+ logger.error("Could not plan a branch from that turn.");
3083
+ } else {
3084
+ try {
3085
+ const { createBranchSession } =
3086
+ await import("../harness/jsonl-session-store.js");
3087
+ const res = createBranchSession({
3088
+ branchSessionId: plan.branchSessionId,
3089
+ parentSessionId: sessionId,
3090
+ parentTurnId: plan.parentTurnId,
3091
+ messages: messages.slice(0, turnIndex),
3092
+ meta: { title: `Branch of ${sessionId}` },
3093
+ });
3094
+ logger.log(
3095
+ chalk.green(
3096
+ res.created
3097
+ ? `🌿 branched to ${res.branchSessionId} — ${res.messages} message(s) kept; parent ${sessionId} untouched`
3098
+ : `🌿 branch ${res.branchSessionId} already exists; parent ${sessionId} untouched`,
3099
+ ),
3100
+ );
3101
+ for (const line of renderBranchPlan(plan))
3102
+ logger.log(chalk.yellow(line));
3103
+ logger.log(
3104
+ chalk.gray(
3105
+ ` resume it with cc agent --resume ${res.branchSessionId}` +
3106
+ (plan.requiresWorktree
3107
+ ? " (write task → branch into an isolated worktree)"
3108
+ : ""),
3109
+ ),
3110
+ );
3111
+ } catch (e) {
3112
+ logger.error(`branch failed: ${e.message}`);
3113
+ }
3114
+ }
3115
+ }
3116
+ prompt();
3117
+ return;
3118
+ }
3119
+ if (parsed.command === "list") {
3049
3120
  logger.log(chalk.bold("\nRewind — pick a user turn (newest first):"));
3050
3121
  logger.log(renderTurnList(listUserTurns(messages)));
3051
3122
  const fileHint = _checkpointMarks.length
3052
3123
  ? " (restores files to that point too — checkpoints are on)"
3053
3124
  : " (conversation only — start with --checkpoint / git to also rewind files)";
3054
3125
  logger.log(chalk.gray(`Usage: /rewind <n>${fileHint}`));
3126
+ logger.log(
3127
+ chalk.gray(
3128
+ " scope: /rewind <n> --conversation | --files | --both (default)",
3129
+ ),
3130
+ );
3131
+ logger.log(
3132
+ chalk.gray(
3133
+ " branch: /rewind <n> --branch [--write] forks an independent session (parent kept)",
3134
+ ),
3135
+ );
3055
3136
  if (_clearedConversation) {
3056
3137
  logger.log(
3057
3138
  chalk.gray(
@@ -3060,50 +3141,67 @@ export async function startAgentRepl(options = {}) {
3060
3141
  );
3061
3142
  }
3062
3143
  } else {
3063
- // Snapshot BEFORE rewindToTurn truncates `messages`: buildRewindPlan
3064
- // needs the target turn still present to bind it to its checkpoint.
3144
+ const scope = parsed.scope;
3145
+ const rewindConversation = scope !== RESTORE_SCOPE.FILES;
3146
+ const restoreFiles = scope !== RESTORE_SCOPE.CONVERSATION;
3147
+ // Snapshot + locate the target turn BEFORE any truncation:
3148
+ // buildRewindPlan needs the turn still present to bind its checkpoint,
3149
+ // and a files-only rewind keeps the conversation intact entirely.
3065
3150
  const preRewind = messages.slice();
3066
- const res = rewindToTurn(messages, arg);
3067
- if (!res) {
3068
- logger.error(`No such turn: ${arg} — run /rewind to list.`);
3151
+ const targetTurn = listUserTurns(messages, { limit: 1000 }).find(
3152
+ (t) => t.n === parsed.n,
3153
+ );
3154
+ if (!targetTurn) {
3155
+ logger.error(`No such turn: ${parsed.n} — run /rewind to list.`);
3069
3156
  } else {
3070
- logger.log(
3071
- chalk.yellow(
3072
- `⎌ rewound — dropped ${res.removed} message(s); edit and resend below`,
3073
- ),
3074
- );
3075
- // Coverage-aware honesty (P1 turn/checkpoint binding): print what a
3076
- // restore can and cannot promise for this turn BEFORE offering the
3077
- // file restore — a shell/external side-effect → PARTIAL, no
3078
- // checkpoint → files can't be restored, conversation/files drift.
3079
- // Derived from the REPL's existing marks (no new agent-loop events);
3157
+ const turnIndex = targetTurn.index;
3158
+ // 1) Conversation rewind (skipped for files-only).
3159
+ if (rewindConversation) {
3160
+ const res = rewindToTurn(messages, parsed.n);
3161
+ logger.log(
3162
+ chalk.yellow(
3163
+ `⎌ rewound — dropped ${res.removed} message(s); edit and resend below`,
3164
+ ),
3165
+ );
3166
+ } else {
3167
+ logger.log(
3168
+ chalk.yellow("⎌ files-only rewind — conversation left intact"),
3169
+ );
3170
+ }
3171
+ // 2) Coverage-aware honesty for the CHOSEN scope (P1 turn/checkpoint
3172
+ // binding): print what a restore can and cannot promise — a shell/
3173
+ // external side-effect → PARTIAL, no checkpoint → files can't be
3174
+ // restored, and a scope-specific conversation/files drift. Derived
3175
+ // from the REPL's existing marks (no new agent-loop events);
3080
3176
  // best-effort so the advisory never blocks the rewind itself.
3081
3177
  try {
3082
3178
  const plan = buildRewindPlan(
3083
3179
  preRewind,
3084
3180
  _checkpointMarks,
3085
- res.index,
3181
+ turnIndex,
3182
+ scope,
3086
3183
  );
3087
3184
  for (const line of renderRewindWarnings(plan))
3088
3185
  logger.log(chalk.yellow(line));
3089
3186
  } catch {
3090
3187
  /* advisory only — a rewind must never fail over its own warning */
3091
3188
  }
3092
- // Claude-Code parity: rewind restores files too. Match the dropped
3093
- // turn to the snapshot taken just before it first mutated the tree,
3094
- // then offer to roll the working tree back to it (undoable — the
3095
- // restore takes its own safety checkpoint first).
3096
- const cp = pickCheckpointForTurn(_checkpointMarks, res.index);
3097
- pruneMarksAfter(_checkpointMarks, res.index);
3189
+ // 3) File restore (skipped for conversation-only). Match the turn to
3190
+ // the snapshot taken just before it first mutated the tree, then
3191
+ // offer to roll the working tree back to it (undoable — the restore
3192
+ // takes its own safety checkpoint first). Prune dropped-turn marks
3193
+ // only when the conversation was actually truncated.
3194
+ const cp = restoreFiles
3195
+ ? pickCheckpointForTurn(_checkpointMarks, turnIndex)
3196
+ : null;
3197
+ if (rewindConversation)
3198
+ pruneMarksAfter(_checkpointMarks, turnIndex);
3098
3199
  if (cp) {
3099
3200
  const q = (p) => new Promise((r) => rl.question(p, r));
3100
- const ans = (
3101
- await q(
3102
- chalk.yellow(
3103
- ` Also restore files to before this turn? (checkpoint ${cp.id}) [Y/n] `,
3104
- ),
3105
- )
3106
- )
3201
+ const promptText = rewindConversation
3202
+ ? ` Also restore files to before this turn? (checkpoint ${cp.id}) [Y/n] `
3203
+ : ` Restore files to before this turn? (checkpoint ${cp.id}) [Y/n] `;
3204
+ const ans = (await q(chalk.yellow(promptText)))
3107
3205
  .trim()
3108
3206
  .toLowerCase();
3109
3207
  if (ans === "" || ans === "y" || ans === "yes") {
@@ -3120,7 +3218,7 @@ export async function startAgentRepl(options = {}) {
3120
3218
  );
3121
3219
  } catch (e) {
3122
3220
  logger.error(
3123
- ` file restore skipped: ${e.message} (conversation already rewound)`,
3221
+ ` file restore skipped: ${e.message}${rewindConversation ? " (conversation already rewound)" : ""}`,
3124
3222
  );
3125
3223
  }
3126
3224
  } else {
@@ -3130,9 +3228,19 @@ export async function startAgentRepl(options = {}) {
3130
3228
  ),
3131
3229
  );
3132
3230
  }
3231
+ } else if (restoreFiles && scope === RESTORE_SCOPE.FILES) {
3232
+ logger.log(
3233
+ chalk.gray(
3234
+ " no checkpoint captured for that turn — nothing to restore",
3235
+ ),
3236
+ );
3133
3237
  }
3134
3238
  prompt();
3135
- if (res.text) rl.write(res.text);
3239
+ // Prefill the input line with the turn's text only when the
3240
+ // conversation was rewound (edit-and-resend); files-only leaves the
3241
+ // conversation and the prompt untouched.
3242
+ if (rewindConversation && typeof targetTurn.content === "string")
3243
+ rl.write(targetTurn.content);
3136
3244
  return;
3137
3245
  }
3138
3246
  }
@@ -3437,7 +3545,9 @@ export async function startAgentRepl(options = {}) {
3437
3545
  if (!_asyncHookSupervisor) {
3438
3546
  const { AsyncHookSupervisor } =
3439
3547
  await import("../lib/async-hook-supervisor.cjs");
3440
- _asyncHookSupervisor = new AsyncHookSupervisor();
3548
+ _asyncHookSupervisor = new AsyncHookSupervisor({
3549
+ persistStats: true,
3550
+ });
3441
3551
  }
3442
3552
  dispatchAsyncHooks(_settingsHooks, "ConfigChange", payload, {
3443
3553
  cwd,
@@ -4813,7 +4923,9 @@ export async function startAgentRepl(options = {}) {
4813
4923
  if (!_asyncHookSupervisor) {
4814
4924
  const { AsyncHookSupervisor } =
4815
4925
  await import("../lib/async-hook-supervisor.cjs");
4816
- _asyncHookSupervisor = new AsyncHookSupervisor();
4926
+ _asyncHookSupervisor = new AsyncHookSupervisor({
4927
+ persistStats: true,
4928
+ });
4817
4929
  }
4818
4930
  dispatchAsyncHooks(
4819
4931
  _settingsHooks,
@@ -5100,7 +5212,7 @@ export async function startAgentRepl(options = {}) {
5100
5212
  if (_settingsHooks && !_asyncHookSupervisor) {
5101
5213
  const { AsyncHookSupervisor } =
5102
5214
  await import("../lib/async-hook-supervisor.cjs");
5103
- _asyncHookSupervisor = new AsyncHookSupervisor();
5215
+ _asyncHookSupervisor = new AsyncHookSupervisor({ persistStats: true });
5104
5216
  }
5105
5217
  const {
5106
5218
  content: response,
@@ -5442,7 +5554,9 @@ export async function startAgentRepl(options = {}) {
5442
5554
  if (!_asyncHookSupervisor) {
5443
5555
  const { AsyncHookSupervisor } =
5444
5556
  await import("../lib/async-hook-supervisor.cjs");
5445
- _asyncHookSupervisor = new AsyncHookSupervisor();
5557
+ _asyncHookSupervisor = new AsyncHookSupervisor({
5558
+ persistStats: true,
5559
+ });
5446
5560
  }
5447
5561
  dispatchAsyncHooks(
5448
5562
  _settingsHooks,
@@ -26,6 +26,7 @@ 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
28
  import { applyCredentialProxy } from "../lib/credential-proxy.js";
29
+ import { describeBackgroundCommand } from "../lib/terminal-context.js";
29
30
  import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
30
31
  import { getPlanModeManager } from "../lib/plan-mode.js";
31
32
  import { CLISkillLoader } from "../lib/skill-loader.js";
@@ -45,6 +46,11 @@ import {
45
46
  import { createToolContext } from "../tools/tool-context.js";
46
47
  import { createToolTelemetryRecord } from "../tools/tool-telemetry.js";
47
48
  import { isAbortError, throwIfAborted } from "../lib/abort-utils.js";
49
+ import {
50
+ classifyEditReplay,
51
+ editIdempotencyKey,
52
+ EDIT_REPLAY,
53
+ } from "../lib/idempotency.js";
48
54
  import { buildSearchCommand } from "../lib/search-command.js";
49
55
  import { discoverCommands } from "../lib/slash-commands.js";
50
56
  import {
@@ -138,18 +144,37 @@ function _readBgStream(stream) {
138
144
  }
139
145
 
140
146
  /**
141
- * Snapshot of background shell tasks (for REPL/host status surfaces).
142
- * @returns {Array<{id:string,status:string,command:string,exitCode:number|null}>}
147
+ * Snapshot of background shell tasks (for REPL/host status surfaces AND the
148
+ * model-facing `check_shell { list:true }` tool).
149
+ *
150
+ * P1-2 (terminal-context): the `command` string can carry an inline secret
151
+ * (`FOO=token cmd`, `curl -H "Authorization: Bearer …"`); this list is shown to
152
+ * the model and rendered in `/tasks`, so run it through
153
+ * `describeBackgroundCommand` to secret-redact the command and expose the PID.
154
+ * The task's own lifecycle `status` (running/exited/failed/error) is preserved
155
+ * — that is distinct from a health check, so it is NOT replaced. `stoppable` is
156
+ * gated on the task still running (a valid pid alone doesn't make a dead task
157
+ * stoppable).
158
+ * @returns {Array<{id:string,status:string,command:string,pid:number|null,
159
+ * stoppable:boolean,exitCode:number|null,startedAt:string,endedAt:string}>}
143
160
  */
144
161
  export function listBackgroundShellTasks() {
145
- return Array.from(_backgroundShellTasks.values()).map((t) => ({
146
- id: t.id,
147
- status: t.status,
148
- command: t.command,
149
- exitCode: t.exitCode,
150
- startedAt: t.startedAt,
151
- endedAt: t.endedAt,
152
- }));
162
+ return Array.from(_backgroundShellTasks.values()).map((t) => {
163
+ const desc = describeBackgroundCommand({
164
+ command: t.command,
165
+ pid: t.child?.pid,
166
+ });
167
+ return {
168
+ id: t.id,
169
+ status: t.status,
170
+ command: desc.command, // P1-2: secret-redacted
171
+ pid: desc.pid, // P1-2: PID surfaced
172
+ stoppable: t.status === "running" && desc.stoppable,
173
+ exitCode: t.exitCode,
174
+ startedAt: t.startedAt,
175
+ endedAt: t.endedAt,
176
+ };
177
+ });
153
178
  }
154
179
 
155
180
  // Kill a background task's whole process tree. Because tasks are spawned with
@@ -2100,6 +2125,26 @@ async function _withPostEditDiagnostics(result, filePath, cwd) {
2100
2125
  return newDiagnostics ? { ...result, newDiagnostics } : result;
2101
2126
  }
2102
2127
 
2128
+ /**
2129
+ * Drop skills whose `paths:` frontmatter scopes them to a different subtree than
2130
+ * the agent's cwd (large-monorepo lazy skill surface). A skill with no `paths`
2131
+ * applies everywhere, so a skill set with none is returned unchanged. Best-effort:
2132
+ * any failure (or an unlocatable project root → relCwd "") falls open to the full
2133
+ * list, never hiding a skill spuriously.
2134
+ */
2135
+ async function filterSkillsByCwd(skills, cwd) {
2136
+ try {
2137
+ if (!Array.isArray(skills) || skills.every((s) => !s?.paths)) return skills;
2138
+ const { filterSkillsByRelCwd, relCwdForCwd } =
2139
+ await import("../lib/skill-path-scope.js");
2140
+ const { findProjectRoot } = await import("../lib/project-detector.js");
2141
+ const root = (cwd && findProjectRoot(cwd)) || cwd || "";
2142
+ return filterSkillsByRelCwd(skills, relCwdForCwd(cwd, root));
2143
+ } catch {
2144
+ return skills; // fail-open: never hide a skill because scoping errored
2145
+ }
2146
+ }
2147
+
2103
2148
  async function executeToolInner(
2104
2149
  name,
2105
2150
  args,
@@ -2323,6 +2368,30 @@ async function executeToolInner(
2323
2368
  replaceAll,
2324
2369
  );
2325
2370
  if (count === 0) {
2371
+ // Idempotent replay (P0-2 "Diff Apply 内容哈希"): a resumed edit whose
2372
+ // old_string is already gone but whose new_string is present already
2373
+ // landed. Report a no-op success (NO write occurs — zero data risk)
2374
+ // rather than a misleading "not found" error, so a recovered worker
2375
+ // doesn't get stuck re-issuing an edit that already happened.
2376
+ const replay = classifyEditReplay({
2377
+ content,
2378
+ oldString: args.old_string,
2379
+ newString: args.new_string,
2380
+ });
2381
+ if (replay === EDIT_REPLAY.ALREADY_APPLIED) {
2382
+ return attachDescriptor({
2383
+ success: true,
2384
+ path: filePath,
2385
+ alreadyApplied: true,
2386
+ idempotencyKey: editIdempotencyKey({
2387
+ path: args.path,
2388
+ oldString: args.old_string,
2389
+ newString: args.new_string,
2390
+ replaceAll,
2391
+ }),
2392
+ note: "edit already applied (new_string present, old_string absent) — no change made",
2393
+ });
2394
+ }
2326
2395
  return attachDescriptor({ error: "old_string not found in file" });
2327
2396
  }
2328
2397
  // Require a UNIQUE match (Claude-Code Edit parity) — replacing the first
@@ -3220,6 +3289,16 @@ async function executeToolInner(
3220
3289
  goalMaxTokens: args.goal_max_tokens ?? null,
3221
3290
  goalMaxCost: args.goal_max_cost ?? null,
3222
3291
  goalMaxTime: args.goal_max_time ?? null,
3292
+ // P1-8: action classes this unattended task may still perform (e.g.
3293
+ // "publish", "external_message"). Accepts an array or a comma list.
3294
+ unattendedAllowlist: Array.isArray(args.unattended_allow)
3295
+ ? args.unattended_allow
3296
+ : typeof args.unattended_allow === "string"
3297
+ ? args.unattended_allow
3298
+ .split(",")
3299
+ .map((s) => s.trim())
3300
+ .filter(Boolean)
3301
+ : null,
3223
3302
  };
3224
3303
  if (action === "wakeup") {
3225
3304
  if (!args.prompt) {
@@ -3620,7 +3699,10 @@ async function executeToolInner(
3620
3699
  }
3621
3700
 
3622
3701
  case "run_skill": {
3623
- const allSkills = skillLoader.getResolvedSkills().filter(skillAllowed);
3702
+ const allSkills = await filterSkillsByCwd(
3703
+ skillLoader.getResolvedSkills().filter(skillAllowed),
3704
+ cwd,
3705
+ );
3624
3706
  if (allSkills.length === 0) {
3625
3707
  return attachDescriptor({
3626
3708
  error: _skillAllowlist
@@ -3766,7 +3848,10 @@ async function executeToolInner(
3766
3848
  }
3767
3849
 
3768
3850
  case "list_skills": {
3769
- let skills = skillLoader.getResolvedSkills().filter(skillAllowed);
3851
+ let skills = await filterSkillsByCwd(
3852
+ skillLoader.getResolvedSkills().filter(skillAllowed),
3853
+ cwd,
3854
+ );
3770
3855
  if (skills.length === 0) {
3771
3856
  return attachDescriptor({
3772
3857
  error: _skillAllowlist
@@ -6379,6 +6464,22 @@ function extractInitialPromptText(messages) {
6379
6464
  return "";
6380
6465
  }
6381
6466
 
6467
+ /**
6468
+ * Stable id for a permission GATE decision, so a denied/gated tool span can be
6469
+ * correlated with the decision that blocked it. Only gated results carry a
6470
+ * `policy` (allow-path tools execute with no distinct decision), so this returns
6471
+ * null when there's nothing to identify. Deterministic (call id + gate) — no
6472
+ * clock/RNG — so a consumer holding the tool-result's policy + tool_call_id can
6473
+ * recompute it. Not exported (no shim-parity impact) — only the telemetry seam
6474
+ * uses it.
6475
+ */
6476
+ function permissionDecisionId(callId, policy) {
6477
+ if (!policy || typeof policy !== "object" || !policy.decision) return null;
6478
+ const gate = policy.via || policy.decision;
6479
+ const base = callId || "call";
6480
+ return `${base}:perm:${gate}`;
6481
+ }
6482
+
6382
6483
  export async function* agentLoop(messages, options) {
6383
6484
  // Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
6384
6485
  // When options.iterationBudget is provided (e.g. from parent agent),
@@ -6858,6 +6959,18 @@ export async function* agentLoop(messages, options) {
6858
6959
  }
6859
6960
  }
6860
6961
 
6962
+ // Per-span unified ids (P2 observability): the run-level default attributes
6963
+ // stamp session/agent/workflow ids on every span, but turn/prompt ids are
6964
+ // per-iteration. A "turn" is one model iteration of the run; "prompt" is the
6965
+ // model request within that turn. Normalized through buildTelemetryAttributes
6966
+ // so they get the same charset-sanitized + cardinality-bounded treatment as
6967
+ // the run-level ids (turn.id correlates with the agent.iteration counter).
6968
+ const modelIdAttrs = recorder
6969
+ ? buildTelemetryAttributes({
6970
+ turnId: `${runId}:t${budget.consumed}`,
6971
+ promptId: `${runId}:t${budget.consumed}:p`,
6972
+ })
6973
+ : null;
6861
6974
  const result = await _withSpan(
6862
6975
  recorder,
6863
6976
  "agent.model",
@@ -6865,6 +6978,7 @@ export async function* agentLoop(messages, options) {
6865
6978
  "gen_ai.system": options.provider || "ollama",
6866
6979
  "gen_ai.request.model": options.model || "unknown",
6867
6980
  "agent.iteration": budget.consumed,
6981
+ ...(modelIdAttrs || {}),
6868
6982
  },
6869
6983
  () => llmCall(callMessages, options),
6870
6984
  (span, r) => {
@@ -6886,6 +7000,21 @@ export async function* agentLoop(messages, options) {
6886
7000
  Array.isArray(r?.message?.tool_calls) &&
6887
7001
  r.message.tool_calls.length > 0,
6888
7002
  );
7003
+ // response CONTENT is opt-in (--otlp-content): stamped only when
7004
+ // enabled, redacted + length-capped through the same normalizer; the
7005
+ // field is entirely absent by default so default OTLP stays unchanged.
7006
+ if (options.otlpIncludeContent === true && r?.message?.content) {
7007
+ const respAttrs = buildTelemetryAttributes(
7008
+ { response: r.message.content },
7009
+ { includeContent: true },
7010
+ );
7011
+ if (respAttrs["content.response"] != null) {
7012
+ span.setAttribute(
7013
+ "content.response",
7014
+ respAttrs["content.response"],
7015
+ );
7016
+ }
7017
+ }
6889
7018
  },
6890
7019
  "model_error",
6891
7020
  );
@@ -7163,17 +7292,66 @@ export async function* agentLoop(messages, options) {
7163
7292
 
7164
7293
  let toolResult;
7165
7294
  let toolError = null;
7295
+ // Per-span unified ids (P2 observability): a tool span carries its turn.id
7296
+ // (correlating with the model span of the same iteration), the provider's
7297
+ // tool_use.id (so a tool-result event can be tied back to its call), and —
7298
+ // when auto-checkpoint fired before a mutating tool — the checkpoint.id the
7299
+ // user could restore to. All normalized through buildTelemetryAttributes.
7300
+ // tool_arguments CONTENT is opt-in (--otlp-content): the alias key is only
7301
+ // present when explicitly enabled, so by default the field is omitted
7302
+ // entirely (byte-identical default OTLP), and even opted-in it's length-
7303
+ // capped by redactContent. Mirrors the run-level content.prompt opt-in.
7304
+ const toolContentOptIn = options.otlpIncludeContent === true;
7305
+ const toolIdAttrs = recorder
7306
+ ? buildTelemetryAttributes(
7307
+ {
7308
+ turnId: `${runId}:t${budget.consumed}`,
7309
+ toolUseId: call.id,
7310
+ checkpointId: cpId || undefined,
7311
+ ...(toolContentOptIn ? { toolArguments: toolArgs } : {}),
7312
+ },
7313
+ { includeContent: toolContentOptIn },
7314
+ )
7315
+ : null;
7166
7316
  try {
7167
7317
  toolResult = await _withSpan(
7168
7318
  recorder,
7169
7319
  "agent.tool",
7170
- { "tool.name": toolName },
7320
+ { "tool.name": toolName, ...(toolIdAttrs || {}) },
7171
7321
  () => executeTool(toolName, toolArgs, toolContext),
7172
7322
  (span, r) => {
7173
7323
  span.setAttribute(
7174
7324
  "tool.is_error",
7175
7325
  !!(r && typeof r === "object" && r.error),
7176
7326
  );
7327
+ // permission.decision_id (P2): a GATED tool result carries a
7328
+ // `policy` (deny / ask-fail / host-block / sandbox); allow-path
7329
+ // tools execute with no distinct decision. Stamp a stable id
7330
+ // (derived from the call + gate, so it's recomputable from the
7331
+ // tool-result's policy + tool_call_id) plus the low-cardinality
7332
+ // decision, letting a blocked tool span be tied to its decision.
7333
+ const decId =
7334
+ r && typeof r === "object"
7335
+ ? permissionDecisionId(call.id, r.policy)
7336
+ : null;
7337
+ if (decId) {
7338
+ const permAttrs = buildTelemetryAttributes({
7339
+ decisionId: decId,
7340
+ "permission.decision": r.policy.decision,
7341
+ });
7342
+ if (permAttrs["permission.decision_id"]) {
7343
+ span.setAttribute(
7344
+ "permission.decision_id",
7345
+ permAttrs["permission.decision_id"],
7346
+ );
7347
+ }
7348
+ if (permAttrs["permission.decision"]) {
7349
+ span.setAttribute(
7350
+ "permission.decision",
7351
+ permAttrs["permission.decision"],
7352
+ );
7353
+ }
7354
+ }
7177
7355
  },
7178
7356
  "tool_error",
7179
7357
  );
@@ -288,6 +288,66 @@ export async function collectDoctorReport() {
288
288
  };
289
289
  }
290
290
 
291
+ /**
292
+ * Parse `git worktree list --porcelain` output into `{ path, branch }` entries.
293
+ *
294
+ * Pure (no I/O) so it can be unit-tested against fixed fixtures. A detached
295
+ * worktree has no `branch` line and yields `branch: null`.
296
+ */
297
+ export function parseGitWorktrees(porcelain) {
298
+ if (!porcelain || typeof porcelain !== "string") return [];
299
+ const entries = [];
300
+ let cur = null;
301
+ for (const line of porcelain.split("\n")) {
302
+ const trimmed = line.trimEnd();
303
+ if (trimmed.startsWith("worktree ")) {
304
+ if (cur) entries.push(cur);
305
+ cur = { path: trimmed.slice("worktree ".length).trim(), branch: null };
306
+ } else if (trimmed.startsWith("branch ") && cur) {
307
+ cur.branch = trimmed
308
+ .slice("branch ".length)
309
+ .replace("refs/heads/", "")
310
+ .trim();
311
+ }
312
+ }
313
+ if (cur) entries.push(cur);
314
+ return entries;
315
+ }
316
+
317
+ /** Best-effort live git worktree list for the current repo. */
318
+ export function collectWorktrees() {
319
+ return parseGitWorktrees(safeExec("git worktree list --porcelain"));
320
+ }
321
+
322
+ /**
323
+ * Map a doctor report + ambient runtime signals into the input contract of
324
+ * `buildDiagnosticBundle` (src/lib/diagnostic-bundle.js).
325
+ *
326
+ * Pure & deterministic aside from reading this process's own platform tags:
327
+ * the caller gathers live signals (env, worktrees, lockfiles) and passes them
328
+ * in, so the mapping itself is unit-testable. Session-runtime fields
329
+ * (connectionState, reconnectHistory, trace, redactionEvents) are absent from a
330
+ * cold `cc doctor` invocation and left to the bundle builder's safe defaults.
331
+ */
332
+ export function buildDoctorDiagnosticInput(report, signals = {}) {
333
+ const r = report || {};
334
+ return {
335
+ version: r.version || null,
336
+ platform: {
337
+ os: process.platform,
338
+ arch: process.arch,
339
+ node: process.versions?.node || null,
340
+ },
341
+ ports: (Array.isArray(r.ports) ? r.ports : [])
342
+ .filter((p) => p && p.open)
343
+ .map((p) => ({ port: p.port, proto: p.name })),
344
+ worktrees: Array.isArray(signals.worktrees) ? signals.worktrees : [],
345
+ lockfiles: Array.isArray(signals.lockfiles) ? signals.lockfiles : [],
346
+ env: signals.env && typeof signals.env === "object" ? signals.env : {},
347
+ notes: signals.notes != null ? signals.notes : null,
348
+ };
349
+ }
350
+
291
351
  /**
292
352
  * Collect a machine-readable status report.
293
353
  *