chainlesschain 0.162.170 → 0.162.172

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 (186) hide show
  1. package/bin/chainlesschain.js +8 -0
  2. package/package.json +6 -1
  3. package/src/assets/web-panel/assets/{AIOps-BTXN24I3.js → AIOps-oKCWU-LK.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BBB1VF4j.js → ActionButton-4CBfc5E4.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BVCXq0k6.js → Analytics-BEmR-k_b.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-C3EOo2s5.js → AppLayout-D6a4ZnrC.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-CsJTZSMv.js → Artifacts-BRWIHCPl.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CP0AllJ_.js → Audit-CzGKlkTc.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BCAjs08m.js → BackgroundAgents-CmMYN10o.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-BxQI5XH4.js → Backup-CJ6e0ukD.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-Bu-PNzC5.js → BaseInput-BpEvDrNt.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-DjPQynup.js → Chat-wWuwwjKu.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CygkRopu.js → ChatBubbleRenderer-CJLyGo_u.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BxL6xm7Z.js → Checkbox-C6pW60Br.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CcyB7h_6.js → Codegen-DF-nP22Y.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-8NQiKJJx.js → Col-BuueEwZW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-B31qy5OS.js → Community-DuE0Yzwr.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-C-5fAsZ4.js → Compact-BrHim8EB.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BTSccvF0.js → Compliance-DgAet8f_.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-BXctU9IK.js → Cowork-B9wY05lx.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-CxVFX9NG.js → Cron-CvNBxfJX.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DEF3dq8M.js → Crosschain-BnZRENa0.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-BmKcE9he.js → DID-DiqYLHTI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CECBq_wG.js → Dashboard-t3SsRzZL.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-U-iuPB0n.js → Dropdown-BCjMLam9.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-D8BIlSPL.js → EmailListRenderer-D2v9-IHJ.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BzQGfUH-.js → FamilyGuardDashboard-_iQu35qa.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-4_WlOqLZ.js → Federation-Bkty8VqB.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-gtrW-DdF.js → FormItemContext-DdPW81BY.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-BLcvJJB6.js → GenericCardRenderer-DrNYM4Zj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Bk0janGD.js → Git-BjF-B2h0.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CcfyMlV8.js → Governance-DPhGdJ1v.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CBLrAf4M.js → Inference--PPksOVj.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-ClguhteZ.js → KnowledgeGraph-y5WdV_JV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Dp_oeKfJ.js → Logs-B6_x7zba.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-Bg8zSyF8.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BHk50smM.js → Marketplace-CHpfMu1A.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BAY3CV6J.js → McpTools-qqjtX3Cf.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-w0pG2AKx.js → Memory-hASSgxbw.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-C0VkDUE3.js → MobileBridge-Dwh7Bhcf.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BCvQ80I9.js → MobileProjects-D09voh7W.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-DSIl41Tt.js → Mtc-9G1dXeId.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-D1mzUlIr.js → MtcAudit-CWtYXj6f.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-CeJz4VSS.js → Multisig-bDI0EnDk.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-_O3aweu8.js → NLProgramming-CTanRaBP.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Dt7_pO86.js → Notes-BdQENJFv.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-Dhg3Nktv.js → NotificationSettings-JvnQdStO.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-QsAPcTb2.js → OrderTableRenderer-BMGL9RPJ.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BuiaUPkv.js → Organization-sBZnIb2D.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C3jb6knl.js → Overflow-CzOhNGE6.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BOMyrLkb.js → P2P-D_D2yqda.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-UvLStCgS.js → PdhVaultBrowser-D5lKd_Kx.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-B9cN0dJH.js → Permissions-L5s-wENg.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DNjW5qro.js → PersonalDataHub-iI49x6cH.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-CFLyZ6Sh.js → Pipeline-DPHqyk09.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D3w6vOxf.js → Privacy-B-ZL9360.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-cwcVsn8K.js → ProjectInit-f05YzsN2.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-aUoAroc-.js → ProjectSettings-DZLKS6ex.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-CrzQv7Zv.js → Projects-ButF3DyM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-jZaPizKR.js → Providers-CFDSXnkR.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DNzmwcfg.js → QrScannerModal-1N6Sdxdm.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-m-5Gwt70.js → QuickAsk-tEqRwRSR.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-CrrAjNxP.js → Recommend-D4ZVHIwz.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-BDCMjRFe.js → RemoteSession-Cqm32lhm.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-CPCt1Jc8.js → Reputation-BnjVsU_a.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-C-RorxHs.js → Row-DzpHyTuz.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-CnQs4TuY.js → RssFeed-Du5-ouwx.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-B1jkeFZY.js → Search-CYztyf2o.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CGKI3vvd.js → Security-C1hVxy8Z.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-DQjesYF8.js → Services-BscEbAZ2.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C9Ipj0A6.js → Skeleton-B2sMLqKQ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-D2j54i9O.js → Skills-CoHKax06.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-xhrVjTeb.js → Sla-Bpie5KO6.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-C6KMOUCa.js → SpeechSettings-BX63ijn5.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CmIPU5wR.js → SyncSettings-Bf72so5A.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-B-T1J42r.js → Tasks-CVB2PZk4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-Aq669sjw.js → Templates-q0qyclJx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BKWUWNXB.js → Tenant-CW-W-i0k.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEeAvA8L.js → Terminal-D7QoPiXQ.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C465ySdn.js → TimelineRenderer-BaRDAbyA.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BgVqF-bX.js → Tokens-D6U4PRW8.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-BMNHmxwH.js → Trigger-ClBDWdLo.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-B_adk89L.js → Trust-C_M6rWPq.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-Biw38mog.js → UkeySign-bXHx8s4C.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-AC-eaw5k.js → VideoEditing-BcIt_ieI.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-8lQ5_t0X.js → Wallet-Dd-4jcLs.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-JDmH_-DF.js → WebAuthn-B4F6yxQK.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DHQAdITG.js → WorkflowEditor-BEHkpfJq.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-C3qauUVv.js → chat-D7hhRpIO.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DLP70JGe.js → colors-AMRhrRzS.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DjsoqA5t.js → compact-item-BYpsOET5.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CfEdl1in.js → createContext-CoUkR4LQ.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-BRylvxzY.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DWwHHlBG.js → hasIn-CJKJMKki.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Dfka-vzJ.js → index-8pmZHnQU.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DgQ92xdO.js → index-BBJFiZ9Y.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-VtCz3A64.js → index-BP69Ul3S.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-C8wrUFzY.js → index-BTFHYauX.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D5VL0NYy.js → index-BUJt2ar4.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-B-rD2rhi.js → index-Bj30Aq6h.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-gu8zBhtp.js → index-BlpuqGt5.js} +1 -1
  102. package/src/assets/web-panel/assets/index-Bzvxvzlk.js +1 -0
  103. package/src/assets/web-panel/assets/{index-CgruUvB3.js → index-C19xZKIP.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DTEcPJGf.js → index-C6bg22vc.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C4k6JGm_.js → index-CH6XEWdj.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C0xMzvk8.js → index-CK1Kknpl.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-UEbYl2pu.js → index-CNyLbBGj.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dmt1HDWZ.js → index-COg__P6M.js} +19 -19
  109. package/src/assets/web-panel/assets/{index-ArZCi1rK.js → index-CRlJOWsf.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BqPuyN4p.js → index-CScG8TAY.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B4gCFHtB.js → index-CdL9cQIE.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C47EiQ7d.js → index-Ck35u_0v.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Dn2rNMmv.js → index-CkLZQtsh.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-T5emAyFT.js → index-DFoujkWD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DbFnfyKh.js → index-DFwh0R9h.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CsEk9s04.js → index-DStN9E7o.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DTDpF8MS.js → index-DcozYrXV.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_g5q2hF3.js → index-De5mYp86.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bp_WNm2_.js → index-DfEVOzNb.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-OfMwkFIo.js → index-Dfna8sKa.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BcDeMCUK.js → index-DjbMaBLf.js} +1 -1
  122. package/src/assets/web-panel/assets/index-Dl6H3dxS.js +1 -0
  123. package/src/assets/web-panel/assets/{index-D-te-S9f.js → index-Dpl_SJuk.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DKsWujXP.js → index-DyF9uYzB.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BydBh7oT.js → index-HF3GvATm.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DAcBi6F-.js → index-OJmXGn8n.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CNeLedtU.js → index-ZPdNPhUq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Ds1BfT_C.js → index-djikyGz3.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CbF27GqZ.js → index-gQWWnP3_.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CnhJbWsl.js → index-owVbLJnH.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Lh0e2afo.js → index-tWEDZHzj.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DbjUePgq.js → index-ttTl376q.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-nuZaMsMy.js → index-z5rYvstM.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-C_pMmi75.js → initDefaultProps-B_Uk-wzg.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BvKgS96o.js → motion-ez7H8y8b.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-C-1U4R1I.js → move-CKWUhhhX.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-D6BC968-.js → mtc-parser-DkNl7ocj.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DxOZvcGQ.js → omit-C0uA2ytw.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-LpzmHdkw.js → pickAttrs-CySaWMPl.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-Bm--qom1.js → placementArrow-BWL3-3xo.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CVZZlhJf.js → responsiveObserve-BRy_wJog.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DD1xINGx.js → slide-DmmvSaBD.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-LrjHAy63.js → statusUtils-C_7SYY0K.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BqknYxTe.js → styleChecker-DYgBJGDh.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dljy3diT.js → useFlexGapSupport-nPkoZkT6.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-OedUtPLc.js → useFs-CzpsfP4_.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-Cz-kxbP_.js → usePersonalDataHub-Bo2vvMWl.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-C8ZuTk8W.js → vnode-D-hXAnHb.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CjbgJYXQ.js → zoom-DW7seTGG.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agent.js +39 -14
  152. package/src/commands/background-session.js +12 -1
  153. package/src/commands/permissions.js +24 -1
  154. package/src/gateways/ws/ws-agent-handler.js +26 -1
  155. package/src/gateways/ws/ws-approval-gate.js +131 -0
  156. package/src/index.js +63 -845
  157. package/src/lib/agent-ipc-bus.js +331 -0
  158. package/src/lib/agent-worktree.js +173 -5
  159. package/src/lib/background-agent-supervisor.js +193 -4
  160. package/src/lib/context-source-ledger.js +164 -0
  161. package/src/lib/execution-trace/index.js +10 -0
  162. package/src/lib/execution-trace/metrics-collector.js +218 -0
  163. package/src/lib/execution-trace/trace-context.js +182 -0
  164. package/src/lib/hook-manager.js +17 -0
  165. package/src/lib/hooks-v2-runtime.js +278 -0
  166. package/src/lib/interaction-adapter.js +26 -5
  167. package/src/lib/metrics-collector.js +127 -0
  168. package/src/lib/observability/index.js +33 -0
  169. package/src/lib/observability/otlp-exporter.js +260 -0
  170. package/src/lib/otlp-exporter.js +45 -0
  171. package/src/lib/process-execution-broker/SPAWN_AUDIT_REPORT.md +117 -0
  172. package/src/lib/process-execution-broker/index.js +450 -0
  173. package/src/lib/process-execution-broker/patch-child-process.js +50 -0
  174. package/src/lib/process-execution-broker/spawn-audit.json +3814 -0
  175. package/src/lib/repl-rewind.js +62 -14
  176. package/src/lib/runtime-provenance-ledger.js +190 -0
  177. package/src/lib/session-hooks.js +41 -0
  178. package/src/lib/turn-binding.js +134 -0
  179. package/src/program-base.js +4 -1
  180. package/src/repl/agent-repl.js +77 -3
  181. package/src/repl/repl-turn-binding.js +91 -0
  182. package/src/runtime/headless-runner.js +59 -0
  183. package/src/assets/web-panel/assets/MarkdownRenderer-RQ1HbTY5.js +0 -1
  184. package/src/assets/web-panel/assets/devWarning-Cwuuqc_f.js +0 -1
  185. package/src/assets/web-panel/assets/index-BkTk-zJK.js +0 -1
  186. package/src/assets/web-panel/assets/index-C-BSTEhe.js +0 -1
@@ -6,16 +6,21 @@ import {
6
6
  openSync,
7
7
  readFileSync,
8
8
  readdirSync,
9
+ realpathSync,
9
10
  renameSync,
10
11
  rmSync,
11
12
  statSync,
12
13
  writeFileSync,
13
14
  } from "node:fs";
14
15
  import { randomBytes } from "node:crypto";
15
- import { join } from "node:path";
16
+ import { join, resolve } from "node:path";
16
17
  import { fileURLToPath } from "node:url";
17
18
  import { getHomeDir } from "./paths.js";
18
19
  import { deriveSessionState } from "./session-lifecycle.js";
20
+ import {
21
+ finishAgentWorktree,
22
+ validateAgentWorktree,
23
+ } from "./agent-worktree.js";
19
24
 
20
25
  export const DEFAULT_HEARTBEAT_INTERVAL_MS = 5000;
21
26
  export const DEFAULT_HEARTBEAT_STALE_MS = 120000;
@@ -280,6 +285,20 @@ export function writeBackgroundAgentState(state) {
280
285
  if (Number(current.pinnedAt || 0) > Number(next.pinnedAt || 0)) {
281
286
  next = { ...next, pinned: current.pinned, pinnedAt: current.pinnedAt };
282
287
  }
288
+ // A background worktree is immutable session identity, just like the
289
+ // conversation id. Heartbeat/finalize/rename writers may race with a stale
290
+ // snapshot, but no same-id transition is allowed to move the run back to
291
+ // the main checkout or drop the cleanup metadata.
292
+ if (current.worktreePath) {
293
+ next = {
294
+ ...next,
295
+ cwd: current.worktreePath,
296
+ repoRoot: current.repoRoot,
297
+ worktreePath: current.worktreePath,
298
+ baseSha: current.baseSha,
299
+ branch: current.branch,
300
+ };
301
+ }
283
302
  }
284
303
  const tmp = `${target}.${process.pid}.tmp`;
285
304
  writeFileSync(tmp, JSON.stringify(next, null, 2), { mode: 0o600 });
@@ -500,7 +519,7 @@ export function renameBackgroundAgent(id, title, options = {}) {
500
519
  * conversation session is NOT touched — that's `cc session delete`.
501
520
  */
502
521
  export function removeBackgroundAgent(id, options = {}) {
503
- const state = effectiveBackgroundAgentState(readBackgroundAgentState(id), {
522
+ let state = effectiveBackgroundAgentState(readBackgroundAgentState(id), {
504
523
  now: options.now,
505
524
  heartbeatStaleMs: options.heartbeatStaleMs,
506
525
  });
@@ -513,15 +532,28 @@ export function removeBackgroundAgent(id, options = {}) {
513
532
  }
514
533
  try {
515
534
  stopBackgroundAgent(id);
535
+ state =
536
+ effectiveBackgroundAgentState(readBackgroundAgentState(id), {
537
+ now: options.now,
538
+ heartbeatStaleMs: options.heartbeatStaleMs,
539
+ }) || state;
516
540
  } catch {
517
541
  /* best-effort — the record removal below is the point of rm --force */
518
542
  }
519
543
  }
544
+ const worktree = cleanupBackgroundAgentWorktree(state, {
545
+ keepWorktree: options.keepWorktree === true,
546
+ });
520
547
  rmSync(statePath(id), { force: true });
521
548
  if (options.keepLog !== true) {
522
549
  rmSync(logPath(id), { force: true });
523
550
  }
524
- return { id, removed: true, status: state.status };
551
+ return {
552
+ id,
553
+ removed: true,
554
+ status: state.status,
555
+ ...(worktree ? { worktree } : {}),
556
+ };
525
557
  }
526
558
 
527
559
  /**
@@ -598,6 +630,68 @@ export function buildFollowUpArgv(argv, opts = {}) {
598
630
  return out;
599
631
  }
600
632
 
633
+ function canonicalPath(value) {
634
+ const absolute = resolve(String(value || ""));
635
+ try {
636
+ const real = realpathSync.native
637
+ ? realpathSync.native(absolute)
638
+ : realpathSync(absolute);
639
+ return process.platform === "win32" ? real.toLowerCase() : real;
640
+ } catch {
641
+ return process.platform === "win32" ? absolute.toLowerCase() : absolute;
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Normalize and verify worktree metadata before it enters a background job.
647
+ * The metadata is persisted as top-level state fields for compatibility with
648
+ * status/IDE consumers: repoRoot/worktreePath/baseSha/branch.
649
+ */
650
+ export function normalizeBackgroundWorktree(worktree, cwd) {
651
+ if (!worktree) return null;
652
+ const normalized = {
653
+ repoRoot: resolve(String(worktree.repoRoot || "")),
654
+ worktreePath: resolve(String(worktree.worktreePath || worktree.path || "")),
655
+ baseSha: String(worktree.baseSha || ""),
656
+ branch: String(worktree.branch || ""),
657
+ };
658
+ if (
659
+ !normalized.repoRoot ||
660
+ !normalized.worktreePath ||
661
+ !normalized.baseSha ||
662
+ !normalized.branch
663
+ ) {
664
+ throw new Error(
665
+ "Cannot launch background agent: incomplete worktree metadata",
666
+ );
667
+ }
668
+ if (canonicalPath(cwd) !== canonicalPath(normalized.worktreePath)) {
669
+ throw new Error(
670
+ "Cannot launch background agent: cwd does not match the persisted worktree path",
671
+ );
672
+ }
673
+ const validation = validateAgentWorktree({
674
+ ...normalized,
675
+ path: normalized.worktreePath,
676
+ });
677
+ if (!validation.valid) {
678
+ throw new Error(
679
+ `Cannot launch background agent: worktree metadata is not usable (${validation.reason})`,
680
+ );
681
+ }
682
+ return normalized;
683
+ }
684
+
685
+ function persistedWorktree(state) {
686
+ if (!state?.worktreePath) return null;
687
+ return {
688
+ repoRoot: state.repoRoot,
689
+ worktreePath: state.worktreePath,
690
+ baseSha: state.baseSha,
691
+ branch: state.branch,
692
+ };
693
+ }
694
+
601
695
  /**
602
696
  * Continue a finished/crashed background session as a NEW background session
603
697
  * on the same conversation (`cc daemon resume <id> <prompt>`). The original
@@ -622,14 +716,27 @@ export function resumeBackgroundAgent(id, prompt, options = {}) {
622
716
  }
623
717
  const text = String(prompt || "").trim();
624
718
  if (!text) throw new Error("resume requires a prompt");
719
+ const worktree = persistedWorktree(state);
720
+ if (
721
+ worktree &&
722
+ options.cwd &&
723
+ canonicalPath(options.cwd) !== canonicalPath(worktree.worktreePath)
724
+ ) {
725
+ throw new Error(
726
+ `Background agent ${id} is bound to worktree "${worktree.worktreePath}" and cannot resume in a different cwd`,
727
+ );
728
+ }
625
729
  const argv = ["agent", "--session", state.sessionId, "-p", text];
626
730
  return launchBackgroundAgent({
627
731
  argv,
628
- cwd: options.cwd || state.cwd || process.cwd(),
732
+ // Worktree sessions fail closed: never fall back to process.cwd(), which
733
+ // could silently resume a write task in the main checkout.
734
+ cwd: worktree?.worktreePath || options.cwd || state.cwd || process.cwd(),
629
735
  sessionId: state.sessionId,
630
736
  title: options.title || state.title || text.slice(0, 100),
631
737
  cliEntry: options.cliEntry,
632
738
  followUpArgv: ["agent", "--session", state.sessionId],
739
+ worktree,
633
740
  });
634
741
  }
635
742
 
@@ -667,8 +774,10 @@ export function launchBackgroundAgent({
667
774
  title,
668
775
  cliEntry,
669
776
  followUpArgv,
777
+ worktree,
670
778
  }) {
671
779
  assertUsableCwd(cwd);
780
+ const worktreeState = normalizeBackgroundWorktree(worktree, cwd);
672
781
  const id = createBackgroundAgentId();
673
782
  const dir = backgroundAgentsDir();
674
783
  const jobFile = join(dir, `${id}.job.${process.pid}.json`);
@@ -683,6 +792,7 @@ export function launchBackgroundAgent({
683
792
  title: title || "Background agent",
684
793
  cliEntry: cliEntry || process.argv[1],
685
794
  logFile: logPath(id),
795
+ ...(worktreeState || {}),
686
796
  // Present = interactive attach can start follow-up turns; absent = the
687
797
  // transport rejects prompts (log-only session).
688
798
  ...(Array.isArray(followUpArgv) ? { followUpArgv } : {}),
@@ -706,6 +816,7 @@ export function launchBackgroundAgent({
706
816
  endedAt: null,
707
817
  exitCode: null,
708
818
  logFile: job.logFile,
819
+ ...(worktreeState || {}),
709
820
  };
710
821
  writeBackgroundAgentState(state);
711
822
  let child;
@@ -866,6 +977,84 @@ export function stopBackgroundAgent(id) {
866
977
  return { ...next, stopped: true };
867
978
  }
868
979
 
980
+ function activeWorktreeProcesses(state) {
981
+ const active = [];
982
+ if (
983
+ Number.isInteger(Number(state?.pid)) &&
984
+ Number(state.pid) > 0 &&
985
+ isSameProcess(state.pid, state.startedAt)
986
+ ) {
987
+ active.push("worker");
988
+ }
989
+ if (
990
+ Number.isInteger(Number(state?.agentPid)) &&
991
+ Number(state.agentPid) > 0 &&
992
+ isSameProcess(state.agentPid, state.agentStartedAt)
993
+ ) {
994
+ active.push("agent");
995
+ }
996
+ return active;
997
+ }
998
+
999
+ /**
1000
+ * Explicit background-record cleanup owns the worktree teardown. Normal
1001
+ * completion, kill, lost-worker reconciliation, attach and resume all retain
1002
+ * it so a later resume continues in the exact same isolated checkout.
1003
+ */
1004
+ export function cleanupBackgroundAgentWorktree(state, options = {}) {
1005
+ const worktree = persistedWorktree(state);
1006
+ if (!worktree) return null;
1007
+ if (options.keepWorktree === true) {
1008
+ return {
1009
+ removed: false,
1010
+ kept: true,
1011
+ reason: "kept by explicit request",
1012
+ path: worktree.worktreePath,
1013
+ };
1014
+ }
1015
+
1016
+ const blockers = [];
1017
+ const active = activeWorktreeProcesses(state);
1018
+ if (active.length) blockers.push(`active ${active.join("+")} process`);
1019
+ if (Number(state?.pendingApprovals || 0) > 0) {
1020
+ blockers.push("pending approval");
1021
+ }
1022
+ if (state?.pendingQuestion) blockers.push("pending user input");
1023
+ if (
1024
+ Number(state?.uncertainSideEffects || 0) > 0 ||
1025
+ state?.phase === "uncertain_side_effect" ||
1026
+ state?.phase === "uncertain-side-effect"
1027
+ ) {
1028
+ blockers.push("unfinished or uncertain side effect");
1029
+ }
1030
+ if (blockers.length) {
1031
+ throw new Error(
1032
+ `Refusing to clean background worktree "${worktree.worktreePath}": ${blockers.join(", ")}`,
1033
+ );
1034
+ }
1035
+
1036
+ if (!existsSync(worktree.worktreePath)) {
1037
+ return {
1038
+ removed: false,
1039
+ kept: false,
1040
+ reason: "worktree path already missing",
1041
+ path: worktree.worktreePath,
1042
+ };
1043
+ }
1044
+
1045
+ const result = finishAgentWorktree({
1046
+ ...worktree,
1047
+ path: worktree.worktreePath,
1048
+ });
1049
+ if (!result.removed) {
1050
+ throw new Error(
1051
+ `Refusing to remove background agent record while its worktree is kept (${result.reason}). ` +
1052
+ "Resolve or preserve the work first, or pass --keep-worktree to remove only the record.",
1053
+ );
1054
+ }
1055
+ return { ...result, path: worktree.worktreePath };
1056
+ }
1057
+
869
1058
  export function openBackgroundLogFile(id) {
870
1059
  const fd = openSync(logPath(id), "a", 0o600);
871
1060
  return { fd, close: () => closeSync(fd) };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Context Source Ledger — M4-1: 上下文来源归因系统
3
+ * 对应文档 §2.5
4
+ *
5
+ * 统一追踪每段上下文的来源、工具调用、权限模式、置信度,
6
+ * 让"AI 为什么此刻知道这件事"完全可追溯
7
+ */
8
+
9
+ import crypto from "node:crypto";
10
+
11
+ /**
12
+ * @typedef {Object} ContextEntry
13
+ * @property {string} entryId
14
+ * @property {string} sessionId
15
+ * @property {string} turnId
16
+ * @property {string} sourceType - slash_command|mcp|tool|hook|skill|agent|file|memory|prompt|plugin
17
+ * @property {string} sourceId - tool name, mcp server name, hook id, etc.
18
+ * @property {'system'|'developer'|'plugin'|'user'|'agent'} permissionMode
19
+ * @property {number} confidence - 0-1
20
+ * @property {string} [summary]
21
+ * @property {any} [metadata]
22
+ * @property {number} tokenCount
23
+ * @property {Date} insertedAt
24
+ * @property {string[]} tags
25
+ */
26
+
27
+ class ContextSourceLedger {
28
+ constructor() {
29
+ /** @type {Map<string, ContextEntry>} */
30
+ this._entries = new Map();
31
+ /** @type {Map<string, ContextEntry[]>} turnIndex: turnId -> entries */
32
+ this._byTurn = new Map();
33
+ /** @type {Map<string, ContextEntry[]>} sessionIndex: sessionId -> entries */
34
+ this._bySession = new Map();
35
+ }
36
+
37
+ /**
38
+ * Record a context source entry
39
+ * @param {Partial<ContextEntry>} entry
40
+ * @returns {string} entryId
41
+ */
42
+ record(entry) {
43
+ const entryId = entry.entryId || crypto.randomUUID();
44
+ const full = {
45
+ entryId,
46
+ sessionId: entry.sessionId || "unknown",
47
+ turnId: entry.turnId || "unknown",
48
+ sourceType: entry.sourceType || "unknown",
49
+ sourceId: entry.sourceId || "unknown",
50
+ permissionMode: entry.permissionMode || "agent",
51
+ confidence: entry.confidence ?? 0.8,
52
+ summary: entry.summary,
53
+ metadata: entry.metadata,
54
+ tokenCount: entry.tokenCount || 0,
55
+ insertedAt: new Date(),
56
+ tags: entry.tags || [],
57
+ };
58
+
59
+ this._entries.set(entryId, full);
60
+
61
+ // Index by turn
62
+ if (!this._byTurn.has(full.turnId)) this._byTurn.set(full.turnId, []);
63
+ this._byTurn.get(full.turnId).push(full);
64
+
65
+ // Index by session
66
+ if (!this._bySession.has(full.sessionId)) this._bySession.set(full.sessionId, []);
67
+ this._bySession.get(full.sessionId).push(full);
68
+
69
+ return entryId;
70
+ }
71
+
72
+ /**
73
+ * Get all context entries for a given turn
74
+ * @param {string} turnId
75
+ * @returns {ContextEntry[]}
76
+ */
77
+ getTurnContext(turnId) {
78
+ return this._byTurn.get(turnId) || [];
79
+ }
80
+
81
+ /**
82
+ * Get all context entries for a session
83
+ * @param {string} sessionId
84
+ * @returns {ContextEntry[]}
85
+ */
86
+ getSessionContext(sessionId) {
87
+ return this._bySession.get(sessionId) || [];
88
+ }
89
+
90
+ /**
91
+ * Generate context provenance summary for a turn
92
+ * Shows exactly where each piece of context came from
93
+ * @param {string} turnId
94
+ * @returns {Object} provenance report
95
+ */
96
+ getTurnProvenance(turnId) {
97
+ const entries = this.getTurnContext(turnId);
98
+ const byType = {};
99
+ const byPermission = {};
100
+ const bySource = {};
101
+ let totalTokens = 0;
102
+ let avgConfidence = 0;
103
+
104
+ for (const e of entries) {
105
+ byType[e.sourceType] = (byType[e.sourceType] || 0) + 1;
106
+ byPermission[e.permissionMode] = (byPermission[e.permissionMode] || 0) + 1;
107
+ bySource[e.sourceId] = (bySource[e.sourceId] || 0) + 1;
108
+ totalTokens += e.tokenCount;
109
+ avgConfidence += e.confidence;
110
+ }
111
+
112
+ avgConfidence = entries.length > 0 ? avgConfidence / entries.length : 0;
113
+
114
+ return {
115
+ turnId,
116
+ totalEntries: entries.length,
117
+ totalTokens,
118
+ avgConfidence: Math.round(avgConfidence * 100) / 100,
119
+ byType,
120
+ byPermission,
121
+ bySource,
122
+ entries: entries.map(e => ({
123
+ sourceType: e.sourceType,
124
+ sourceId: e.sourceId,
125
+ permissionMode: e.permissionMode,
126
+ confidence: e.confidence,
127
+ tokenCount: e.tokenCount,
128
+ summary: e.summary,
129
+ tags: e.tags,
130
+ })),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Clear entries for a session
136
+ * @param {string} sessionId
137
+ */
138
+ clearSession(sessionId) {
139
+ const entries = this._bySession.get(sessionId) || [];
140
+ for (const e of entries) {
141
+ this._entries.delete(e.entryId);
142
+ const turnEntries = this._byTurn.get(e.turnId);
143
+ if (turnEntries) {
144
+ const idx = turnEntries.findIndex(x => x.entryId === e.entryId);
145
+ if (idx >= 0) turnEntries.splice(idx, 1);
146
+ }
147
+ }
148
+ this._bySession.delete(sessionId);
149
+ }
150
+
151
+ /** Get stats */
152
+ getStats() {
153
+ return {
154
+ totalEntries: this._entries.size,
155
+ sessionsTracked: this._bySession.size,
156
+ turnsTracked: this._byTurn.size,
157
+ };
158
+ }
159
+ }
160
+
161
+ // Singleton
162
+ const ledger = new ContextSourceLedger();
163
+ export default ledger;
164
+ export { ContextSourceLedger };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * ExecutionTrace - M3 执行追踪统一导出
3
+ *
4
+ * 包含分布式追踪和指标收集功能:
5
+ * - TraceContext: W3C Trace Context 兼容的上下文传播
6
+ * - MetricsCollector: 轻量级性能指标收集
7
+ */
8
+
9
+ export * from "./trace-context.js";
10
+ export * from "./metrics-collector.js";
@@ -0,0 +1,218 @@
1
+ /**
2
+ * MetricsCollector - M3 轻量级指标收集器
3
+ *
4
+ * 收集CLI运行时的性能指标:
5
+ * - 执行计数器
6
+ * - 延迟直方图
7
+ * - 标签化指标
8
+ * - 自动与traceId关联
9
+ */
10
+
11
+ import { AsyncLocalStorage } from "node:async_hooks";
12
+ import { createHash, randomBytes } from "node:crypto";
13
+ import { traceContext } from "./trace-context.js";
14
+
15
+ class MetricsCollector {
16
+ constructor() {
17
+ this._counters = new Map();
18
+ this._histograms = new Map();
19
+ this._gauges = new Map();
20
+ this._flushedAt = Date.now();
21
+ }
22
+
23
+ /**
24
+ * Increment a counter metric
25
+ * @param {string} name - Metric name
26
+ * @param {number} value - Value to increment by (default: 1)
27
+ * @param {Object} labels - Key-value labels
28
+ */
29
+ increment(name, value = 1, labels = {}) {
30
+ const key = this._metricKey(name, labels);
31
+ const current = this._counters.get(key) || 0;
32
+ this._counters.set(key, current + value);
33
+ }
34
+
35
+ /**
36
+ * Record a value in a histogram (latency, sizes, etc.)
37
+ * @param {string} name - Metric name
38
+ * @param {number} value - Value to record
39
+ * @param {Object} labels - Key-value labels
40
+ */
41
+ observe(name, value, labels = {}) {
42
+ const key = this._metricKey(name, labels);
43
+ const data = this._histograms.get(key) || {
44
+ count: 0,
45
+ sum: 0,
46
+ min: Infinity,
47
+ max: -Infinity,
48
+ values: [],
49
+ };
50
+ data.count += 1;
51
+ data.sum += value;
52
+ data.min = Math.min(data.min, value);
53
+ data.max = Math.max(data.max, value);
54
+ // Keep last 1000 values for percentile calculation
55
+ if (data.values.length > 1000) {
56
+ data.values.shift();
57
+ }
58
+ data.values.push(value);
59
+ this._histograms.set(key, data);
60
+ }
61
+
62
+ /**
63
+ * Set a gauge value
64
+ * @param {string} name - Metric name
65
+ * @param {number} value - Gauge value
66
+ * @param {Object} labels - Key-value labels
67
+ */
68
+ setGauge(name, value, labels = {}) {
69
+ const key = this._metricKey(name, labels);
70
+ this._gauges.set(key, value);
71
+ }
72
+
73
+ /**
74
+ * Time an async function and record latency
75
+ * @param {string} name - Metric name
76
+ * @param {Function} fn - Async function to time
77
+ * @param {Object} labels - Key-value labels
78
+ * @returns {Promise<*>} Function result
79
+ */
80
+ async time(name, fn, labels = {}) {
81
+ const start = performance.now();
82
+ try {
83
+ const result = await fn();
84
+ this.observe(name, performance.now() - start, {
85
+ ...labels,
86
+ status: "success",
87
+ });
88
+ this.increment(`${name}_total`, 1, { ...labels, status: "success" });
89
+ return result;
90
+ } catch (err) {
91
+ this.observe(name, performance.now() - start, {
92
+ ...labels,
93
+ status: "error",
94
+ });
95
+ this.increment(`${name}_total`, 1, {
96
+ ...labels,
97
+ status: "error",
98
+ errorType: err.name,
99
+ });
100
+ throw err;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get all collected metrics as OTLP-compatible format
106
+ * @returns {Object} OTLP metrics data
107
+ */
108
+ collect() {
109
+ const metrics = [];
110
+
111
+ // Convert counters
112
+ for (const [key, value] of this._counters) {
113
+ const { name, labels } = this._parseKey(key);
114
+ metrics.push({
115
+ name,
116
+ type: "counter",
117
+ value,
118
+ labels,
119
+ timestamp: Date.now(),
120
+ });
121
+ }
122
+
123
+ // Convert histograms
124
+ for (const [key, data] of this._histograms) {
125
+ const { name, labels } = this._parseKey(key);
126
+ data.values.sort((a, b) => a - b);
127
+ const p50 = data.values[Math.floor(data.values.length * 0.5)];
128
+ const p95 = data.values[Math.floor(data.values.length * 0.95)];
129
+ const p99 = data.values[Math.floor(data.values.length * 0.99)];
130
+ metrics.push({
131
+ name,
132
+ type: "histogram",
133
+ count: data.count,
134
+ sum: data.sum,
135
+ min: data.min,
136
+ max: data.max,
137
+ avg: data.sum / data.count,
138
+ p50,
139
+ p95,
140
+ p99,
141
+ labels,
142
+ timestamp: Date.now(),
143
+ });
144
+ }
145
+
146
+ // Convert gauges
147
+ for (const [key, value] of this._gauges) {
148
+ const { name, labels } = this._parseKey(key);
149
+ metrics.push({
150
+ name,
151
+ type: "gauge",
152
+ value,
153
+ labels,
154
+ timestamp: Date.now(),
155
+ });
156
+ }
157
+
158
+ return {
159
+ resourceMetrics: [
160
+ {
161
+ resource: {
162
+ attributes: [
163
+ {
164
+ key: "service.name",
165
+ value: { stringValue: "chainlesschain-cli" },
166
+ },
167
+ {
168
+ key: "service.version",
169
+ value: { stringValue: "0.10.0-alpha.74" },
170
+ },
171
+ ],
172
+ },
173
+ scopeMetrics: [
174
+ {
175
+ scope: { name: "chainlesschain.cli" },
176
+ metrics,
177
+ },
178
+ ],
179
+ },
180
+ ],
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Reset all metrics
186
+ */
187
+ reset() {
188
+ this._counters.clear();
189
+ this._histograms.clear();
190
+ this._gauges.clear();
191
+ this._flushedAt = Date.now();
192
+ }
193
+
194
+ _metricKey(name, labels) {
195
+ const labelStr = Object.entries(labels)
196
+ .sort(([a], [b]) => a.localeCompare(b))
197
+ .map(([k, v]) => `${k}=${v}`)
198
+ .join(",");
199
+ return labelStr ? `${name}{${labelStr}}` : name;
200
+ }
201
+
202
+ _parseKey(key) {
203
+ const match = key.match(/^([^{]+)(?:\{([^}]+)\})?$/);
204
+ if (!match) return { name: key, labels: {} };
205
+ const [, name, labelStr] = match;
206
+ if (!labelStr) return { name, labels: {} };
207
+ const labels = {};
208
+ for (const pair of labelStr.split(",")) {
209
+ const [k, v] = pair.split("=");
210
+ if (k) labels[k] = v;
211
+ }
212
+ return { name, labels };
213
+ }
214
+ }
215
+
216
+ export { MetricsCollector };
217
+ export const metricsCollector = new MetricsCollector();
218
+ export default metricsCollector;