chainlesschain 0.162.156 → 0.162.158

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 (195) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-DjBXlypQ.js → AIOps-BwpTJTN_.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Cv9er9bp.js → ActionButton-BtQOoo3l.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-CLKDjSNs.js → Analytics-Duf_rXZa.js} +3 -3
  7. package/src/assets/web-panel/assets/AppLayout-XQK3wjvB.js +9 -0
  8. package/src/assets/web-panel/assets/Artifacts-CTVt96zW.css +1 -0
  9. package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +1 -0
  10. package/src/assets/web-panel/assets/{Audit-CXafD178.js → Audit-DNjOGQLh.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-CP5c-GSd.js → BackgroundAgents-FH9RAfB5.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-BbYLQfyt.js → Backup-ZIbrR4-U.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-Bqe0MwZL.js → BaseInput-DSt3NaND.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-jNA_yUPH.js → Chat-CY-wvbjv.js} +6 -6
  15. package/src/assets/web-panel/assets/Chat-KSGPtU0L.css +1 -0
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BOFLF01s.js → ChatBubbleRenderer-BAV9V-t-.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-Dlzz-zD1.js → Checkbox-8JVadE_B.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-BAsDQjGf.js → Codegen-CN83nPYI.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-Ju0HrAuS.js → Col-xPSvqdN8.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DynJd43Z.js → Community-D78cnIjG.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CabbZVww.js → Compact-CSw6Csbp.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-DW4BmAoh.js → Compliance-E_HhjcYR.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-Bk1S_7X-.js → Cowork-DybeQjmm.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-By4sh_NM.js → Cron-DS0BJeLY.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-DdZdbS1C.js → Crosschain-BOwVgkbs.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-CkL-sQyS.js → DID-DRUr9YuX.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-g_mOpumg.js → Dashboard-Cht-yUKW.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DtHyE7Ao.js → Dropdown-BLr_EJ9X.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-Kpvov5Wr.js → EmailListRenderer-Drexuh5W.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-C4kZoePR.js → FamilyGuardDashboard-D9Q2Kq63.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-BAwWoK2N.js → Federation-C-Z1xI-T.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-DeI0i5f8.js → FormItemContext-DLHPMkQF.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-CkcKRtos.js → GenericCardRenderer-DxxEdn2r.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-kX6jjI9y.js → Git-syATcaBt.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BXJCBm6x.js → Governance-CYe_r8Zf.js} +1 -1
  36. package/src/assets/web-panel/assets/Inference-GWxErB7k.js +1 -0
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-3Dnuf1Wh.js → KnowledgeGraph-qQvVCZoi.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-lVAZCtUB.js → Logs-BenAU2W8.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-7agE3Or1.js +1 -0
  40. package/src/assets/web-panel/assets/MarkdownRenderer-DbzhFwvz.css +1 -0
  41. package/src/assets/web-panel/assets/Marketplace-D4Jn5fpD.js +1 -0
  42. package/src/assets/web-panel/assets/{McpTools-CqjzefRU.js → McpTools-DtAwoqiQ.js} +4 -4
  43. package/src/assets/web-panel/assets/{Memory-BFiPAxk6.js → Memory-BI_O50I8.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-D0O43VwF.js → MobileBridge-CQWE2lqx.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-B3now48t.js → MobileProjects-CVtTqgtg.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-DybhtaCS.js → Mtc-Dnr16qsn.js} +2 -2
  47. package/src/assets/web-panel/assets/{MtcAudit-D7MmY4F0.js → MtcAudit-CQ5Xe4-a.js} +5 -5
  48. package/src/assets/web-panel/assets/{Multisig-wZvGJnB8.js → Multisig-Bjr8O-wy.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-BNPpgfvn.js → NLProgramming-CEe-KirU.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-B6FS3KBj.js → Notes-spKJIKF9.js} +4 -4
  51. package/src/assets/web-panel/assets/{NotificationSettings-CK0yla9U.js → NotificationSettings-DRp83LFG.js} +1 -1
  52. package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +1 -0
  53. package/src/assets/web-panel/assets/{Organization-geqgwN1T.js → Organization-BlgB0IYg.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-BWJYZFbC.js → Overflow-a_35HqZI.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-DnU1505F.js → P2P-CSNHCNPC.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-BGY74KnC.js → PdhVaultBrowser-C8M1JH-1.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-CgArrWtQ.js → Permissions-DmjJi9Ms.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-C2vYE61a.js → PersonalDataHub-DfuwRyf4.js} +2 -2
  59. package/src/assets/web-panel/assets/{Pipeline-CBYLI4yW.js → Pipeline-B2-S4ekE.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-B-PdW9aN.js → Privacy-B1dOiBLC.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-CuWQegbV.js → ProjectInit-_pNmc-DX.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-4d32m_cS.js → ProjectSettings-DmwG2Ii1.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-VLA7ttwd.js → Projects-COATmHxF.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-B_Izb_oa.js → Providers-C6HSjg1d.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-C6B_ndXV.js → QrScannerModal-CrDmS7l1.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-C6tavL-y.js → QuickAsk-CfKvWt-x.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-CF-LaZ6x.js → Recommend-DMa6Kuzv.js} +1 -1
  68. package/src/assets/web-panel/assets/RemoteSession-DrktUo5x.js +4 -0
  69. package/src/assets/web-panel/assets/Reputation-BTPQ1vjL.js +1 -0
  70. package/src/assets/web-panel/assets/{Row-DO0fd48A.js → Row-B6Q7QkMG.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-BV5LDzFR.js → RssFeed-B1IJhAmX.js} +3 -3
  72. package/src/assets/web-panel/assets/Search-xbea1kGD.js +1 -0
  73. package/src/assets/web-panel/assets/{Security-DwSrWRCw.js → Security-7rs0cO3F.js} +4 -4
  74. package/src/assets/web-panel/assets/{Services-BEf1QyM4.js → Services-0fjixK0S.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-LZXTpovy.js → Skeleton-B_rVq9Cu.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-SKgc0Zug.js → Skills-C5uM90b9.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-C_QVOtkN.js → Sla-U4pYCIZH.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-v5TW2V-X.js → SpeechSettings-Cdz4yTWx.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-CQPjHdqU.js → SyncSettings-Me3aetHv.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-DCd-uYcI.js → Tasks-Cf6aNkL_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-62VDIjtj.js → Templates-CYatk4tU.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-BnFQ0pNy.js → Tenant-DIMKLw7o.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-D3v0MGSj.js → Terminal-C_cp-uSJ.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-BI1Ka-cu.js → TimelineRenderer-cUPVNADp.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-BwX1jXYP.js → Tokens-CiaEIXVf.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-CSA8TqNx.js → Trigger-DB1WU5lQ.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-Bn3IjCx7.js → Trust-Cfht9Ijy.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-pUbOAqNi.js → UkeySign-K3P4Vt9S.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-Db2RercF.js → VideoEditing-DWjhjXRh.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-CpR1BVAl.js → Wallet-D9-Q1Kan.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-Yxtkn7Zp.js → WebAuthn-BDszy-oV.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-CYLe9a2E.js → WorkflowEditor-DIhij4rA.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-gsOGdak0.js → chat-D7VCiKbh.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-D9fN299a.js → colors-BvOd9pVn.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-vkd7s-ay.js → compact-item-DoqXBiAO.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-C7YkbMyy.js → createContext-CrKRzUtp.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-ig7reRSU.js → hasIn-BWAev4WC.js} +1 -1
  99. package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
  100. package/src/assets/web-panel/assets/{index-W52sHRdr.js → index-1poMpMU0.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZAxCnjS.js → index-8g5TJCZ3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-9TxdMYju.js → index-B0vGEbJT.js} +1 -1
  103. package/src/assets/web-panel/assets/index-B1_pRTtB.js +1 -0
  104. package/src/assets/web-panel/assets/{index-DhdXbsXX.js → index-B30NvR21.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B7QRpKif.js → index-BCyP071m.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bt-9ZhIu.js → index-BHOcVYrx.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BYe9HRSt.js → index-BN9m4Ux2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BkP17Z95.js → index-BWWsaimP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CDWTycrg.js → index-BXyl8O23.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CspGymmg.js → index-BZGk8AbZ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DFfxF7Vt.js → index-Bf9YlbDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DQ8U7kzC.js → index-Bj7EgmOp.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D2yhtk6a.js → index-BkgnGX7v.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-FzGr6CVk.js → index-BoYrS_L2.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BMw48d19.js → index-Bq_ctRpz.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CAkz1f1r.js → index-C5kRmj3P.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-SDrcQXO1.js → index-CDGLdn12.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-gj8kxcAi.js → index-CEPHMSih.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-PkH39qsp.js → index-CEXfKSiI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BHHN_KTS.js → index-CSb-tnIo.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-gpurplyq.js → index-CmJ6xKBO.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BfpVcXKa.js → index-CoD31Ndm.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CGzGe5tv.js → index-Cx7M7PSF.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BhftELHI.js → index-D23L1V76.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-DPguq3X7.js → index-DV-Rfmfx.js} +4 -4
  126. package/src/assets/web-panel/assets/{index-BpGlLbRv.js → index-DlGu7As6.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C8HZMygt.js → index-DvDOpsJB.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DuwbKHoS.js → index-HwHdTusY.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-C7AY39sg.js → index-ISvPZ05r.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nX8YE6cj.js → index-JzUmJMvn.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DvCJXmrx.js → index-KR1-Em9C.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Co2NYyew.js → index-PDxGjKRu.js} +1 -1
  133. package/src/assets/web-panel/assets/index-TMM9FuW-.js +1 -0
  134. package/src/assets/web-panel/assets/{index-D6s_kbjt.js → index-YGDy7nNq.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-Dy_TKZ9h.js → index-gmxqu5Kk.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-DFM1oNe1.js → index-oY45XrkG.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-bVbgYBhv.js → index-ww1QtLnX.js} +1 -1
  138. package/src/assets/web-panel/assets/{index-BvDEukXe.js → index-zH-tuuGZ.js} +1 -1
  139. package/src/assets/web-panel/assets/{initDefaultProps-BrgfVRg9.js → initDefaultProps-XW-OR3mL.js} +1 -1
  140. package/src/assets/web-panel/assets/{motion-D_3xmwac.js → motion-CeuEHh20.js} +1 -1
  141. package/src/assets/web-panel/assets/{move-DDDJdlxw.js → move-Dva79Keg.js} +1 -1
  142. package/src/assets/web-panel/assets/{mtc-parser-BK2uEWwn.js → mtc-parser-C0B6bD2j.js} +1 -1
  143. package/src/assets/web-panel/assets/{omit-BPHKbDcf.js → omit-AkZvO-a2.js} +1 -1
  144. package/src/assets/web-panel/assets/{pickAttrs-LNnTscuz.js → pickAttrs-B4r55qGc.js} +1 -1
  145. package/src/assets/web-panel/assets/{placementArrow-CvmTZll5.js → placementArrow-BLH-z7YE.js} +1 -1
  146. package/src/assets/web-panel/assets/{responsiveObserve-eBj7qi2J.js → responsiveObserve-ksEGZ2Wf.js} +1 -1
  147. package/src/assets/web-panel/assets/{slide-D68hHkrM.js → slide-Cth4OD1Z.js} +1 -1
  148. package/src/assets/web-panel/assets/{statusUtils-daTlaeo0.js → statusUtils-9S83ezxw.js} +1 -1
  149. package/src/assets/web-panel/assets/{styleChecker-DtyKMtHB.js → styleChecker-7yQmQGZ4.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFlexGapSupport-ekFwFs9h.js → useFlexGapSupport-DpGnMmPc.js} +1 -1
  151. package/src/assets/web-panel/assets/{useFs-jRaSEoF4.js → useFs-C4zDZIXH.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-DD5nGzjA.js → usePersonalDataHub-CoLICdUZ.js} +1 -1
  153. package/src/assets/web-panel/assets/{vnode-KcgLUiom.js → vnode-CKyH-35X.js} +1 -1
  154. package/src/assets/web-panel/assets/{zoom-Cyk2PQ-s.js → zoom-DRDGit17.js} +1 -1
  155. package/src/assets/web-panel/index.html +2 -2
  156. package/src/command-manifest.json +8 -1
  157. package/src/commands/agent.js +49 -5
  158. package/src/commands/artifacts.js +161 -0
  159. package/src/commands/browse.js +204 -2
  160. package/src/data/changelog.json +13 -2
  161. package/src/gateways/ws/artifact-protocol.js +219 -0
  162. package/src/gateways/ws/message-dispatcher.js +6 -0
  163. package/src/gateways/ws/ws-server.js +32 -0
  164. package/src/index.js +6 -0
  165. package/src/lib/accessibility.js +55 -0
  166. package/src/lib/artifact-store.js +236 -0
  167. package/src/lib/chrome-connector.js +349 -0
  168. package/src/lib/hook-runner.cjs +47 -7
  169. package/src/lib/ide-context.js +84 -0
  170. package/src/lib/permission-rules.cjs +26 -3
  171. package/src/lib/plugin-runtime/scopes.js +12 -0
  172. package/src/lib/remote-control.js +35 -0
  173. package/src/lib/safe-mode.js +40 -0
  174. package/src/lib/shell-selector.cjs +203 -0
  175. package/src/lib/skill-loader.js +19 -0
  176. package/src/lib/slash-dispatch.js +34 -0
  177. package/src/lib/web-ui-server.js +120 -7
  178. package/src/repl/agent-repl.js +330 -116
  179. package/src/repl/remote-approval.js +111 -0
  180. package/src/runtime/agent-core.js +129 -8
  181. package/src/runtime/coding-agent-contract-shared.cjs +105 -0
  182. package/src/runtime/coding-agent-policy.cjs +31 -0
  183. package/src/runtime/headless-stream.js +51 -1
  184. package/src/runtime/policies/agent-policy.js +4 -0
  185. package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +0 -9
  186. package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
  187. package/src/assets/web-panel/assets/Inference-ujD2JSKo.js +0 -1
  188. package/src/assets/web-panel/assets/Marketplace-qnzNPm-A.js +0 -1
  189. package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +0 -1
  190. package/src/assets/web-panel/assets/RemoteSession-DBqTTQ9_.js +0 -4
  191. package/src/assets/web-panel/assets/Reputation-B01FALUi.js +0 -1
  192. package/src/assets/web-panel/assets/Search-CTti4_MP.js +0 -1
  193. package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +0 -1
  194. package/src/assets/web-panel/assets/index-BEibanjg.js +0 -1
  195. package/src/assets/web-panel/assets/index-z_25tU3p.js +0 -1
