chainlesschain 0.162.154 → 0.162.156

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 (205) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CIpoXyVI.js → AIOps-DjBXlypQ.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Ch_QXIPk.js → ActionButton-Cv9er9bp.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CvaBYShv.js → Analytics-CLKDjSNs.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +9 -0
  8. package/src/assets/web-panel/assets/{Audit-Bng8eHCW.js → Audit-CXafD178.js} +1 -1
  9. package/src/assets/web-panel/assets/BackgroundAgents-CP5c-GSd.js +1 -0
  10. package/src/assets/web-panel/assets/BackgroundAgents-Dq2340Kl.css +1 -0
  11. package/src/assets/web-panel/assets/{Backup-DvBs8-lm.js → Backup-BbYLQfyt.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-DKwajm0g.js → BaseInput-Bqe0MwZL.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-BoGpZ3uO.js → Chat-jNA_yUPH.js} +5 -5
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-mDQl_PXO.js → ChatBubbleRenderer-BOFLF01s.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-BMJEmxFQ.js → Checkbox-Dlzz-zD1.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-Bsoag5CV.js → Codegen-BAsDQjGf.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-CDjH9-sp.js → Col-Ju0HrAuS.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-Cg2QHPQ6.js → Community-DynJd43Z.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-BU2BaqP-.js → Compact-CabbZVww.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-D6ZYMCqZ.js → Compliance-DW4BmAoh.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DaZ8VW-I.js → Cowork-Bk1S_7X-.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-DQY1RH9v.js → Cron-By4sh_NM.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-BVR28vk9.js → Crosschain-DdZdbS1C.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-N44KvTP-.js → DID-CkL-sQyS.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Bjhlx6wS.js → Dashboard-g_mOpumg.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-CpLYxpy1.js → Dropdown-DtHyE7Ao.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-srEITSPI.js → EmailListRenderer-Kpvov5Wr.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-juOC_fX2.js → FamilyGuardDashboard-C4kZoePR.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-nRaQtaXW.js → Federation-BAwWoK2N.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DflhTj09.js → FormItemContext-DeI0i5f8.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-DulI_3NI.js → GenericCardRenderer-CkcKRtos.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-BxOPefXn.js → Git-kX6jjI9y.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-C6G_Wnwi.js → Governance-BXJCBm6x.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-BnFYa2BF.js → Inference-ujD2JSKo.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-TqQLEabm.js → KnowledgeGraph-3Dnuf1Wh.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-BH4AVh7s.js → Logs-lVAZCtUB.js} +2 -2
  37. package/src/assets/web-panel/assets/{Marketplace-DoWLTaEE.js → Marketplace-qnzNPm-A.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D6yC7OG_.js → McpTools-CqjzefRU.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-DLCeHj21.js → Memory-BFiPAxk6.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-BZBE0GRl.js → MobileBridge-D0O43VwF.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DJA15G1J.js → MobileProjects-B3now48t.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CyTKJDHn.js → Mtc-DybhtaCS.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-DZKjgEVc.js → MtcAudit-D7MmY4F0.js} +5 -5
  44. package/src/assets/web-panel/assets/{Multisig-2rMPNhSO.js → Multisig-wZvGJnB8.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-VeMk4Fx-.js → NLProgramming-BNPpgfvn.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-jr2GHMhh.js → Notes-B6FS3KBj.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-WN9VZyFb.js → NotificationSettings-CK0yla9U.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-DdjVHuzT.js → Organization-geqgwN1T.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-l8vBsGGq.js → Overflow-BWJYZFbC.js} +1 -1
  51. package/src/assets/web-panel/assets/{OverrideContext-BlgL9440.js → OverrideContext-C0w0P4A3.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-CVQDHHwq.js → P2P-DnU1505F.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-CTsNh8qE.js → PdhVaultBrowser-BGY74KnC.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-DLW87uyK.js → Permissions-CgArrWtQ.js} +3 -3
  55. package/src/assets/web-panel/assets/{PersonalDataHub-N8OvASTx.js → PersonalDataHub-C2vYE61a.js} +2 -2
  56. package/src/assets/web-panel/assets/{Pipeline-BPZmdlz2.js → Pipeline-CBYLI4yW.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-MFZw_GNh.js → Privacy-B-PdW9aN.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-zW0pEsfU.js → ProjectInit-CuWQegbV.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CppVHr_o.js → ProjectSettings-4d32m_cS.js} +2 -2
  60. package/src/assets/web-panel/assets/{Projects-BTglkJO2.js → Projects-VLA7ttwd.js} +1 -1
  61. package/src/assets/web-panel/assets/{Providers-CPHZ6JKT.js → Providers-B_Izb_oa.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-DO4klNZ2.js → QrScannerModal-C6B_ndXV.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-B37TTqUa.js → QuickAsk-C6tavL-y.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-n7KIfK7y.js → Recommend-CF-LaZ6x.js} +1 -1
  65. package/src/assets/web-panel/assets/{RemoteSession-SjVoS9mm.js → RemoteSession-DBqTTQ9_.js} +2 -2
  66. package/src/assets/web-panel/assets/{Reputation-Dt6RTeHi.js → Reputation-B01FALUi.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-Dty6jz5V.js → Row-DO0fd48A.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-DDLhqw5j.js → RssFeed-BV5LDzFR.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-DrYbXi-W.js → Search-CTti4_MP.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Fc9Z6fbB.js → Security-DwSrWRCw.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BAhPgjSM.js → Services-BEf1QyM4.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-8JF7EPfE.js → Skeleton-LZXTpovy.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DL39dY0t.js → Skills-SKgc0Zug.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-CSvjcATc.js → Sla-C_QVOtkN.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-Demx7Gnx.js → SpeechSettings-v5TW2V-X.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-CKLnRWJr.js → SyncSettings-CQPjHdqU.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-DCd-uYcI.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-AjQI5rxD.js → Templates-62VDIjtj.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-dbtEh1yR.js → Tenant-BnFQ0pNy.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-DX-9jjfk.js → Terminal-D3v0MGSj.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C11EDZRt.js → TimelineRenderer-BI1Ka-cu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-GiA9fiZn.js → Tokens-BwX1jXYP.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BPcnclJn.js → Trigger-CSA8TqNx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-C-DVY8nq.js → Trust-Bn3IjCx7.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DxodSwME.js → UkeySign-pUbOAqNi.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-D_WV0O8O.js → VideoEditing-Db2RercF.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-DSeNlVSU.js → Wallet-CpR1BVAl.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-Bwuuvpz1.js → WebAuthn-Yxtkn7Zp.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-8M96EU1N.js → WorkflowEditor-CYLe9a2E.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-D5S6SmLm.js → chat-gsOGdak0.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-DVfhbsdP.js → collapseMotion-CqM1QOwM.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-5fbnGkAP.js → colors-D9fN299a.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-DaSuuMlW.js → compact-item-vkd7s-ay.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-BNOHcXAF.js → createContext-C7YkbMyy.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-CL0F2SnX.js → echarts-C61Vn3mP.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-DONwZUra.js → hasIn-ig7reRSU.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-BsDmGpcT.js → icons-DEmcnxBf.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BJp4kRjw.js → index-9TxdMYju.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-MOgO5Yvx.js → index-B7QRpKif.js} +1 -1
  101. package/src/assets/web-panel/assets/index-BEibanjg.js +1 -0
  102. package/src/assets/web-panel/assets/{index-CAYmc5Nh.js → index-BHHN_KTS.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BcWMZqb4.js → index-BMw48d19.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B_yDD9Lm.js → index-BYe9HRSt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-xo2f4DZR.js → index-BfpVcXKa.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DbdQBn4n.js → index-BhftELHI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Dd7xwGcD.js → index-BkP17Z95.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-RkInlwV0.js → index-BpGlLbRv.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DM3Ug61e.js → index-Bt-9ZhIu.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CrkCFohi.js → index-BvDEukXe.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-_6S41ikV.js → index-C7AY39sg.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-uR5KTNZA.js → index-C8HZMygt.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-5_fj5gVd.js → index-CAkz1f1r.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BLzMuz38.js → index-CDWTycrg.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BEO5f7Vf.js → index-CGzGe5tv.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CEzd5m5a.js → index-Co2NYyew.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BcqOK7Lv.js → index-CspGymmg.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DP2-3A_j.js → index-D2yhtk6a.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-LRIt-AFl.js → index-D6s_kbjt.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B4L3gsNA.js → index-DFM1oNe1.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-KwV9g_cf.js → index-DFfxF7Vt.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BK2N5yVW.js → index-DPguq3X7.js} +4 -4
  123. package/src/assets/web-panel/assets/{index-Cf2OCa8q.js → index-DQ8U7kzC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D7gs_3Ck.js → index-DZAxCnjS.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-PsMErAB1.js → index-DhdXbsXX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CTZeOnzO.js → index-DuwbKHoS.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Dm5A-MN5.js → index-DvCJXmrx.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-vYtprlNs.js → index-Dy_TKZ9h.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DHx2lhHW.js → index-FzGr6CVk.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cn5LtyIH.js → index-PkH39qsp.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CmUOjyL6.js → index-SDrcQXO1.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CztESwB4.js → index-W52sHRdr.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CSfYJqXt.js → index-bVbgYBhv.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BrAJSu-v.js → index-gj8kxcAi.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CLXf-8ek.js → index-gpurplyq.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-ClGqO-2X.js → index-nX8YE6cj.js} +1 -1
  137. package/src/assets/web-panel/assets/index-z_25tU3p.js +1 -0
  138. package/src/assets/web-panel/assets/{initDefaultProps-CxwLynp9.js → initDefaultProps-BrgfVRg9.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-XbPvGJbW.js → motion-D_3xmwac.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-D1u5pcgS.js → move-DDDJdlxw.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BDxhTBMH.js → mtc-parser-BK2uEWwn.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-EtIFGg5k.js → omit-BPHKbDcf.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-D33sWb0l.js → pickAttrs-LNnTscuz.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-BwFRzaGo.js → placementArrow-CvmTZll5.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-CBk2WqvQ.js → responsiveObserve-eBj7qi2J.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-B52HtrKn.js → slide-D68hHkrM.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-D4qxqWnH.js → statusUtils-daTlaeo0.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-BxaCFlOL.js → styleChecker-DtyKMtHB.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-ekFwFs9h.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-C6VAU1_6.js → useFs-jRaSEoF4.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Dc9sO2d7.js → useMergedState-DXbJa_zE.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-rEG6oXG8.js → usePersonalDataHub-DD5nGzjA.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-CAeFRzPf.js → useRefs-BrCfMLMc.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-LwExUYDO.js → useState-CnxtbLoC.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BMxUs6UX.js → vendor-Bn1JzzAn.js} +1 -1
  156. package/src/assets/web-panel/assets/{vnode-DOTafkEi.js → vnode-KcgLUiom.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-BuwwN5K2.js → zoom-Cyk2PQ-s.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/command-manifest.json +50 -1
  160. package/src/commands/agenda.js +228 -0
  161. package/src/commands/agent.js +23 -2
  162. package/src/commands/agents.js +62 -1
  163. package/src/commands/auto-mode.js +87 -0
  164. package/src/commands/background-session.js +499 -0
  165. package/src/commands/batch.js +345 -0
  166. package/src/commands/command.js +4 -1
  167. package/src/commands/permissions.js +49 -0
  168. package/src/commands/remote-control.js +407 -0
  169. package/src/data/changelog.json +12 -2
  170. package/src/gateways/ws/background-agent-protocol.js +337 -0
  171. package/src/gateways/ws/message-dispatcher.js +10 -0
  172. package/src/gateways/ws/remote-session-protocol.js +102 -1
  173. package/src/gateways/ws/ws-server.js +61 -0
  174. package/src/harness/mcp-client.js +8 -0
  175. package/src/index.js +22 -0
  176. package/src/lib/agent-batch.js +235 -0
  177. package/src/lib/agent-notify.js +66 -0
  178. package/src/lib/agent-schedule-store.js +330 -0
  179. package/src/lib/auto-mode-config.js +396 -0
  180. package/src/lib/background-agent-supervisor.js +202 -17
  181. package/src/lib/background-session-transport.js +315 -0
  182. package/src/lib/permission-denial-store.js +159 -0
  183. package/src/lib/remote-approval-bridge.js +360 -0
  184. package/src/lib/remote-control.js +270 -0
  185. package/src/lib/repl-denials.js +26 -0
  186. package/src/lib/shell-approval.js +4 -0
  187. package/src/lib/ws-rpc-client.js +150 -0
  188. package/src/repl/agent-repl.js +208 -16
  189. package/src/repl/permission-tier.js +31 -0
  190. package/src/runtime/agent-core.js +161 -0
  191. package/src/runtime/coding-agent-contract-shared.cjs +104 -0
  192. package/src/runtime/coding-agent-policy.cjs +28 -0
  193. package/src/runtime/headless-runner.js +126 -8
  194. package/src/runtime/headless-stream.js +87 -1
  195. package/src/runtime/mcp-config.js +11 -0
  196. package/src/runtime/mcp-tool-search.js +0 -0
  197. package/src/runtime/policies/agent-policy.js +17 -0
  198. package/src/workers/background-agent-worker.js +218 -37
  199. package/src/assets/web-panel/assets/AppLayout-B6Q6olcr.js +0 -9
  200. package/src/assets/web-panel/assets/OrderTableRenderer-CjNO-e9T.js +0 -1
  201. package/src/assets/web-panel/assets/Tasks-mhITRxjv.js +0 -1
  202. package/src/assets/web-panel/assets/devWarning-K_woVElh.js +0 -1
  203. package/src/assets/web-panel/assets/index-3ILvaBpo.js +0 -1
  204. package/src/assets/web-panel/assets/index-JpB-FoZq.js +0 -1
  205. package/src/assets/web-panel/assets/useFlexGapSupport-B1lWlqXN.js +0 -1
@@ -0,0 +1,407 @@
1
+ /**
2
+ * `cc remote-control` — unified cross-device entry (gap-analysis 第四阶段 #1).
3
+ *
4
+ * One command composes the existing remote-session stack (WS server + E2EE
5
+ * relay + registry + push + audit) into the Claude-Code `/remote-control`
6
+ * shape:
7
+ *
8
+ * cc remote-control # start host + print pairing URI/QR
9
+ * cc remote-control start --relay-url wss://relay… --peer-id my-desktop
10
+ * cc remote-control status [--json]
11
+ * cc remote-control stop [--port N]
12
+ *
13
+ * `start` boots the WS server IN-PROCESS, then opens a loopback client that
14
+ * acts as the remote session HOST (the registry closes a remote session when
15
+ * its host disconnects, so this client stays open for the server's lifetime).
16
+ * With a relay configured you get the E2EE `chainlesschain://remote-session/…`
17
+ * URI; without one you get a direct-LAN pairing URI embedding ws://<lan-ip>.
18
+ * Devices join with observe/prompt/approve/interrupt scopes — the same wire
19
+ * contract the web-panel and Android client already speak.
20
+ */
21
+
22
+ import chalk from "chalk";
23
+ import { logger } from "../lib/logger.js";
24
+ import { loadConfig } from "../lib/config-manager.js";
25
+ import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
26
+ import { WsRpcClient } from "../lib/ws-rpc-client.js";
27
+ import {
28
+ buildDirectPairingUri,
29
+ pickLanAddress,
30
+ readRemoteControlStates,
31
+ removeRemoteControlState,
32
+ renderQrCode,
33
+ resolveRemoteControlOptions,
34
+ writeRemoteControlState,
35
+ } from "../lib/remote-control.js";
36
+
37
+ export function registerRemoteControlCommand(program) {
38
+ const cmd = program
39
+ .command("remote-control")
40
+ .alias("rc")
41
+ .description(
42
+ "Unified remote-control entry — pair mobile/web devices to drive this machine's agent sessions",
43
+ );
44
+
45
+ cmd
46
+ .command("start", { isDefault: true })
47
+ .description(
48
+ "Start the remote-control host (WS server + remote session) and print the pairing URI/QR",
49
+ )
50
+ .option("-p, --port <port>", "WS server port (default 18800)")
51
+ .option("--host <host>", "Bind host (default 0.0.0.0)")
52
+ .option("--token <token>", "Server auth token (default: auto-generated)")
53
+ .option(
54
+ "--relay-url <url>",
55
+ "Signaling relay URL for E2EE pairing (falls back to direct LAN pairing when unset)",
56
+ )
57
+ .option("--peer-id <id>", "Stable relay peer ID for this host")
58
+ .option(
59
+ "--scopes <csv>",
60
+ "Granted device scopes (observe,prompt,approve,interrupt)",
61
+ )
62
+ .option("--name <name>", "Remote session display name")
63
+ .option(
64
+ "--session <agentSessionId>",
65
+ "Attach to an existing agent session instead of creating a new one",
66
+ )
67
+ .option("--no-qr", "Skip terminal QR rendering")
68
+ .option("--json", "Emit machine-readable pairing JSON and keep serving")
69
+ .action(async (options) => {
70
+ const result = await runRemoteControlStart(options);
71
+ if (result.code !== 0) process.exit(result.code);
72
+ });
73
+
74
+ cmd
75
+ .command("status")
76
+ .description("Show discovered remote-control hosts on this machine")
77
+ .option("--json", "Machine-readable JSON output")
78
+ .option("--prune", "Remove stale state files (dead pid)")
79
+ .action((options) => {
80
+ process.exitCode = runRemoteControlStatus(options);
81
+ });
82
+
83
+ cmd
84
+ .command("stop")
85
+ .description("Stop a running remote-control host")
86
+ .option("-p, --port <port>", "Port of the host to stop")
87
+ .option("--json", "Machine-readable JSON output")
88
+ .action((options) => {
89
+ process.exitCode = runRemoteControlStop(options);
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Boot the host: server runtime + loopback host client + remote session +
95
+ * pairing printout + discovery state. Resolves `{code}` (0 = serving; the
96
+ * server + host client keep the process alive) plus live handles so tests and
97
+ * embedders can tear the host down (`{client, server, state}`).
98
+ */
99
+ export async function runRemoteControlStart(options = {}, _deps = {}) {
100
+ const log = _deps.log || ((m) => console.log(m));
101
+ const errLog = _deps.err || ((m) => logger.error(m));
102
+ const env = _deps.env || process.env;
103
+ const stateDir = _deps.stateDir; // undefined → default dir
104
+ let config = {};
105
+ try {
106
+ config = _deps.loadConfig ? _deps.loadConfig() : loadConfig();
107
+ } catch {
108
+ // config is optional for remote-control
109
+ }
110
+
111
+ let resolved;
112
+ try {
113
+ resolved = resolveRemoteControlOptions({
114
+ flags: {
115
+ port: options.port,
116
+ host: options.host,
117
+ token: options.token,
118
+ relayUrl: options.relayUrl,
119
+ peerId: options.peerId,
120
+ scopes: options.scopes,
121
+ name: options.name,
122
+ },
123
+ env,
124
+ config,
125
+ });
126
+ } catch (err) {
127
+ errLog(err.message);
128
+ return { code: 4 };
129
+ }
130
+
131
+ // Refuse to double-start on a port that already has a LIVE host.
132
+ const existing = readRemoteControlStates({ dir: stateDir }).find(
133
+ (state) => state.port === resolved.port && state.alive,
134
+ );
135
+ if (existing) {
136
+ errLog(
137
+ `remote-control host already running on port ${resolved.port} (pid ${existing.pid}). ` +
138
+ `Use \`cc remote-control status\` or \`cc remote-control stop --port ${resolved.port}\`.`,
139
+ );
140
+ return { code: 2 };
141
+ }
142
+
143
+ let server;
144
+ try {
145
+ const startServer =
146
+ _deps.startServer ||
147
+ (async () =>
148
+ createAgentRuntimeFactory()
149
+ .createServerRuntime({
150
+ port: resolved.port,
151
+ host: resolved.host,
152
+ token: resolved.token,
153
+ allowRemote: resolved.host !== "127.0.0.1",
154
+ remoteSessionRelayUrl: resolved.relayUrl,
155
+ remoteSessionPeerId: resolved.peerId,
156
+ })
157
+ .startServer());
158
+ server = await startServer(resolved);
159
+ } catch (err) {
160
+ errLog(`Failed to start remote-control host: ${err.message}`);
161
+ return { code: 1 };
162
+ }
163
+
164
+ // Loopback HOST connection — must stay open, its disconnect closes the
165
+ // remote session (registry semantics).
166
+ const client =
167
+ _deps.createClient?.(resolved) ||
168
+ new WsRpcClient({ url: `ws://127.0.0.1:${resolved.port}` });
169
+ let remoteSession;
170
+ let pairing;
171
+ let agentSessionId = options.session || null;
172
+ try {
173
+ await client.connect();
174
+ await client.auth(resolved.token);
175
+ if (!agentSessionId) {
176
+ const created = await client.request("session-create", {
177
+ sessionType: "agent",
178
+ projectRoot: _deps.cwd || process.cwd(),
179
+ });
180
+ agentSessionId =
181
+ created.sessionId ||
182
+ created.payload?.sessionId ||
183
+ created.session?.sessionId ||
184
+ created.session?.id;
185
+ if (!agentSessionId) {
186
+ throw new Error("session-create returned no sessionId");
187
+ }
188
+ }
189
+ const createdRemote = await client.request("remote-session-create", {
190
+ sessionId: agentSessionId,
191
+ name: resolved.name,
192
+ scopes: resolved.scopes,
193
+ });
194
+ remoteSession = createdRemote.session;
195
+ pairing = createdRemote.pairing;
196
+ } catch (err) {
197
+ errLog(`Failed to create remote session: ${err.message}`);
198
+ client.close();
199
+ try {
200
+ await server.stop?.();
201
+ } catch {
202
+ // best-effort teardown
203
+ }
204
+ return { code: 1 };
205
+ }
206
+
207
+ const relayMode = Boolean(pairing?.uri);
208
+ const lanAddress = _deps.lanAddress || pickLanAddress() || "127.0.0.1";
209
+ const wsUrl = `ws://${lanAddress}:${resolved.port}`;
210
+ const pairingUri = relayMode
211
+ ? pairing.uri
212
+ : buildDirectPairingUri({
213
+ wsUrl,
214
+ serverToken: resolved.token,
215
+ remoteSessionId: remoteSession.sessionId,
216
+ agentSessionId,
217
+ pairingToken: pairing.token,
218
+ scopes: pairing.scopes,
219
+ expiresAt: pairing.expiresAt,
220
+ });
221
+
222
+ const state = {
223
+ pid: process.pid,
224
+ port: resolved.port,
225
+ host: resolved.host,
226
+ wsUrl,
227
+ token: resolved.token,
228
+ mode: relayMode ? "relay" : "direct",
229
+ relayUrl: resolved.relayUrl || null,
230
+ peerId: resolved.peerId || null,
231
+ agentSessionId,
232
+ remoteSessionId: remoteSession.sessionId,
233
+ scopes: pairing.scopes,
234
+ startedAt: new Date().toISOString(),
235
+ };
236
+ const stateFile = writeRemoteControlState(state, { dir: stateDir });
237
+ const cleanup = () => {
238
+ removeRemoteControlState(resolved.port, { dir: stateDir });
239
+ };
240
+ process.once("exit", cleanup);
241
+ process.once("SIGINT", cleanup);
242
+ process.once("SIGTERM", cleanup);
243
+
244
+ if (options.json) {
245
+ log(
246
+ JSON.stringify(
247
+ {
248
+ mode: state.mode,
249
+ wsUrl,
250
+ port: resolved.port,
251
+ agentSessionId,
252
+ remoteSessionId: remoteSession.sessionId,
253
+ pairingUri,
254
+ pairing: {
255
+ token: pairing.token,
256
+ scopes: pairing.scopes,
257
+ expiresAt: pairing.expiresAt,
258
+ },
259
+ stateFile,
260
+ },
261
+ null,
262
+ 2,
263
+ ),
264
+ );
265
+ } else {
266
+ log("");
267
+ log(chalk.bold(" Remote Control ready"));
268
+ log("");
269
+ log(
270
+ ` Mode: ${state.mode === "relay" ? chalk.cyan("relay (E2EE)") : chalk.cyan("direct LAN")}`,
271
+ );
272
+ log(` WS server: ${chalk.cyan(wsUrl)}`);
273
+ log(` Agent sess: ${agentSessionId}`);
274
+ log(` Remote sess: ${remoteSession.sessionId}`);
275
+ log(` Scopes: ${pairing.scopes.join(", ")}`);
276
+ log(
277
+ ` Token exp: ${pairing.expiresAt ? new Date(pairing.expiresAt).toISOString() : "n/a"} ${chalk.dim("(one-time; re-run start for another device)")}`,
278
+ );
279
+ log("");
280
+ log(" Pairing URI (open in ChainlessChain mobile / paste in web panel):");
281
+ log("");
282
+ log(" " + chalk.green(pairingUri));
283
+ if (options.qr !== false) {
284
+ const qr = await renderQrCode(pairingUri, _deps);
285
+ if (qr) {
286
+ log("");
287
+ log(
288
+ qr
289
+ .split("\n")
290
+ .map((line) => " " + line)
291
+ .join("\n"),
292
+ );
293
+ } else {
294
+ log("");
295
+ log(
296
+ chalk.dim(
297
+ " (install the optional `qrcode` package for a terminal QR)",
298
+ ),
299
+ );
300
+ }
301
+ }
302
+ log("");
303
+ log(chalk.dim(" Press Ctrl+C to stop"));
304
+ log("");
305
+ }
306
+
307
+ return { code: 0, client, server, state, pairingUri };
308
+ }
309
+
310
+ export function runRemoteControlStatus(options = {}, _deps = {}) {
311
+ const log = _deps.log || ((m) => console.log(m));
312
+ const states = readRemoteControlStates({ dir: _deps.stateDir });
313
+ if (options.prune) {
314
+ for (const state of states) {
315
+ if (!state.alive && state.port) {
316
+ removeRemoteControlState(state.port, { dir: _deps.stateDir });
317
+ }
318
+ }
319
+ }
320
+ if (options.json) {
321
+ log(
322
+ JSON.stringify(
323
+ // The token stays in the 0600 state file — status output may be pasted
324
+ // into issues/chat, so redact it here.
325
+ states.map(({ token: _token, ...rest }) => rest),
326
+ null,
327
+ 2,
328
+ ),
329
+ );
330
+ return 0;
331
+ }
332
+ if (states.length === 0) {
333
+ log(chalk.gray("\n No remote-control hosts found.\n"));
334
+ return 0;
335
+ }
336
+ log("");
337
+ log(chalk.bold(" Remote-control hosts:\n"));
338
+ for (const state of states) {
339
+ if (state.invalid) {
340
+ log(` ${chalk.red("invalid")} ${state.stateFile}`);
341
+ continue;
342
+ }
343
+ const badge = state.alive ? chalk.green("running") : chalk.yellow("stale");
344
+ log(
345
+ ` ${badge} port ${String(state.port).padEnd(6)} pid ${String(state.pid).padEnd(8)} ` +
346
+ `${(state.mode || "?").padEnd(7)} session ${state.agentSessionId || "?"}`,
347
+ );
348
+ }
349
+ log("");
350
+ return 0;
351
+ }
352
+
353
+ export function runRemoteControlStop(options = {}, _deps = {}) {
354
+ const log = _deps.log || ((m) => console.log(m));
355
+ const errLog = _deps.err || ((m) => logger.error(m));
356
+ const kill = _deps.kill || ((pid) => process.kill(pid));
357
+ const states = readRemoteControlStates({ dir: _deps.stateDir }).filter(
358
+ (state) => !state.invalid,
359
+ );
360
+ const port = options.port ? Number(options.port) : null;
361
+ const candidates = port
362
+ ? states.filter((state) => state.port === port)
363
+ : states;
364
+ if (candidates.length === 0) {
365
+ errLog(
366
+ port
367
+ ? `No remote-control host recorded on port ${port}.`
368
+ : "No remote-control hosts recorded.",
369
+ );
370
+ return 2;
371
+ }
372
+ if (!port && candidates.filter((state) => state.alive).length > 1) {
373
+ errLog(
374
+ "Multiple hosts running — specify which with --port. Use `cc remote-control status` to list.",
375
+ );
376
+ return 4;
377
+ }
378
+ let failures = 0;
379
+ const results = [];
380
+ for (const state of candidates) {
381
+ let stopped = false;
382
+ if (state.alive) {
383
+ try {
384
+ kill(state.pid);
385
+ stopped = true;
386
+ } catch (err) {
387
+ failures += 1;
388
+ errLog(`Failed to stop pid ${state.pid}: ${err.message}`);
389
+ }
390
+ }
391
+ removeRemoteControlState(state.port, { dir: _deps.stateDir });
392
+ results.push({ port: state.port, pid: state.pid, stopped });
393
+ if (!options.json) {
394
+ log(
395
+ stopped
396
+ ? chalk.green(
397
+ ` ✓ stopped host on port ${state.port} (pid ${state.pid})`,
398
+ )
399
+ : chalk.gray(
400
+ ` · cleaned stale record for port ${state.port} (pid ${state.pid} not running)`,
401
+ ),
402
+ );
403
+ }
404
+ }
405
+ if (options.json) log(JSON.stringify({ results }, null, 2));
406
+ return failures > 0 ? 1 : 0;
407
+ }
@@ -3,7 +3,7 @@
3
3
  "releases": [
4
4
  {
5
5
  "productVersion": "Unreleased",
6
- "cliVersion": "0.162.154",
6
+ "cliVersion": "0.162.156",
7
7
  "cliVersions": [
8
8
  "0.4.46",
9
9
  "0.37.4",
@@ -11,11 +11,21 @@
11
11
  "0.162.151",
12
12
  "0.162.152",
13
13
  "0.162.153",
14
- "0.162.154"
14
+ "0.162.154",
15
+ "0.162.155",
16
+ "0.162.156"
15
17
  ],
16
18
  "date": null,
17
19
  "title": "",
18
20
  "sections": [
21
+ {
22
+ "heading": "Added — cc CLI 0.162.156:agenda/batch 双命令(第四阶段收口)+ @chainlesschain/agent-sdk 0.1.0 npm 首发(CLI-only npm 发版)",
23
+ "body": "> `chainlesschain` 0.162.155 → **0.162.156** 发 npm `latest`(经 `npm-publish.yml`,`--provenance --access public`);**`@chainlesschain/agent-sdk` 0.1.0 同 run 首发 npm**(平台化第三阶段产物,npm-publish.yml / release.yml 均已加发布 step,防 trap #127 漏发)。纯 `packages/cli/src`(+web-panel dist 随包)+ 新独立包,未触 `pdh/lib` → 无 Android cc bundle rollover / 无 USR_VERSION 改动。命令数 **170 → 172**(新增顶层 `agenda`/`batch`,清单已重生,13 文档面已扫)。\n\n- **`cc agenda` + notify/schedule 双 agent 工具(第四阶段批19)**:Monitor/Cron/Push 暴露为 agent 可调工具(AGENT_TOOLS 21→23)——`notify` 推送到用户已配置的多通道通知器(Telegram/企业微信/钉钉/飞书);`schedule` 一工具五动作(wakeup/cron/monitor/list/cancel),单轮进程持不住定时器 → 意图持久化到 `~/.chainlesschain/agent-schedule/*.jsonl`(0600),由 `cc agenda list|run|cancel` 消费触发(wakeup/cron 到期 spawn `cc agent -p`,monitor 跑命令匹配 stop_when 即通知)。两工具 LOW 风险、plan 模式禁用;29 单测 + 真进程 smoke。\n- **`cc batch` 动态 worktree 批处理(第四阶段批20,第四阶段全收口)**:把大改动拆成独立单元并行跑——`--units <file>` 或 `--decompose <goal> --parts N`(agent 经 `--json-schema` 产单元 JSON,`--plan-only` 只打印);每单元独立 git worktree(复用 `cc team --worktree` 的 worktree-isolator),`--test` 门控、`--merge` 顺序集成(后合分支与已合冲突时报告不强合、测试失败单元永不合并);15 单测 + 2 真 git/worktree 集成测试。\n- **`@chainlesschain/agent-sdk` 0.1.0 首发**:`cc agent` stream-json 双工协议的 TypeScript 正式契约(Agent Protocol v1)——流式事件全词汇 + 审批回调(fail-closed)+ 检查点 + 会话恢复;`AgentSession` spawn 客户端(Windows `cmd.exe /c` shim + `NoDefaultCurrentDirectoryInExePath` 反劫持 + `taskkill /T` 进程树回收内置)、`attachBackgroundSession` 后台管道接管、`/browser` 浏览器安全入口(web-panel bg-\\* 帧)、`/protocol` 纯类型入口;双构建 ESM+CJS,零运行时依赖,36 测试(单元+真管道集成+真 CLI e2e)。VS Code 扩展(vendored)与 web-panel(vite alias)已消费;JetBrains 对齐语言中立 `docs/PROTOCOL.md`。用户文档 `docs-site/docs/chainlesschain/agent-sdk.md`。\n- 修复(随本版仓内,不在 cli 包面):SDK 真 CLI e2e 抓出的两 IDE 首会话不落盘洞(IDE 重载后 resume 静默空会话)已在两插件修复,随各自下次插件发版出货。"
24
+ },
25
+ {
26
+ "heading": "Added — cc CLI 0.162.155:权限模式/auto 分类器 + 后台会话可交互接管全家桶(第一阶段收口,CLI-only npm 发版)",
27
+ "body": "> `chainlesschain` 0.162.154 → **0.162.155** 已发 npm `latest`(经 `npm-publish.yml`,`--provenance --access public`)。纯 `packages/cli/src`(+web-panel dist 随包)增量,未触 `pdh/lib` → 无 Android cc bundle rollover / 无 USR_VERSION 改动。gap-analysis「第一阶段:安全与可运营性」4 项全部落地(批 1-15,9 commit `e3ab46c73f..1618a3ca83`)。命令数 **165 → 170**(新增顶层 `attach`/`auto-mode`/`daemon`/`logs`/`remote-control`,清单已重生)。发版前本机三层全绿(unit 21808 / integration 除 1 已知 real-spawn flake 隔离 3 连绿 / e2e 628)。\n\n- **权限模式补齐 `manual`/`auto`/`dontAsk`**(headless + 交互 REPL 双面):`manual`=default 别名;`auto`=可配置分类器——settings `autoMode.decisions` 按 riskLevel(对象/数组形)与 tool/commandPattern 细粒度规则(`*` glob,声明序优先)映射 allow/ask/deny,未配置时字节不变映射 trusted;`dontAsk`=需确认动作直接 deny 不弹审批(REPL 两处 confirmer 全覆盖)。REPL `/permissions auto|dontask` 中途切换,Shift+Tab 循环兼容;`cc agent --permission-mode` 对交互式会话生效(顺带修 `resolveAgentPolicy` 白名单静默丢弃 `--vim/--think/--fallback-model/--pdh` 等交互 flag 的预存 bug)。\n- **`cc auto-mode defaults|config [--json]`**:内置分类默认值文档 + user/project/local/managed 分层合并后的有效配置(含细粒度规则展示);修 `mergeSandboxSettings` 数组分支把数组形 decisions 毁成 `\"[object Object]\"` 的潜伏 bug(新 `mergeAutoModeSettings`,decisions=有序规则集 closer 层整体替换)。\n- **`cc permissions recent|denials [--json|--clear]` + 决策逐层解释链**:跨会话持久 denial ring buffer(headless + REPL 双源);被拒 run_shell 附带 `permissionChain`(settings-rules→shell-policy→approval-gate 各层判定+理由),`/permissions denials` 与 `cc permissions recent` 渲染紧凑链——一眼看清「allow 规则为何没生效」。\n- **后台会话可运营全家桶**:顶层 `cc logs <id>` / `cc attach <id>` / `cc daemon status|view|stop|rename|resume`。worker heartbeat + workerPid/agentPid 分离 + stale-heartbeat/PID-reuse 防误杀(可靠标 `lost`);`cc daemon resume <id> <prompt>` 把崩溃/终态会话按 sessionId 续接为新后台运行(原 argv 有意不持久化防 secrets 落盘)。\n- **`cc attach` 可交互接管(session transport)**:worker 每会话托管本地 NDJSON 控制通道(Win named pipe / POSIX socket,随机 token 存 0600 state 文件);attach 后键入即发 follow-up prompt(同 `--session` 自动续接对话史,多轮 turn 循环),`/stop` 截断当前轮,turn 运行中断开任务照跑、idle 断开会话收尾;transport 不可用自动回退日志流,`--no-input` 强制纯日志。\n- **web-panel「后台 Agent」面板**:新 `bg-*` WS 协议(list/view/attach/prompt/stop-turn/detach/stop/rename/resume,attach 期间推送 worker 事件与日志增量);takeover token 永不过 WS 边界。面板含列表/接管卡片/日志流/重命名/续跑。\n- 修复:后台 rename 与 worker read-modify-write 竞态(写后校验+有界重试);`settingsVerdict` 未传入 executeToolInner 的解释链断点;launcher/worker 状态竞写(先写 state 后 spawn + heartbeat 重申 transport)。\n- **`cc remote-control`(第四阶段批17,随本版一同发布)**:统一远控入口(别名 `rc`)——start/status/stop 组合既有 remote-session 栈(WS server + E2EE relay + registry + ledger + push + audit),配对 URI 双模式(relay E2EE / 直连 LAN),跨进程 0600 state 发现;顺带修 `cc serve --remote-session-relay-url` 白名单丢参从未生效的预存 bug。\n- **MCP tool search(第二阶段批16,随本版一同发布)**:大规模 MCP 面下工具定义超上下文阈值(默认窗口 10%,`mcp.toolSearch` settings / `CC_TOOL_SEARCH` env 可配)时自动换成 `[deferred]` stub + 内部 `tool_search` 检索工具;完整 schema 经 tool result 返回(append-only,保 prompt cache 稳定);REPL `/context` 新增 MCP tool schemas 占用与优化建议一节。未超阈值/关闭时行为字节不变。"
28
+ },
19
29
  {
20
30
  "heading": "Added — cc CLI 0.162.154:`cc complete` 内联补全后端(IDE ghost-text)+ 本地化残留清扫(CLI-only npm 发版)",
21
31
  "body": "> `chainlesschain` 0.162.153 → **0.162.154** 已发 npm `latest`(经 `npm-publish.yml`,`--provenance --access public`)。纯 `packages/cli/src` 增量,未触 `pdh/lib` → 无 Android cc bundle rollover / 无 USR_VERSION 改动。发版动因:VS Code 0.37.7 + JetBrains 0.4.49 的内联 ghost-text 补全依赖 `cc complete`,此命令不在已发的 0.162.153 里 → 不发 npm 则两端补全功能对终端用户不可用。命令数 **164 → 165**(新增 `cc complete`,清单已重生)。发版前本机三层全绿(unit+integration+e2e)。\n\n- **`cc complete` — IDE 内联补全(fill-in-the-middle)后端**:读 stdin JSON `{prefix,suffix,language}` → 打印光标处应插入的代码(`--json` 输出 `{completion,model,provider}`)。复用 `cc ask` 的 provider 路由(`queryLLM`)——honor 用户已配置的 LLM/provider/key,零新鉴权(本地 FIM 模型如 `qwen2.5-coder` 更快,慢速 chat 模型也能用因为是手动触发)。纯可测 helper:`buildFimPrompt`(cursor-sentinel 提示让无原生 FIM 的 chat 模型也有双向上下文)+ `cleanCompletion`(剥 markdown fence / `<CURSOR>` / 硬上限 2000 字符;**故意不做 suffix-overlap 裁剪**——朴素「补全结尾等于 suffix 首行就切」会吃掉合法的结尾 token 如 `if` 块自己的 `}`,重复交给 prompt 的「勿重复周围代码」指令处理)。fail-quiet:后端故障返回空补全而非在编辑器里抛错。IDE 侧(VS Code `InlineCompletionItemProvider` / JetBrains `InlineCompletionProvider`)共享同一后端,两端行为一致。"