chainlesschain 0.162.45 → 0.162.47

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 (160) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-D-W6s4eA.js → AIOps-BBjFOl4N.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-nPOyfwP_.js → ActionButton-B7A6lHz3.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DoX0H6wa.js → Analytics-DZgNxdvw.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BYNnmmUE.js → AppLayout-CFbPWVnh.js} +3 -3
  6. package/src/assets/web-panel/assets/{Audit-AjglOteL.js → Audit-Ao9RxevG.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DT38xAKk.js → Backup-B1AGqahI.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-DT1pD4sT.js → BaseInput-M01bA2JM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-Z77BT13i.js → Chat-CC0Gmd9k.js} +6 -6
  10. package/src/assets/web-panel/assets/ChatBubbleRenderer-DKe4yjqq.js +1 -0
  11. package/src/assets/web-panel/assets/{Checkbox-CVOMmTDm.js → Checkbox-CpEqAg5P.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-Bqy11vCf.js → Codegen-9c1H2P_L.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-C3A1d5Wt.js → Col-sH2Obo8q.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-D2q6UNaX.js → Community-DyzjH37r.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-C2701nAm.js → Compact-DsNatLOG.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-SJnaA5oN.js → Compliance-B3ws7qwL.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-BvTYuhJy.js → Cowork-odGfUMsv.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-sJ-9lLUV.js → Cron-bxO6NLDR.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CStGE_K9.js → Crosschain-xHCPQpE9.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-JY0Kx-7a.js → DID-3l7HvzNC.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BiZEZPRN.js → Dashboard-BNzT5YBj.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-DK2BszlZ.js → Dropdown-DZynMYpG.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DtjOYS0Q.js → EmailListRenderer-6HZ2XA9g.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D2EI2B5E.js → FamilyGuardDashboard-DqzqNzRP.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-CebAtHrY.js → Federation-CI7lf1MF.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Bd8WNQLm.js → FormItemContext-D2m3YZ-k.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-tH3zcpN9.js → GenericCardRenderer-Bwcc-9EB.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DARoXdm7.js → Git-C7qN3bA1.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-gcKIyn7g.js → Governance-JfuugS1c.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-Rh4pllPC.js → Inference-BNsHm2IH.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-Cvh73WwA.js → KnowledgeGraph-CTH_FiL1.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-BBZSagwy.js → Logs-CxcfBcmS.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-e5C7xHES.js → Marketplace-B9PU3qlx.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-BzfXvajQ.js → McpTools-BlvQpnvj.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-C3ZgSBrL.js → Memory-Diiig9gp.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-BYVobYJJ.js → MobileBridge-CGfUn4xi.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-Bl8xCXs4.js → MobileProjects-CFvH6A9E.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-BxNytLEb.js → Mtc-CT_1x1Gr.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-BUadScwJ.js → MtcAudit-DPAjU1Qn.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-BWZmAn6M.js → Multisig-nks2HW0C.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-B4eau0Yo.js → NLProgramming-BWd74a91.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-eHe1Jd1n.js → Notes-B342yG3u.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-DFMSI_xu.js → NotificationSettings-C9kjn20B.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-Ctejeurt.js → OrderTableRenderer-DKQ3FPUN.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-M_NYVPks.js → Organization-Ch-qkiyP.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Bi-TzxkX.js → Overflow-DeV865TY.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-vtorIyuM.js → P2P-9pzyAAE1.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-D9I3WUUZ.js → PdhVaultBrowser-oGgQiWpR.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-YFbIKLZw.js → Permissions-MPDfvUva.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-JM8yN0w0.js → PersonalDataHub-Es8GFY4_.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-Cyvwlqpr.js → Pipeline-R10lJHtN.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CIKHwg7m.js → Privacy-8YMg8xrV.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-Bj6-CmbC.js → ProjectInit-B-L3DMBv.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-CBnTXhIt.js → ProjectSettings-pR4l7GCe.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-B3CwPTNd.js → Projects-5vaXw-7d.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-dtUZaXuY.js → Providers-C_E-G6Bh.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-BWzt81Dm.js → QuickAsk-CM-FSYbK.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CToDb_n9.js → Recommend-DcwFptAP.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-B6MCFsXH.js → Reputation-DJKj97w-.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-CipchQUs.js → Row-BfI2mGUm.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-CR3mpmuE.js → RssFeed-D6tzthT0.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-DRvRD7_k.js → Search-CkxIxA5y.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-Bv1bEvfO.js → Security-BeTfFgoX.js} +3 -3
  64. package/src/assets/web-panel/assets/{Services-BLAljjtu.js → Services-BahX89X1.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-BRFNIS9s.js → Skeleton-55na3k97.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-B6dEwkkO.js → Skills-DZ-dxsiR.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-A97O6hQJ.js → Sla-BUTClrI0.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-DNBeqO5l.js → SpeechSettings-C0c2AmSU.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-C61PTeaN.js → SyncSettings-Vr5zzdPz.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-C8q-8JXF.js → Tasks-BrouZA03.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-R-4ZBAPY.js → Templates-DnaU_7nb.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-dRpKvdmN.js → Tenant-1dw4SFBA.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-egMhD-wn.js → Terminal-Pe1vBvOm.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BbXUD3AZ.js → TimelineRenderer-dDxMvAvM.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-CuYIAO1f.js → Tokens-CPJMLjkE.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-bt_5ffBh.js → Trigger-B1IWp6HK.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-rk4LCtlg.js → Trust-iswzFvfA.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-Bgf8zhuS.js → UkeySign-CuMmDUvU.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-B_D_kcq5.js → VideoEditing-BguHSBQ5.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-BuhroOcU.js → Wallet-CW4PFXSw.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DNVp_EWD.js → WebAuthn-YOO0lqjJ.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-C94Nr2Yq.js → WorkflowEditor-A_z3yLuG.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CqvcO82L.js → chat-BSHj9uiJ.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-CiHv7zLX.js → colors-D7gA-kvN.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BbL_7DRy.js → compact-item-ChDt0wUv.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-BcnUkPGe.js → createContext-5UAhDsNU.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-NW6VMT5L.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-bKZM79uJ.js → hasIn-7ysmGHSf.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-C0Dew6UJ.js → index--UCy6rsJ.js} +1 -1
  90. package/src/assets/web-panel/assets/index-A6oY6RQQ.js +1 -0
  91. package/src/assets/web-panel/assets/{index-sdSZZzwh.js → index-B6OX-6N1.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-UrCGcZXp.js → index-BGlolJwU.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-kHUuRAFp.js → index-BLoQ2FkI.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-D5X8cqw-.js → index-BTzZlYgQ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Zxnsweba.js → index-B_xBUmAE.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Dpb_Zh3w.js → index-BcPEgfGF.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-ABAWaz7Y.js → index-BhsGGFa4.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Drdlb5iB.js → index-BoCy4bxa.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CWPLkrPr.js → index-BzPxVTI2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C1b8TGYq.js → index-C4nXq9eB.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Bfcq_Svy.js → index-C62SpQws.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bh9TSxy7.js → index-C93TfkpQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DAayTu3W.js → index-CGVsJplV.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-ByEejvcB.js → index-CHdEHlNY.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CPJ4NWCv.js → index-COwKdUAS.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-2nrwvHPn.js → index-Cl10KB1R.js} +1 -1
  107. package/src/assets/web-panel/assets/index-Cn2g4vqt.js +1 -0
  108. package/src/assets/web-panel/assets/{index-vzVBm4mg.js → index-CylU_ot3.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DNBdA_cQ.js → index-D-pJNNc5.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DYH3St42.js → index-D9R6sJiP.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-B76qMGe0.js → index-DSRnnGez.js} +3 -3
  112. package/src/assets/web-panel/assets/{index-Bd0u4iTg.js → index-Db4hf2WV.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DPHskdVZ.js → index-DcKVERvm.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BlIYy94Y.js → index-DfjMbFZA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-J8Xba8Zt.js → index-Dj3IHF24.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B1StZUmz.js → index-Dq4fd0cB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Cc48TfA8.js → index-DslmMjVo.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Kl_dtIkL.js → index-DttrXREN.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D-jqR16_.js → index-HUiKkQOf.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BySbK7vA.js → index-NtFFgW-x.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-JteeOvFE.js → index-QPBJl4wk.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-AJLQ9KfF.js → index-WM_jYoK8.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Csgtimo8.js → index-ZOmjAajS.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CQonLmOf.js → index-_8xZf-zU.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Kt6y6Hh5.js → index-pk9WOpeN.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-SeP48n4C.js → index-s_MPIulF.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BSzQlPjx.js → index-trVj-RG1.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-Cg2ZQCW2.js → initDefaultProps-BcWdcWH2.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-CVup4XZf.js → motion-D-GC86LC.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-0we4XeEJ.js → move-7N01T4vz.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DNvkWjsk.js → omit-B8_G8lAi.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-DNGWkffo.js → pickAttrs-BeEwgLq8.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-DdMP7Lk2.js → placementArrow-CiQwkkN_.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-CgCiGZ6t.js → responsiveObserve-CtfIP2YM.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-CQxOWXtr.js → slide-DnA-Z3fK.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-BVYop9K6.js → statusUtils-Ckaf7hg9.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BTkUPq_y.js → styleChecker-CE7d6-LQ.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-CKncli3p.js → useFlexGapSupport-BBC2EqTi.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-B7k6cPxd.js → useFs-BWncdesy.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-CiVRczRO.js → usePersonalDataHub-BVJgKdiT.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-CPueAEhr.js → vnode-CdZoGwk8.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BhzuJF_P.js → zoom-8BsyiHWV.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +44 -30
  145. package/src/commands/hub.js +87 -0
  146. package/src/harness/mcp-client.js +86 -1
  147. package/src/lib/auto-checkpoint-default.js +49 -0
  148. package/src/lib/personal-data-hub-wiring.js +44 -2
  149. package/src/lib/repl-completer.js +6 -3
  150. package/src/lib/sensitive-file-guard.js +58 -0
  151. package/src/lib/skill-loader.js +43 -7
  152. package/src/lib/sub-agent-context.js +3 -0
  153. package/src/repl/agent-repl.js +27 -1
  154. package/src/runtime/agent-core.js +55 -0
  155. package/src/runtime/headless-stream.js +36 -4
  156. package/src/runtime/mcp-config.js +19 -1
  157. package/src/assets/web-panel/assets/ChatBubbleRenderer-C1MKMzjB.js +0 -1
  158. package/src/assets/web-panel/assets/devWarning-BSbU0obw.js +0 -1
  159. package/src/assets/web-panel/assets/index-DHgbmZ-Y.js +0 -1
  160. package/src/assets/web-panel/assets/index-pr3q7Jlo.js +0 -1
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * Agentic AI assistant - Claude Code style
3
3
  * chainlesschain agent [--model] [--provider]
4
4
  *
@@ -44,7 +44,7 @@ export function resolveAddDirs(rawDirs) {
44
44
  * thinking `budget_tokens`), or undefined when unset/invalid. Pure; exported for
45
45
  * tests. The companion `thinking` toggle comes from --think/--ultrathink; a
46
46
  * budget without that toggle is a no-op (chatWithTools only reads it when
47
- * thinking is on, and only for legacy models adaptive models use effort).
47
+ * thinking is on, and only for legacy models 鈥?adaptive models use effort).
48
48
  *