@@ -111,6 +111,7 @@ import {
111
111
  resolveAskIdleTimeoutMs,
112
112
  questionWithIdleTimeout,
113
113
  } from "./permission-prompt.js";
114
+ import { describeAskContext, raceLocalAndRemote } from "./remote-approval.js";
114
115
  import {
115
116
  parsePermissionTier,
116
117
  parsePermissionModeArg,
@@ -448,6 +449,11 @@ export async function startAgentRepl(options = {}) {
448
449
  // gate is wired. The wrapper is installed only when settings customize the
449
450
  // map, and only bites while _sessionTier === "auto" (isActive predicate).
450
451
  let _autoModeResolved = null;
452
+ // `/remote-control` (批17/18 REPL 收口): paired-device approvals for this
453
+ // interactive session. Holds { bridge, pairing, close } while active; both
454
+ // interactive confirmers below race the terminal against the paired device
455
+ // (first answer wins). null = local-only (byte-identical prompt path).
456
+ let _remoteApproval = null;
451
457
  const baseUrl = options.baseUrl || "http://localhost:11434";
452
458
  const apiKey = options.apiKey || null;
453
459
  // Configured vision model (config.llm.visionModel) — used when a turn carries
@@ -617,54 +623,79 @@ export async function startAgentRepl(options = {}) {
617
623
  _autoModeResolved = null; // fail to the plain gate tiers
618
624
  }
619
625
  if (typeof _approvalGate.setConfirmer === "function") {
620
- _approvalGate.setConfirmer(async ({ tool, args, riskLevel }) => {
621
- if (_dontAskActive()) {
622
- process.stdout.write(
623
- chalk.yellow(
624
- `\n ✕ denied without asking (dontAsk mode): ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}\n`,
625
- ),
626
- );
627
- return false;
628
- }
629
- await _fireNotification(
630
- `Permission needed: ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}`,
631
- );
626
+ // The local prompt as a cancelable handle so an active /remote-control
627
+ // can race it against a paired device: cancel closes the readline when
628
+ // the device answers first, and `canceled` mutes the stranded
629
+ // idle-timeout leg so it can't print "auto-denied" after the fact.
630
+ const askGateApprovalLocally = ({ tool, args, riskLevel }) => {
632
631
  const rlConfirm = readline.createInterface({
633
632
  input: process.stdin,
634
633
  output: process.stdout,
635
634
  });
635
+ let canceled = false;
636
636
  const q = (p) => new Promise((res) => rlConfirm.question(p, res));
637
637
  const cmd = args?.command ? ` ${args.command}` : "";
638
- const res = await questionWithIdleTimeout(
639
- q,
640
- chalk.yellow(
641
- `\n[ApprovalGate] ${riskLevel || "medium"} risk command:${cmd}\n` +
642
- ` Proceed? [y]es once / [a]lways allow / [N]o: `,
643
- ),
644
- _askIdleTimeoutMs,
645
- );
646
- rlConfirm.close();
647
- if (res.timedOut) {
648
- process.stdout.write(
638
+ const promise = (async () => {
639
+ const res = await questionWithIdleTimeout(
640
+ q,
649
641
  chalk.yellow(
650
- `\n ⏱ no response in ${_askIdleTimeoutMs}ms — auto-denied\n`,
642
+ `\n[ApprovalGate] ${riskLevel || "medium"} risk command:${cmd}\n` +
643
+ ` Proceed? [y]es once / [a]lways allow / [N]o: `,
651
644
  ),
645
+ _askIdleTimeoutMs,
652
646
  );
653
- return false;
654
- }
655
- const ans = res.answer.trim().toLowerCase();
656
- if (ans === "a" || ans === "always") {
657
- const saved = await _persistAlwaysAllow(tool || "run_shell", args);
658
- if (saved) {
647
+ rlConfirm.close();
648
+ if (canceled) return false; // raced out — the device already decided
649
+ if (res.timedOut) {
659
650
  process.stdout.write(
660
- chalk.green(
661
- ` ✓ always allow: added ${saved.rule} → ${saved.file}\n`,
651
+ chalk.yellow(
652
+ `\n ⏱ no response in ${_askIdleTimeoutMs}ms — auto-denied\n`,
662
653
  ),
663
654
  );
655
+ return false;
656
+ }
657
+ const ans = res.answer.trim().toLowerCase();
658
+ if (ans === "a" || ans === "always") {
659
+ const saved = await _persistAlwaysAllow(tool || "run_shell", args);
660
+ if (saved) {
661
+ process.stdout.write(
662
+ chalk.green(
663
+ ` ✓ always allow: added ${saved.rule} → ${saved.file}\n`,
664
+ ),
665
+ );
666
+ }
667
+ return true;
664
668
  }
665
- return true;
669
+ return ans === "y" || ans === "yes";
670
+ })();
671
+ return {
672
+ promise,
673
+ cancel: () => {
674
+ canceled = true;
675
+ rlConfirm.close();
676
+ },
677
+ };
678
+ };
679
+ _approvalGate.setConfirmer(async ({ tool, args, riskLevel }) => {
680
+ if (_dontAskActive()) {
681
+ process.stdout.write(
682
+ chalk.yellow(
683
+ `\n ✕ denied without asking (dontAsk mode): ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}\n`,
684
+ ),
685
+ );
686
+ return false;
666
687
  }
667
- return ans === "y" || ans === "yes";
688
+ await _fireNotification(
689
+ `Permission needed: ${riskLevel || "medium"}-risk ${tool || "run_shell"}${args?.command ? " — " + args.command : ""}`,
690
+ );
691
+ const local = askGateApprovalLocally({ tool, args, riskLevel });
692
+ if (!_remoteApproval?.bridge) return local.promise;
693
+ return raceLocalAndRemote({
694
+ bridge: _remoteApproval.bridge,
695
+ ask: describeAskContext({ tool, args, riskLevel }),
696
+ local,
697
+ writeOut: (text) => process.stdout.write(chalk.cyan(text)),
698
+ });
668
699
  });
669
700
  }
670
701
  } catch (_err) {
@@ -693,45 +724,68 @@ export async function startAgentRepl(options = {}) {
693
724
  loaded.rules.ask.length +
694
725
  loaded.rules.deny.length;
695
726
  _permissionRules = total > 0 ? loaded.rules : null;
696
- // Confirmer is shared by permission `ask` rules AND hook `ask` decisions,
697
- // so define it unconditionally (a `hook:` rule label flows through too).
698
- _permissionConfirm = async ({ tool, args, rule, reason }) => {
699
- if (_dontAskActive()) {
700
- process.stdout.write(
701
- chalk.yellow(
702
- `\n ✕ denied without asking (dontAsk mode): ${tool}${rule ? ` (${rule})` : reason ? ` — ${reason}` : ""}\n`,
703
- ),
704
- );
705
- return false;
706
- }
707
- await _fireNotification(
708
- `Permission needed: ${tool}${rule ? " (" + rule + ")" : ""}`,
709
- );
727
+ // Local prompt as a cancelable handle (same shape as the gate confirmer
728
+ // above) so /remote-control can race it against a paired device.
729
+ const askPermissionLocally = ({ tool, args, rule, reason }) => {
710
730
  const rl = readline.createInterface({
711
731
  input: process.stdin,
712
732
  output: process.stdout,
713
733
  });
734
+ let canceled = false;
714
735
  const q = (p) => new Promise((res) => rl.question(p, res));
715
736
  // Picks the right phrasing whether the caller passed a `rule`
716
737
  // (settings/hook ask) or a `reason` (destructive-git / sensitive-file
717
738
  // guards) — avoids the literal "null" the old template printed.
718
739
  const header = buildPermissionPrompt({ tool, args, rule, reason });
719
- const res = await questionWithIdleTimeout(
720
- q,
721
- chalk.yellow(`\n${header}\n Proceed? (y/N) `),
722
- _askIdleTimeoutMs,
723
- );
724
- rl.close();
725
- if (res.timedOut) {
740
+ const promise = (async () => {
741
+ const res = await questionWithIdleTimeout(
742
+ q,
743
+ chalk.yellow(`\n${header}\n Proceed? (y/N) `),
744
+ _askIdleTimeoutMs,
745
+ );
746
+ rl.close();
747
+ if (canceled) return false; // raced out — the device already decided
748
+ if (res.timedOut) {
749
+ process.stdout.write(
750
+ chalk.yellow(
751
+ `\n ⏱ no response in ${_askIdleTimeoutMs}ms — auto-denied\n`,
752
+ ),
753
+ );
754
+ return false;
755
+ }
756
+ const ans = res.answer.trim().toLowerCase();
757
+ return ans === "y" || ans === "yes";
758
+ })();
759
+ return {
760
+ promise,
761
+ cancel: () => {
762
+ canceled = true;
763
+ rl.close();
764
+ },
765
+ };
766
+ };
767
+ // Confirmer is shared by permission `ask` rules AND hook `ask` decisions,
768
+ // so define it unconditionally (a `hook:` rule label flows through too).
769
+ _permissionConfirm = async ({ tool, args, rule, reason }) => {
770
+ if (_dontAskActive()) {
726
771
  process.stdout.write(
727
772
  chalk.yellow(
728
- `\n ⏱ no response in ${_askIdleTimeoutMs}ms — auto-denied\n`,
773
+ `\n ✕ denied without asking (dontAsk mode): ${tool}${rule ? ` (${rule})` : reason ? ` — ${reason}` : ""}\n`,
729
774
  ),
730
775
  );
731
776
  return false;
732
777
  }
733
- const ans = res.answer.trim().toLowerCase();
734
- return ans === "y" || ans === "yes";
778
+ await _fireNotification(
779
+ `Permission needed: ${tool}${rule ? " (" + rule + ")" : ""}`,
780
+ );
781
+ const local = askPermissionLocally({ tool, args, rule, reason });
782
+ if (!_remoteApproval?.bridge) return local.promise;
783
+ return raceLocalAndRemote({
784
+ bridge: _remoteApproval.bridge,
785
+ ask: describeAskContext({ tool, args, rule, reason }),
786
+ local,
787
+ writeOut: (text) => process.stdout.write(chalk.cyan(text)),
788
+ });
735
789
  };
736
790
  } catch (_err) {
737
791
  if (_err?.code === "CC_MANAGED_SETTINGS_INVALID") throw _err;
@@ -900,6 +954,60 @@ export async function startAgentRepl(options = {}) {
900
954
  // Non-critical — SessionManager integration must not block startup
901
955
  }
902
956
 
957
+ // `/remote-control` start/stop (批17 slash 入口 + 批18 REPL 接线): reuses
958
+ // the headless assembly (self-hosted WS on an OS-assigned port + a
959
+ // RemoteApprovalBridge for this session); the interactive difference is
960
+ // purely how the confirmers consume it — they RACE the terminal prompt
961
+ // against the paired device instead of gating on the remote alone.
962
+ const _startRemoteApproval = async () => {
963
+ const { startHeadlessRemoteApproval } =
964
+ await import("../lib/remote-approval-bridge.js");
965
+ const started = await startHeadlessRemoteApproval({
966
+ agentSessionId:
967
+ sessionId || `repl-${process.pid}-${Date.now().toString(36)}`,
968
+ isText: false, // the REPL prints its own pairing lines below
969
+ });
970
+ _remoteApproval = {
971
+ bridge: started.bridge,
972
+ pairing: started.pairing,
973
+ close: started.close,
974
+ };
975
+ logger.log(
976
+ chalk.cyan(
977
+ " remote-control: permission prompts can be answered from a paired device (first answer — terminal or device — wins)",
978
+ ),
979
+ );
980
+ logger.log(` pairing: ${started.pairing.uri}`);
981
+ try {
982
+ const { renderQrCode } = await import("../lib/remote-control.js");
983
+ const qr = await renderQrCode(started.pairing.uri);
984
+ if (qr) logger.log(qr);
985
+ } catch (_err) {
986
+ // QR is progressive enhancement — the URI line is the contract
987
+ }
988
+ };
989
+ const _stopRemoteApproval = async () => {
990
+ const active = _remoteApproval;
991
+ _remoteApproval = null; // confirmers fall back to local-only immediately
992
+ if (active) await active.close().catch(() => undefined);
993
+ };
994
+
995
+ // --remote-control also applies to interactive sessions. Headless fails
996
+ // closed when the bridge can't start (approvals would silently fall
997
+ // closed); interactively the terminal still prompts, so a start failure
998
+ // degrades to local-only with a warning instead of aborting the session.
999
+ if (options.remoteControl === true) {
1000
+ try {
1001
+ await _startRemoteApproval();
1002
+ } catch (err) {
1003
+ logger.log(
1004
+ chalk.yellow(
1005
+ ` remote-control unavailable (${err.message}) — approvals stay local-only`,
1006
+ ),
1007
+ );
1008
+ }
1009
+ }
1010
+
903
1011
  // --system-prompt replaces the built-in prompt; --append-system-prompt
904
1012
  // extends it (parity with the headless runners). The base is kept so an
905
1013
  // output-style persona can be swapped in/out at runtime via /output-style.
@@ -1262,6 +1370,24 @@ export async function startAgentRepl(options = {}) {
1262
1370
  options.vimMode === true || process.env.CC_VIM === "1" ? true : false;
1263
1371
  let _vim = null;
1264
1372
 
1373
+ // --disable-slash-commands (Claude-Code parity): built-in slash dispatch,
1374
+ // custom command macros and MCP prompt expansion are all bypassed — a
1375
+ // "/"-leading line reaches the model verbatim, the same fall-through an
1376
+ // unmatched slash line already takes. /exit and /quit stay live so the
1377
+ // session remains closable from the keyboard.
1378
+ const _slashCommandsDisabled =
1379
+ options.disableSlashCommands === true ||
1380
+ /^(1|true|yes|on)$/i.test(
1381
+ String(process.env.CC_DISABLE_SLASH_COMMANDS || "").trim(),
1382
+ );
1383
+
1384
+ // --ax-screen-reader / CC_SCREEN_READER: force the mono theme (no ANSI
1385
+ // color codes) so output linearizes cleanly. The repainting status line is
1386
+ // already killed by the CC_STATUSLINE=0 switch agent.js applies.
1387
+ const _screenReaderMode = /^(1|true|yes|on)$/i.test(
1388
+ String(process.env.CC_SCREEN_READER || "").trim(),
1389
+ );
1390
+
1265
1391
  // Color theme (Claude-Code `/theme` parity). Capture chalk's auto-detected
1266
1392
  // level BEFORE any theme touches it so `mono`→level 0 stays reversible, then
1267
1393
  // apply the persisted theme (config `cli.theme`). `mono` strips all color;
@@ -1282,6 +1408,10 @@ export async function startAgentRepl(options = {}) {
1282
1408
  } catch (_e) {
1283
1409
  /* config optional — keep default */
1284
1410
  }
1411
+ // Screen-reader mode overrides the persisted theme: colors are invisible to
1412
+ // a reader and ANSI codes are noise, so run mono for this session (the
1413
+ // stored cli.theme is untouched — /theme still shows and can change it).
1414
+ if (_screenReaderMode) _theme = "mono";
1285
1415
  applyThemeChalk(_theme, chalk, _chalkBaselineLevel);
1286
1416
  const themedPrompt = (text) => {
1287
1417
  const a = promptAccent(_theme);
@@ -1316,65 +1446,70 @@ export async function startAgentRepl(options = {}) {
1316
1446
  // cwd left unset on purpose: the completer resolves process.cwd() lazily
1317
1447
  // so it follows `/cd` mid-session.
1318
1448
  // Keep in sync with the rl.on("line") handlers + /help below.
1319
- slashCommands: [
1320
- "/add-dir",
1321
- "/agents",
1322
- "/auto",
1323
- "/btw",
1324
- "/cd",
1325
- "/clear",
1326
- "/compact",
1327
- "/config",
1328
- "/context",
1329
- "/copy",
1330
- "/cost",
1331
- "/cowork",
1332
- "/doctor",
1333
- "/effort",
1334
- "/exit",
1335
- "/export",
1336
- "/help",
1337
- "/hooks",
1338
- "/ide",
1339
- "/init",
1340
- "/mcp",
1341
- "/memory",
1342
- "/microcompact",
1343
- "/model",
1344
- "/output-style",
1345
- "/permissions",
1346
- "/plan",
1347
- "/pr-comments",
1348
- "/profile",
1349
- "/provider",
1350
- "/quit",
1351
- "/reindex",
1352
- "/release-notes",
1353
- "/reload-plugins",
1354
- "/reload-skills",
1355
- "/review",
1356
- "/rewind",
1357
- "/search",
1358
- "/session",
1359
- "/sessions",
1360
- "/stats",
1361
- "/status",
1362
- "/statusline",
1363
- "/sub-agents",
1364
- "/task",
1365
- "/tasks",
1366
- "/terminal-setup",
1367
- "/theme",
1368
- "/think",
1369
- "/todos",
1370
- "/ultrathink",
1371
- "/vim",
1372
- ],
1449
+ // --disable-slash-commands: nothing to complete — "/" input is plain text.
1450
+ slashCommands: _slashCommandsDisabled
1451
+ ? []
1452
+ : [
1453
+ "/add-dir",
1454
+ "/agents",
1455
+ "/auto",
1456
+ "/btw",
1457
+ "/cd",
1458
+ "/clear",
1459
+ "/compact",
1460
+ "/config",
1461
+ "/context",
1462
+ "/copy",
1463
+ "/cost",
1464
+ "/cowork",
1465
+ "/doctor",
1466
+ "/effort",
1467
+ "/exit",
1468
+ "/export",
1469
+ "/help",
1470
+ "/hooks",
1471
+ "/ide",
1472
+ "/init",
1473
+ "/mcp",
1474
+ "/memory",
1475
+ "/microcompact",
1476
+ "/model",
1477
+ "/output-style",
1478
+ "/permissions",
1479
+ "/plan",
1480
+ "/pr-comments",
1481
+ "/profile",
1482
+ "/provider",
1483
+ "/quit",
1484
+ "/reindex",
1485
+ "/release-notes",
1486
+ "/reload-plugins",
1487
+ "/reload-skills",
1488
+ "/remote-control",
1489
+ "/review",
1490
+ "/rewind",
1491
+ "/search",
1492
+ "/session",
1493
+ "/sessions",
1494
+ "/stats",
1495
+ "/status",
1496
+ "/statusline",
1497
+ "/sub-agents",
1498
+ "/task",
1499
+ "/tasks",
1500
+ "/terminal-setup",
1501
+ "/theme",
1502
+ "/think",
1503
+ "/todos",
1504
+ "/ultrathink",
1505
+ "/vim",
1506
+ ],
1373
1507
  // User/project custom commands (.claude/commands/*.md) join TAB completion
1374
1508
  // alongside the built-ins above. Sync + best-effort; the completer
1375
1509
  // TTL-caches the result so this filesystem walk runs at most once per few
1376
1510
  // seconds, and process.cwd() makes it follow `/cd` mid-session.
1377
1511
  getDynamicSlashCommands: () => {
1512
+ if (_slashCommandsDisabled) return [];
1378
1513
  try {
1379
1514
  return discoverCommands(process.cwd()).map((cmd) => `/${cmd.name}`);
1380
1515
  } catch {
@@ -1719,7 +1854,20 @@ export async function startAgentRepl(options = {}) {
1719
1854
  _mlBuffer.length = 0;
1720
1855
  }
1721
1856
 
1722
- const trimmed = input.trim();
1857
+ const rawLine = input.trim();
1858
+ // --disable-slash-commands: hide "/"-leading input from every built-in
1859
+ // slash handler below by dispatching a sentinel no handler can match; the
1860
+ // turn tail restores the user's real text (promptText), so the line goes
1861
+ // to the model verbatim — the same fall-through an unmatched slash line
1862
+ // already takes. /exit and /quit stay live so the session can be closed.
1863
+ // Predicate + sentinel are pure (lib/slash-dispatch.js, unit-tested).
1864
+ const { slashDispatchBypassed, slashBypassSentinel } =
1865
+ await import("../lib/slash-dispatch.js");
1866
+ const _slashBypassed = slashDispatchBypassed(
1867
+ rawLine,
1868
+ _slashCommandsDisabled,
1869
+ );
1870
+ const trimmed = _slashBypassed ? slashBypassSentinel() : rawLine;
1723
1871
  if (!trimmed) {
1724
1872
  prompt();
1725
1873
  return;
@@ -1870,6 +2018,9 @@ export async function startAgentRepl(options = {}) {
1870
2018
  logger.log(
1871
2019
  ` ${chalk.cyan("/permissions")} Allow/ask/deny rules; set/cycle tier (/permissions <tier> · Shift+Tab); /permissions denials to review blocked calls`,
1872
2020
  );
2021
+ logger.log(
2022
+ ` ${chalk.cyan("/remote-control")} Answer permission prompts from a paired phone/web device (on|off|status; alias /rc)`,
2023
+ );
1873
2024
  logger.log(
1874
2025
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
1875
2026
  );
@@ -3989,6 +4140,57 @@ export async function startAgentRepl(options = {}) {
3989
4140
  return;
3990
4141
  }
3991
4142
 
4143
+ // `/remote-control` — pair a phone/web device to answer this session's
4144
+ // permission prompts (批17 slash 入口 + 批18 REPL 接线). First answer —
4145
+ // terminal or device — wins; devices get permission.request cards + push.
4146
+ if (
4147
+ trimmed === "/remote-control" ||
4148
+ trimmed.startsWith("/remote-control ") ||
4149
+ trimmed === "/rc" ||
4150
+ trimmed.startsWith("/rc ")
4151
+ ) {
4152
+ const arg = trimmed
4153
+ .replace(/^\/(remote-control|rc)/, "")
4154
+ .trim()
4155
+ .toLowerCase();
4156
+ if (arg === "off" || arg === "stop") {
4157
+ if (!_remoteApproval) {
4158
+ logger.info("remote-control is not active.");
4159
+ } else {
4160
+ await _stopRemoteApproval();
4161
+ logger.info(
4162
+ "remote-control stopped — approvals are local-only again.",
4163
+ );
4164
+ }
4165
+ } else if (arg === "status") {
4166
+ if (!_remoteApproval) {
4167
+ logger.info("remote-control: off — start with /remote-control");
4168
+ } else {
4169
+ logger.log(` pairing: ${_remoteApproval.pairing.uri}`);
4170
+ const approvers = await _remoteApproval.bridge.approverCount();
4171
+ logger.log(
4172
+ ` paired approvers: ${approvers}${approvers === 0 ? " (scan the pairing URI from the mobile app or web panel)" : ""}`,
4173
+ );
4174
+ }
4175
+ } else if (arg === "" || arg === "on" || arg === "start") {
4176
+ if (_remoteApproval) {
4177
+ logger.info(
4178
+ "remote-control already running — /remote-control status",
4179
+ );
4180
+ } else {
4181
+ try {
4182
+ await _startRemoteApproval();
4183
+ } catch (err) {
4184
+ logger.info(`remote-control failed to start: ${err.message}`);
4185
+ }
4186
+ }
4187
+ } else {
4188
+ logger.info("Usage: /remote-control [on|off|status] (alias /rc)");
4189
+ }
4190
+ prompt();
4191
+ return;
4192
+ }
4193
+
3992
4194
  // `/cost` — running token spend + estimated $ for this session (Claude-Code
3993
4195
  // parity). In-memory accumulation, so it works without session persistence.
3994
4196
  if (trimmed === "/cost" || trimmed.startsWith("/cost ")) {
@@ -4086,13 +4288,15 @@ export async function startAgentRepl(options = {}) {
4086
4288
  // parity. resolveSlashMacro maps a leading /name to a command macro and
4087
4289
  // expands its template; a non-match returns the line unchanged so a literal
4088
4290
  // prompt like "/etc/hosts" still reaches the LLM. Wire is unit-tested.
4089
- let promptText = trimmed;
4291
+ // Under --disable-slash-commands the sentinel never reached a handler;
4292
+ // restore the user's real text so the model sees it verbatim.
4293
+ let promptText = _slashBypassed ? rawLine : trimmed;
4090
4294
 
4091
4295
  // MCP server-provided prompts (Claude-Code parity): `/mcp__<server>__<name>
4092
4296
  // [json-args]` fetches a rendered prompt template from the connected MCP
4093
4297
  // server and uses its text as this turn's input. Falls through unchanged
4094
4298
  // when the line isn't an MCP prompt command.
4095
- if (promptText.startsWith("/mcp__")) {
4299
+ if (!_slashBypassed && promptText.startsWith("/mcp__")) {
4096
4300
  try {
4097
4301
  const expanded = await expandMcpPrompt(
4098
4302
  promptText,
@@ -4114,7 +4318,7 @@ export async function startAgentRepl(options = {}) {
4114
4318
  // `/pr-comments [<n>|<url>] [--repo owner/name]` (Claude-Code parity):
4115
4319
  // fetch a GitHub PR's reviews + conversation + inline comments via `gh` and
4116
4320
  // feed them as this turn's input so the agent can address the feedback.
4117
- if (promptText.startsWith("/pr-comments")) {
4321
+ if (!_slashBypassed && promptText.startsWith("/pr-comments")) {
4118
4322
  try {
4119
4323
  const { expandPrComments } = await import("./pr-comments.js");
4120
4324
  const res = await expandPrComments(promptText);
@@ -4927,6 +5131,16 @@ export async function startAgentRepl(options = {}) {
4927
5131
  }
4928
5132
  }
4929
5133
 
5134
+ // Tear down /remote-control (bridge + its self-hosted WS server) so the
5135
+ // pairing endpoint doesn't outlive the session.
5136
+ if (_remoteApproval) {
5137
+ try {
5138
+ await _stopRemoteApproval();
5139
+ } catch (_e) {
5140
+ // Non-critical
5141
+ }
5142
+ }
5143
+
4930
5144
  // Kill any background run_shell tasks so a backgrounded command (e.g. a
4931
5145
  // dev server) doesn't outlive the REPL session.
4932
5146
  try {