49
49
  * @param {string|number} [raw]
50
50
  * @returns {number|undefined}
@@ -85,7 +85,7 @@ export function registerAgentCommand(program) {
85
85
  )
86
86
  .argument(
87
87
  "[task...]",
88
- "Headless task when given (or with -p / piped stdin), runs non-interactively",
88
+ "Headless task 鈥?when given (or with -p / piped stdin), runs non-interactively",
89
89
  )
90
90
  .option("--model <model>", "Model name")
91
91
  .option(
@@ -118,11 +118,11 @@ export function registerAgentCommand(program) {
118
118
  .option("--session <id>", "Resume a previous agent session")
119
119
  .option(
120
120
  "-c, --continue",
121
- "Resume the most recent session (no id needed claude --continue parity)",
121
+ "Resume the most recent session (no id needed 鈥?claude --continue parity)",
122
122
  )
123
123
  .option(
124
124
  "--resume [id]",
125
- "Resume a session by id (no id most recent claude --resume parity)",
125
+ "Resume a session by id (no id 鈫?most recent 鈥?claude --resume parity)",
126
126
  )
127
127
  .option(
128
128
  "--fork-session",
@@ -135,7 +135,11 @@ export function registerAgentCommand(program) {
135
135
  )
136
136
  .option(
137
137
  "--checkpoint",
138
- "Auto-snapshot the work tree before each mutating tool (git repo; cc checkpoint restore to roll back)",
138
+ "Auto-snapshot the work tree before each mutating tool (DEFAULT inside a git repo; cc checkpoint restore to roll back)",
139
+ )
140
+ .option(
141
+ "--no-checkpoint",
142
+ "Disable auto-checkpointing (it is on by default inside git repos)",
139
143
  )
140
144
  .option("--agent-id <id>", "Agent id for scoped memory recall")
141
145
  .option("--recall-limit <n>", "Top-K memories to inject into system prompt")
@@ -150,7 +154,7 @@ export function registerAgentCommand(program) {
150
154
  "--bundle <path>",
151
155
  "Agent bundle directory (chainless-agent.toml + AGENTS.md + skills/ + mcp.json + USER.md)",
152
156
  )
153
- // ── Headless / print mode (Claude-Code `claude -p` parity) ───────────
157
+ // 鈹€鈹€ Headless / print mode (Claude-Code `claude -p` parity) 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
154
158
  .option(
155
159
  "-p, --print [prompt]",
156
160
  "Headless: run one non-interactive turn and exit",
@@ -239,7 +243,18 @@ export function registerAgentCommand(program) {
239
243
  "--settings <file>",
240
244
  "Merge an extra .claude/settings.json-shaped file for this run: permission rules (allow/ask/deny) + native config overrides (model, env)",
241
245
  )
242
- .action(async (task, options) => {
246
+ .action(async (task, options, command) => {
247
+ // Claude-Code parity: auto-checkpoint defaults ON inside a git repo
248
+ // (shadow-commit engine, zero working-tree touch); explicit
249
+ // --checkpoint / --no-checkpoint always wins.
250
+ const { resolveAutoCheckpoint } = await import(
251
+ "../lib/auto-checkpoint-default.js"
252
+ );
253
+ const autoCheckpoint = resolveAutoCheckpoint({
254
+ flagValue: options.checkpoint,
255
+ flagSource: command?.getOptionValueSource?.("checkpoint"),
256
+ cwd: process.cwd(),
257
+ });
243
258
  // `--continue` / `--resume` resolve a session id so the user need not
244
259
  // copy it. Explicit `--session <id>` always wins. `--resume <id>` targets
245
260
  // a specific session; `--continue` or a bare `--resume` pick the most
@@ -260,7 +275,7 @@ export function registerAgentCommand(program) {
260
275
  !process.stdin.isTTY;
261
276
  if (headlessIntent) {
262
277
  // Headless replays the JSONL store only, so resolve "most recent"
263
- // from there a DB-only id would resume into an empty transcript.
278
+ // from there 鈥?a DB-only id would resume into an empty transcript.
264
279
  const { getLastSessionId } =
265
280
  await import("../harness/jsonl-session-store.js");
266
281
  options.session = getLastSessionId();
@@ -283,7 +298,7 @@ export function registerAgentCommand(program) {
283
298
 
284
299
  // --fork-session: branch the resolved session into a NEW id so the
285
300
  // ORIGINAL transcript is preserved (Claude-Code parity). One chokepoint
286
- // before headless / stream / interactive dispatch all read
301
+ // before headless / stream / interactive dispatch 鈥?all read
287
302
  // `options.session`. The copy carries the full history, so the branch
288
303
  // replays end-to-end on a later --resume. Only meaningful when resuming;
289
304
  // a no-op (silent) for a fresh run.
@@ -297,12 +312,12 @@ export function registerAgentCommand(program) {
297
312
  );
298
313
  if (fork.missing) {
299
314
  process.stderr.write(
300
- `--fork-session: no headless transcript for "${options.session}" ` +
315
+ `--fork-session: no headless transcript for "${options.session}" 鈥?` +
301
316
  "nothing to fork; continuing on it.\n",
302
317
  );
303
318
  } else if (fork.forkedFrom) {
304
319
  process.stderr.write(
305
- `Forked session ${fork.forkedFrom} → ${fork.sessionId} (original preserved)\n`,
320
+ `Forked session ${fork.forkedFrom} 鈫?${fork.sessionId} (original preserved)\n`,
306
321
  );
307
322
  options.session = fork.sessionId;
308
323
  }
@@ -332,7 +347,7 @@ export function registerAgentCommand(program) {
332
347
  }
333
348
 
334
349
  // The explicit `--model` the user typed, captured BEFORE the --settings
335
- // block below may default options.model so vision input can tell an
350
+ // block below may default options.model 鈥?so vision input can tell an
336
351
  // explicit model from a settings default.
337
352
  const explicitCliModel = options.model;
338
353
 
@@ -354,10 +369,10 @@ export function registerAgentCommand(program) {
354
369
  }
355
370
  if (!options.model && sc.model) options.model = sc.model;
356
371
  } catch {
357
- // settings overrides are best-effort never block the run
372
+ // settings overrides are best-effort 鈥?never block the run
358
373
  }
359
374
 
360
- // Extra workspace roots (--add-dir) shared by headless + interactive.
375
+ // Extra workspace roots (--add-dir) 鈥?shared by headless + interactive.
361
376
  const additionalDirectories = resolveAddDirs(options.addDir);
362
377
 
363
378
  // --image <path> (repeatable): read into {mediaType, base64} for the
@@ -376,7 +391,7 @@ export function registerAgentCommand(program) {
376
391
  // When an image is attached, default the run to the configured vision LLM
377
392
  // (config.llm provider/baseUrl/apiKey + llm.visionModel | --vision-model |
378
393
  // doubao default) so `cc agent --image foo.png` works without extra flags.
379
- // Explicit --provider/--model/etc. always win; no image unchanged.
394
+ // Explicit --provider/--model/etc. always win; no image 鈫?unchanged.
380
395
  const visionLlm = resolveVisionLlm({
381
396
  hasImage: images.length > 0,
382
397
  flags: {
@@ -391,7 +406,7 @@ export function registerAgentCommand(program) {
391
406
 
392
407
  // Config-default LLM (parity with cc ask/chat): a bare `cc agent` honors
393
408
  // config.json `llm` (provider/model/baseUrl/apiKey) instead of silently
394
- // assuming local ollama this is what makes the editor chat panel work
409
+ // assuming local ollama 鈥?this is what makes the editor chat panel work
395
410
  // against a cloud-configured setup. Explicit --provider wins outright;
396
411
  // the vision resolution above still overrides for --image runs. Applied
397
412
  // AFTER resolveVisionLlm so config defaults don't masquerade as explicit
@@ -405,16 +420,15 @@ export function registerAgentCommand(program) {
405
420
  });
406
421
  }
407
422
 
408
- // --think / --ultrathink options.thinking for the agent loop (Anthropic
409
- // extended thinking; ignored by other providers). --think with no value
410
- // true; --think <level> → that level; --ultrathink wins as "ultra".
423
+ // --think / --ultrathink 鈫?options.thinking for the agent loop (Anthropic
424
+ // extended thinking; ignored by other providers). --think with no value 鈫? // true; --think <level> 鈫?that level; --ultrathink wins as "ultra".
411
425
  const thinking = options.ultrathink
412
426
  ? "ultra"
413
427
  : options.think === true
414
428
  ? true
415
429
  : options.think || undefined;
416
430
  // --thinking-budget <n>: legacy-model thinking budget (no-op without
417
- // --think/--ultrathink and on adaptive models). undefined engine default.
431
+ // --think/--ultrathink and on adaptive models). undefined 鈫?engine default.
418
432
  const thinkingBudget = resolveThinkingBudget(options.thinkingBudget);
419
433
 
420
434
  // --fallback-model: a chatFn that retries once on the backup model when
@@ -431,7 +445,7 @@ export function registerAgentCommand(program) {
431
445
  })
432
446
  : undefined;
433
447
 
434
- // ── Streaming-input mode (--input-format stream-json) ────────────────
448
+ // 鈹€鈹€ Streaming-input mode (--input-format stream-json) 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
435
449
  // A persistent multi-turn conversation driven by NDJSON user events on
436
450
  // stdin; output is always NDJSON. Routed before single-prompt handling
437
451
  // so stdin is consumed as events, not as one prompt.
@@ -489,7 +503,7 @@ export function registerAgentCommand(program) {
489
503
  }
490
504
 
491
505
  // Resolve the headless prompt from: --print value, positional task, or
492
- // piped stdin (in that precedence). Any of them headless mode.
506
+ // piped stdin (in that precedence). Any of them 鈫?headless mode.
493
507
  const positional =
494
508
  Array.isArray(task) && task.length > 0 ? task.join(" ") : "";
495
509
  let prompt = "";
@@ -511,7 +525,7 @@ export function registerAgentCommand(program) {
511
525
 
512
526
  if (prompt) {
513
527
  // Resume requested onto a session with no headless (JSONL) transcript?
514
- // Warn instead of silently starting empty headless resume rebuilds
528
+ // Warn instead of silently starting empty 鈥?headless resume rebuilds
515
529
  // from the JSONL store only (DB-only sessions are not replayable here).
516
530
  const resumeRequested =
517
531
  Boolean(options.continue) || options.resume !== undefined;
@@ -520,7 +534,7 @@ export function registerAgentCommand(program) {
520
534
  await import("../harness/jsonl-session-store.js");
521
535
  if (!sessionExists(options.session)) {
522
536
  process.stderr.write(
523
- `Note: no headless transcript for session "${options.session}" ` +
537
+ `Note: no headless transcript for session "${options.session}" 鈥?` +
524
538
  "starting fresh (headless resume reads JSONL sessions only).\n",
525
539
  );
526
540
  }
@@ -549,9 +563,9 @@ export function registerAgentCommand(program) {
549
563
  allowedTools: parseToolList(options.allowedTools),
550
564
  disallowedTools: parseToolList(options.disallowedTools),
551
565
  additionalDirectories,
552
- autoCheckpoint: options.checkpoint === true,
566
+ autoCheckpoint,
553
567
  maxTurns,
554
- // commander maps --no-file-refs options.fileRefs === false
568
+ // commander maps --no-file-refs 鈫?options.fileRefs === false
555
569
  expandFileRefs: options.fileRefs !== false,
556
570
  // --system-prompt / --append-system-prompt (literal or @file)
557
571
  systemPrompt: resolvePromptText(options.systemPrompt, {
@@ -582,7 +596,7 @@ export function registerAgentCommand(program) {
582
596
  chatFn: fallbackChatFn,
583
597
  };
584
598
 
585
- // --json-schema: structured output wrap the runner with capture +
599
+ // --json-schema: structured output 鈥?wrap the runner with capture +
586
600
  // validate + retry (json-schema-output.js); prints only the
587
601
  // validated JSON. Incompatible with stream-json (event stream and a
588
602
  // single JSON contract don't mix).
@@ -621,7 +635,7 @@ export function registerAgentCommand(program) {
621
635
  }
622
636
 
623
637
  // Reached only for an interactive session, where --image has no turn to
624
- // attach to warn instead of silently dropping the attachment.
638
+ // attach to 鈥?warn instead of silently dropping the attachment.
625
639
  if (images.length) {
626
640
  process.stderr.write(
627
641
  "--image is only used in headless mode (-p / a task / piped stdin); ignoring for the interactive session.\n",
@@ -644,7 +658,7 @@ export function registerAgentCommand(program) {
644
658
  parkOnExit: options.parkOnExit, // false when --no-park-on-exit
645
659
  bundlePath: options.bundle || null,
646
660
  additionalDirectories,
647
- autoCheckpoint: options.checkpoint === true,
661
+ autoCheckpoint,
648
662
  // --system-prompt / --append-system-prompt (literal or @file) also
649
663
  // apply to interactive sessions, composed in startAgentRepl.
650
664
  systemPrompt: resolvePromptText(options.systemPrompt, {
@@ -1732,6 +1732,65 @@ async function cmdDouyinAdbSync(options) {
1732
1732
  }
1733
1733
  }
1734
1734
 
1735
+ async function cmdDouyinWatchSync(options) {
1736
+ try {
1737
+ const hub = await (options._getHub || getHub)();
1738
+ const result = await hub.douyinWatchSync({
1739
+ uid: options.uid,
1740
+ stagingDir: options.stagingDir,
1741
+ displayName: options.displayName,
1742
+ limit: parsePositiveInt(options.limit),
1743
+ resolveTitles: !!options.resolveTitles,
1744
+ titleLimit: parsePositiveInt(options.titleLimit),
1745
+ });
1746
+ if (options.json) {
1747
+ printJson(result);
1748
+ return;
1749
+ }
1750
+ if (!result.ok) {
1751
+ logger.log(chalk.red(`✗ douyin-watch-sync failed: ${result.reason}`));
1752
+ logger.log(chalk.gray(` ${result.message || ""}`));
1753
+ if (result.reason === "DOUYIN_NO_ROOT") {
1754
+ logger.log(
1755
+ chalk.gray(
1756
+ " Phone needs Magisk root — Douyin release APK isn't debuggable",
1757
+ ),
1758
+ );
1759
+ } else if (result.reason === "DOUYIN_VIDEO_RECORD_MISSING") {
1760
+ logger.log(
1761
+ chalk.gray(
1762
+ " Open Douyin + watch a few videos to populate video_record.db, then retry",
1763
+ ),
1764
+ );
1765
+ } else if (result.reason === "BRIDGE_UNAVAILABLE") {
1766
+ logger.log(
1767
+ chalk.gray(
1768
+ " Install Android Platform Tools or set ADB_PATH=/path/to/adb",
1769
+ ),
1770
+ );
1771
+ }
1772
+ process.exitCode = 1;
1773
+ return;
1774
+ }
1775
+ const report = result.report || {};
1776
+ const dy = report.douyin || {};
1777
+ const counts = dy.eventCounts || {};
1778
+ logger.log(chalk.green(`✓ douyin-watch-sync succeeded`));
1779
+ logger.log(` uid: ${chalk.cyan(dy.uid || "?")}`);
1780
+ logger.log(` watched: ${counts.history || 0}`);
1781
+ if (options.resolveTitles) {
1782
+ logger.log(` titles: ${dy.titlesResolved || 0} resolved`);
1783
+ }
1784
+ logger.log(` status: ${report.status || "?"}`);
1785
+ logger.log(` rawCount: ${report.rawCount || 0}`);
1786
+ if (dy.cleanupFailed) {
1787
+ logger.log(chalk.gray(` (note: staging cleanup failed — non-fatal)`));
1788
+ }
1789
+ } catch (err) {
1790
+ fail(null, err, options.json);
1791
+ }
1792
+ }
1793
+
1735
1794
  /**
1736
1795
  * Phase 1e — `cc hub bilibili-adb-doctor`
1737
1796
  *
@@ -2309,6 +2368,33 @@ export function registerHubCommand(program) {
2309
2368
  .option("--json", "Output JSON")
2310
2369
  .action(cmdDouyinAdbSync);
2311
2370
 
2371
+ // Douyin watch-history (video_record.db, plaintext — no X-Bogus / no SQLCipher)
2372
+ hub
2373
+ .command("douyin-watch-sync")
2374
+ .description(
2375
+ "Douyin 观看历史 C 路径: pull video_record.db via ADB from the user's Android Douyin App (com.ss.android.ugc.aweme), read record_<uid> (aid + view_time_timestamp + enter_from), ingest as `history` (BROWSE) events. Plaintext — no X-Bogus signing, no SQLCipher. Needs rooted Android + Douyin App with watch history. Add --resolve-titles to enrich each event with the video's desc/author/duration via the web detail endpoint (also no signing).",
2376
+ )
2377
+ .option(
2378
+ "--uid <id>",
2379
+ "Douyin uid to disambiguate multiple accounts (default: largest record_<uid> table)",
2380
+ )
2381
+ .option("--limit <n>", "Cap watch records (default 2000)")
2382
+ .option(
2383
+ "--resolve-titles",
2384
+ "Resolve aweme ids → desc/author/duration via the web detail endpoint (no signing) so events show WHAT was watched",
2385
+ )
2386
+ .option(
2387
+ "--title-limit <n>",
2388
+ "Cap unique videos to title-resolve (default 60; dedup'd, ~200ms/call)",
2389
+ )
2390
+ .option("--display-name <s>", "Account displayName for the snapshot")
2391
+ .option(
2392
+ "--staging-dir <path>",
2393
+ "Custom dir for the temp snapshot JSON (default os.tmpdir())",
2394
+ )
2395
+ .option("--json", "Output JSON")
2396
+ .action(cmdDouyinWatchSync);
2397
+
2312
2398
  hub
2313
2399
  .command("rederive")
2314
2400
  .description(
@@ -2555,6 +2641,7 @@ export const _internal = {
2555
2641
  cmdBilibiliAdbSync,
2556
2642
  cmdBilibiliAdbDoctor,
2557
2643
  cmdDouyinAdbSync,
2644
+ cmdDouyinWatchSync,
2558
2645
  cmdWeiboAdbSync,
2559
2646
  cmdXhsAdbSync,
2560
2647
  cmdToutiaoAdbSync,
@@ -70,6 +70,23 @@ export function isHttpTransport(transportKind) {
70
70
  );
71
71
  }
72
72
 
73
+ /**
74
+ * Heuristic: does this error look like the server went away (vs. the tool
75
+ * itself failing)? Used to gate reconnect-and-retry for servers that have a
76
+ * registered reconnector (e.g. the IDE bridge after a window reload, which
77
+ * comes back on a NEW port with a NEW token).
78
+ *
79
+ * Covers: fetch-level network failures, auth rejection after a token
80
+ * rotation (401/403), an unknown session on a restarted server (404), and
81
+ * this client's own "server gone" states.
82
+ */
83
+ export function isLikelyConnectionError(err) {
84
+ const msg = String((err && err.message) || err || "");
85
+ return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|network error|HTTP 40[134]\b|not connected|not found|not available/i.test(
86
+ msg,
87
+ );
88
+ }
89
+
73
90
  /**
74
91
  * MCP Client — manages connections to MCP servers.
75
92
  */
@@ -78,6 +95,23 @@ export class MCPClient extends EventEmitter {
78
95
  super();
79
96
  this.servers = new Map(); // name → { process, state, tools, resources, config }
80
97
  this._nextId = 1;
98
+ this._reconnectors = new Map(); // name → async () => config|null
99
+ this._reconnecting = new Map(); // name → in-flight reconnect promise
100
+ }
101
+
102
+ /**
103
+ * Register a reconnector for a server: an async function that returns a
104
+ * FRESH connection config (or null when the server can't be found anymore).
105
+ * When a `callTool` on that server fails with a connection-shaped error,
106
+ * the client re-resolves the config, reconnects, and retries the call once.
107
+ *
108
+ * Used by the IDE bridge: a window reload / extension update restarts the
109
+ * editor's MCP server on a new port with a new token, so the original
110
+ * config is permanently dead but a lockfile re-scan finds the new one.
111
+ */
112
+ setReconnector(name, fn) {
113
+ if (typeof fn === "function") this._reconnectors.set(name, fn);
114
+ else this._reconnectors.delete(name);
81
115
  }
82
116
 
83
117
  /**
@@ -286,12 +320,31 @@ export class MCPClient extends EventEmitter {
286
320
  }
287
321
 
288
322
  /**
289
- * Call a tool on a specific server.
323
+ * Call a tool on a specific server. If the server has a registered
324
+ * reconnector and the call fails with a connection-shaped error (server
325
+ * restarted / token rotated / entry dropped), re-resolve the config,
326
+ * reconnect, and retry the call exactly once.
290
327
  * @param {string} serverName - Server name
291
328
  * @param {string} toolName - Tool name
292
329
  * @param {object} args - Tool arguments
293
330
  */
294
331
  async callTool(serverName, toolName, args = {}) {
332
+ try {
333
+ return await this._callToolOnce(serverName, toolName, args);
334
+ } catch (err) {
335
+ if (
336
+ !this._reconnectors.has(serverName) ||
337
+ !isLikelyConnectionError(err)
338
+ ) {
339
+ throw err;
340
+ }
341
+ const reconnected = await this._tryReconnect(serverName);
342
+ if (!reconnected) throw err;
343
+ return await this._callToolOnce(serverName, toolName, args);
344
+ }
345
+ }
346
+
347
+ async _callToolOnce(serverName, toolName, args) {
295
348
  const entry = this.servers.get(serverName);
296
349
  if (!entry) throw new Error(`Server "${serverName}" not found`);
297
350
  if (entry.state !== ServerState.CONNECTED) {
@@ -306,6 +359,38 @@ export class MCPClient extends EventEmitter {
306
359
  return result;
307
360
  }
308
361
 
362
+ /**
363
+ * Re-resolve a server's config via its reconnector and reconnect.
364
+ * Single-flight per server: concurrent failing calls share one attempt
365
+ * (the IDE context injector fires getSelection + getOpenEditors in
366
+ * parallel — a double connect would throw "already connected").
367
+ * Resolves true on success, false on any failure (original error wins).
368
+ */
369
+ _tryReconnect(name) {
370
+ const inFlight = this._reconnecting.get(name);
371
+ if (inFlight) return inFlight;
372
+ const p = (async () => {
373
+ try {
374
+ const fresh = await this._reconnectors.get(name)();
375
+ if (!fresh) return false;
376
+ try {
377
+ await this.disconnect(name);
378
+ } catch {
379
+ // entry may already be gone — connect() below is what matters
380
+ }
381
+ await this.connect(name, fresh);
382
+ this.emit("server-reconnected", { name, url: fresh.url || null });
383
+ return true;
384
+ } catch {
385
+ return false;
386
+ } finally {
387
+ this._reconnecting.delete(name);
388
+ }
389
+ })();
390
+ this._reconnecting.set(name, p);
391
+ return p;
392
+ }
393
+
309
394
  /**
310
395
  * List resources from a specific server or all servers. Each resource is
311
396
  * annotated with its owning `server` (mirrors `listTools`).
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Auto-checkpoint default resolution — Claude-Code parity: checkpoints are ON
3
+ * by default inside a git repository (the shadow-commit engine never touches
4
+ * the working tree or real index, so the only cost is a plumbing commit per
5
+ * mutating tool), OFF elsewhere (the copy-based fallback writes real files
6
+ * under the user home — too surprising as a silent default).
7
+ *
8
+ * Explicit flags always win: `--checkpoint` forces on anywhere (copy engine
9
+ * included), `--no-checkpoint` forces off.
10
+ */
11
+
12
+ import fsDefault from "fs";
13
+ import pathDefault from "path";
14
+
15
+ export const _deps = { fs: fsDefault, path: pathDefault };
16
+
17
+ /** Walk up from cwd looking for a `.git` marker. */
18
+ export function isInsideGitRepo(cwd, deps = _deps) {
19
+ let dir = deps.path.resolve(cwd || ".");
20
+ for (;;) {
21
+ try {
22
+ if (deps.fs.existsSync(deps.path.join(dir, ".git"))) return true;
23
+ } catch {
24
+ /* keep walking */
25
+ }
26
+ const parent = deps.path.dirname(dir);
27
+ if (parent === dir) return false;
28
+ dir = parent;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * @param {object} opts
34
+ * @param {boolean} [opts.flagValue] commander's options.checkpoint
35
+ * @param {string} [opts.flagSource] commander getOptionValueSource("checkpoint")
36
+ * ("cli" when --checkpoint/--no-checkpoint given)
37
+ * @param {string} [opts.cwd]
38
+ * @param {object} [opts.deps]
39
+ * @returns {boolean}
40
+ */
41
+ export function resolveAutoCheckpoint({
42
+ flagValue,
43
+ flagSource,
44
+ cwd = process.cwd(),
45
+ deps = _deps,
46
+ } = {}) {
47
+ if (flagSource === "cli") return flagValue === true;
48
+ return isInsideGitRepo(cwd, deps);
49
+ }
@@ -363,7 +363,7 @@ async function initHub() {
363
363
  // Phase 2a: register `douyin.pull-im-db` extension so the
364
364
  // douyinAdbSync hub method can pull <uid>_im.db cohort from the
365
365
  // user's Android Douyin App.
366
- const { createDouyinDbExtension } =
366
+ const { createDouyinDbExtension, createDouyinWatchExtension } =
367
367
  await import("@chainlesschain/personal-data-hub/adapters/social-douyin-adb");
368
368
  // Phase 3a: register `weibo.cookies` extension for the Weibo
369
369
  // C-path collector (m.weibo.cn cookies + 4 HTTP endpoints).
@@ -377,7 +377,7 @@ async function initHub() {
377
377
  // C-path collector (www.toutiao.com cookies + 4 endpoints, 3 of
378
378
  // which need _signature signing — CLI context falls back to -99
379
379
  // short-circuit, desktop wiring upgrades via ToutiaoSignBridge).
380
- const { createToutiaoCookiesExtension } =
380
+ const { createToutiaoCookiesExtension, createToutiaoAccountExtension } =
381
381
  await import("@chainlesschain/personal-data-hub/adapters/social-toutiao-adb");
382
382
  // Phase 6d: register `kuaishou.cookies` extension. Profile from
383
383
  // cookie's api_ph payload (no HTTP); 3 GraphQL POST endpoints need
@@ -389,9 +389,11 @@ async function initHub() {
389
389
  extensions: {
390
390
  "bilibili.cookies": createBilibiliCookiesExtension(),
391
391
  "douyin.pull-im-db": createDouyinDbExtension(),
392
+ "douyin.watch-history": createDouyinWatchExtension(),
392
393
  "weibo.cookies": createWeiboCookiesExtension(),
393
394
  "xhs.cookies": createXhsCookiesExtension(),
394
395
  "toutiao.cookies": createToutiaoCookiesExtension(),
396
+ "toutiao.account": createToutiaoAccountExtension(),
395
397
  "kuaishou.cookies": createKuaishouCookiesExtension(),
396
398
  },
397
399
  });
@@ -1037,6 +1039,46 @@ async function initHub() {
1037
1039
  }
1038
1040
  },
1039
1041
 
1042
+ // ─── Douyin watch-history (video_record.db, plaintext, no X-Bogus) ───
1043
+ // Pulls video_record.db via the douyin.watch-history extension → reads
1044
+ // record_<uid> (aid + view_time_timestamp + enter_from) → history events
1045
+ // → syncAdapter("social-douyin") snapshot mode. Distinct from
1046
+ // douyinAdbSync (IM db). No SQLCipher, no signing.
1047
+ async douyinWatchSync(opts = {}) {
1048
+ if (!hostAdbBridge) {
1049
+ return {
1050
+ ok: false,
1051
+ reason: "BRIDGE_UNAVAILABLE",
1052
+ message:
1053
+ "host-adb-bridge failed to initialize at hub boot — check `adb` is on PATH or set ADB_PATH env var",
1054
+ };
1055
+ }
1056
+ let collector;
1057
+ try {
1058
+ const mod =
1059
+ await import("@chainlesschain/personal-data-hub/adapters/social-douyin-adb");
1060
+ collector = mod.default ? mod.default : mod;
1061
+ } catch (err) {
1062
+ return {
1063
+ ok: false,
1064
+ reason: "MODULE_LOAD_FAILED",
1065
+ message: err && err.message ? err.message : String(err),
1066
+ };
1067
+ }
1068
+ try {
1069
+ const report = await collector.collectWatchHistoryAndSync(
1070
+ hostAdbBridge,
1071
+ registry,
1072
+ opts,
1073
+ );
1074
+ return { ok: true, report };
1075
+ } catch (err) {
1076
+ const msg = err && err.message ? err.message : String(err);
1077
+ const m = msg.match(/^(DOUYIN_[A-Z_]+)/);
1078
+ return { ok: false, reason: m ? m[1] : "SYNC_FAILED", message: msg };
1079
+ }
1080
+ },
1081
+
1040
1082
  // ─── Phase 3a — Weibo C 路径 one-shot sync ──────────────────────────
1041
1083
  //
1042
1084
  // Pulls m.weibo.cn cookies via the weibo.cookies extension → fetchUid
@@ -86,7 +86,10 @@ export function fileCandidates(prefix, { cwd = process.cwd(), deps } = {}) {
86
86
  * slashCommands?: string[], deps? }
87
87
  */
88
88
  export function makeAtCompleter(opts = {}) {
89
- const cwd = opts.cwd || process.cwd();
89
+ // Lazy when not pinned: `/cd` mid-session moves process.cwd() and the
90
+ // completer must follow (explicit opts.cwd stays static for tests).
91
+ const baseCwd = opts.cwd || null;
92
+ const getCwd = () => baseCwd || process.cwd();
90
93
  const getIde = opts.getIdeOpenFiles || null;
91
94
  const now = opts.deps?.now || Date.now;
92
95
  let ideFiles = [];
@@ -105,7 +108,7 @@ export function makeAtCompleter(opts = {}) {
105
108
  ? files
106
109
  .filter((f) => typeof f === "string" && f.length > 0)
107
110
  .map((f) => {
108
- const rel = path.relative(cwd, f);
111
+ const rel = path.relative(getCwd(), f);
109
112
  // Keep workspace files relative (the natural @ref form);
110
113
  // out-of-workspace files keep their absolute path. On
111
114
  // Windows a cross-drive relative() returns an *absolute*
@@ -145,7 +148,7 @@ export function makeAtCompleter(opts = {}) {
145
148
  refreshIde(); // async top-up for the NEXT tab; this one uses the cache
146
149
  const norm = fwd(at.prefix).toLowerCase();
147
150
  const fromIde = ideFiles.filter((f) => f.toLowerCase().startsWith(norm));
148
- const fromFs = fileCandidates(at.prefix, { cwd, deps: opts.deps });
151
+ const fromFs = fileCandidates(at.prefix, { cwd: getCwd(), deps: opts.deps });
149
152
  const merged = [...new Set([...fromIde, ...fromFs])].slice(
150
153
  0,
151
154
  MAX_CANDIDATES,