chainlesschain 0.162.157 → 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 (173) 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-CfMP2Oud.js → AIOps-BwpTJTN_.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-Zu0N8BQL.js → ActionButton-BtQOoo3l.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DZTwj5T_.js → Analytics-Duf_rXZa.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CVFJh47v.js → AppLayout-XQK3wjvB.js} +5 -5
  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-BMfiLj7T.js → Audit-DNjOGQLh.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-BslPCdGF.js → BackgroundAgents-FH9RAfB5.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-CQ_fMC1r.js → Backup-ZIbrR4-U.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-CsFsARiC.js → BaseInput-DSt3NaND.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-CwfBobco.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-K6Nf3xeN.js → ChatBubbleRenderer-BAV9V-t-.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-DkSyjjPp.js → Checkbox-8JVadE_B.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-B7Fx3zZ6.js → Codegen-CN83nPYI.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-Dzfqn8Ep.js → Col-xPSvqdN8.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DhHPdzMM.js → Community-D78cnIjG.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-Ct18_8y7.js → Compact-CSw6Csbp.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CY64IqBi.js → Compliance-E_HhjcYR.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-BYSmWlUl.js → Cowork-DybeQjmm.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-_4xtP3Ug.js → Cron-DS0BJeLY.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-B4qY-Ycr.js → Crosschain-BOwVgkbs.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-CkCzWkx1.js → DID-DRUr9YuX.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-C1scT7Ke.js → Dashboard-Cht-yUKW.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-DBnQ2EWd.js → Dropdown-BLr_EJ9X.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-C9JhOTq0.js → EmailListRenderer-Drexuh5W.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CWXv5RUv.js → FamilyGuardDashboard-D9Q2Kq63.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-fYdcbz9W.js → Federation-C-Z1xI-T.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-CJqOT3Yj.js → FormItemContext-DLHPMkQF.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-DGCsHc5K.js → GenericCardRenderer-DxxEdn2r.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-EH2K-H0Q.js → Git-syATcaBt.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-DJn1X2pR.js → Governance-CYe_r8Zf.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-Dch1E8qd.js → Inference-GWxErB7k.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-QIur8j8-.js → KnowledgeGraph-qQvVCZoi.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-DUgHldPg.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-DafrSfvd.js → Marketplace-D4Jn5fpD.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-D2xRbfJx.js → McpTools-DtAwoqiQ.js} +4 -4
  43. package/src/assets/web-panel/assets/{Memory-C6RbVHAo.js → Memory-BI_O50I8.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-BPtVBKqn.js → MobileBridge-CQWE2lqx.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-Cuw832cr.js → MobileProjects-CVtTqgtg.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-D6O9fWFa.js → Mtc-Dnr16qsn.js} +4 -4
  47. package/src/assets/web-panel/assets/{MtcAudit-CWZcXIH1.js → MtcAudit-CQ5Xe4-a.js} +4 -4
  48. package/src/assets/web-panel/assets/{Multisig-DhIBxTFd.js → Multisig-Bjr8O-wy.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-wkBFS33e.js → NLProgramming-CEe-KirU.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-B09FxncA.js → Notes-spKJIKF9.js} +4 -4
  51. package/src/assets/web-panel/assets/{NotificationSettings-oQwtH6-9.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-CsF-NIjz.js → Organization-BlgB0IYg.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-bLxv9Dqi.js → Overflow-a_35HqZI.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-DVwQtm4q.js → P2P-CSNHCNPC.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-Bgic3QKm.js → PdhVaultBrowser-C8M1JH-1.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-as80-9ep.js → Permissions-DmjJi9Ms.js} +3 -3
  58. package/src/assets/web-panel/assets/{PersonalDataHub-BGmUw36a.js → PersonalDataHub-DfuwRyf4.js} +2 -2
  59. package/src/assets/web-panel/assets/{Pipeline-BM96zBxG.js → Pipeline-B2-S4ekE.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-BBOzBSWe.js → Privacy-B1dOiBLC.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-GHX41V28.js → ProjectInit-_pNmc-DX.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-DraqzxKD.js → ProjectSettings-DmwG2Ii1.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-BzzdUml4.js → Projects-COATmHxF.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-CSeYNAtW.js → Providers-C6HSjg1d.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-DlzBCdXh.js → QrScannerModal-CrDmS7l1.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-p8iB7od_.js → QuickAsk-CfKvWt-x.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-DjOVXacd.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-CjT3M0Di.js → Reputation-BTPQ1vjL.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-tw8m6T0c.js → Row-B6Q7QkMG.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-B47KBx_f.js → RssFeed-B1IJhAmX.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-D2sx1PBe.js → Search-xbea1kGD.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-JmtL8g7D.js → Security-7rs0cO3F.js} +4 -4
  74. package/src/assets/web-panel/assets/{Services-BxrNqjin.js → Services-0fjixK0S.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-udk8L18t.js → Skeleton-B_rVq9Cu.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-DooLT9of.js → Skills-C5uM90b9.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-ITVZE1xI.js → Sla-U4pYCIZH.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-DCkfqY1n.js → SpeechSettings-Cdz4yTWx.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-CkNlgFjM.js → SyncSettings-Me3aetHv.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-BMAoyCM_.js → Tasks-Cf6aNkL_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-BnrFTlrz.js → Templates-CYatk4tU.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-BjNsy3-i.js → Tenant-DIMKLw7o.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-C0gxLL7V.js → Terminal-C_cp-uSJ.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-CUtj_KHA.js → TimelineRenderer-cUPVNADp.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-DOg61zlq.js → Tokens-CiaEIXVf.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-D4ldCewj.js → Trigger-DB1WU5lQ.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-3lk_Zd8j.js → Trust-Cfht9Ijy.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-kWhFR6Li.js → UkeySign-K3P4Vt9S.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-D3GOFVWg.js → VideoEditing-DWjhjXRh.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-DzrZfBuh.js → Wallet-D9-Q1Kan.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-CyVJVp_g.js → WebAuthn-BDszy-oV.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-i_b0wi9b.js → WorkflowEditor-DIhij4rA.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-DwPCK8I7.js → chat-D7VCiKbh.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-D0vaVqZU.js → colors-BvOd9pVn.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-Don7nGW2.js → compact-item-DoqXBiAO.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-D84R5qy2.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-BeUqVVKU.js → hasIn-BWAev4WC.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BohrJpBf.js → index-1poMpMU0.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-YKLCotoo.js → index-8g5TJCZ3.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-D8r7WKk-.js → index-B0vGEbJT.js} +1 -1
  102. package/src/assets/web-panel/assets/index-B1_pRTtB.js +1 -0
  103. package/src/assets/web-panel/assets/{index-m9bn0F1p.js → index-B30NvR21.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-7QUwcGTg.js → index-BCyP071m.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-4MkfPLGv.js → index-BHOcVYrx.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BZ1N07Sp.js → index-BN9m4Ux2.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DFFzwrNs.js → index-BWWsaimP.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-LMo9bBLJ.js → index-BXyl8O23.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BlfTf0ka.js → index-BZGk8AbZ.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CnnxzIx0.js → index-Bf9YlbDn.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CAdCTTBC.js → index-Bj7EgmOp.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B57al0Z5.js → index-BkgnGX7v.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CzT56x-_.js → index-BoYrS_L2.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-L73NrmlW.js → index-Bq_ctRpz.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CL71rIwo.js → index-C5kRmj3P.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-WRw1pM5E.js → index-CDGLdn12.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-hjgrIX5I.js → index-CEPHMSih.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-IGGttsCv.js → index-CEXfKSiI.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D84BnuFg.js → index-CSb-tnIo.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-x7VYlV41.js → index-CmJ6xKBO.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DQor_7NF.js → index-CoD31Ndm.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CfYRv-zA.js → index-Cx7M7PSF.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DBzBLX-K.js → index-D23L1V76.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DMT8n9vB.js → index-DV-Rfmfx.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-BER98o4P.js → index-DlGu7As6.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CrnVEEQO.js → index-DvDOpsJB.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D4b5XqIy.js → index-HwHdTusY.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Br3SOdER.js → index-ISvPZ05r.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-MKZrJrP_.js → index-JzUmJMvn.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DT40zd7K.js → index-KR1-Em9C.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C0_sKVi7.js → index-PDxGjKRu.js} +1 -1
  132. package/src/assets/web-panel/assets/index-TMM9FuW-.js +1 -0
  133. package/src/assets/web-panel/assets/{index-wesQQ2cH.js → index-YGDy7nNq.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DA0-EJKx.js → index-gmxqu5Kk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-BNpJf-sf.js → index-oY45XrkG.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CExWtm98.js → index-ww1QtLnX.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-De85D9Pc.js → index-zH-tuuGZ.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-DGeU4vPS.js → initDefaultProps-XW-OR3mL.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-Dgqj9ab3.js → motion-CeuEHh20.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-BFyEU6Lr.js → move-Dva79Keg.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-CueCXXPC.js → mtc-parser-C0B6bD2j.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-D2pU5OyE.js → omit-AkZvO-a2.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-CKTf6zD6.js → pickAttrs-B4r55qGc.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-DieJaGCN.js → placementArrow-BLH-z7YE.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-Bxr-rmU6.js → responsiveObserve-ksEGZ2Wf.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-CDEDk3Se.js → slide-Cth4OD1Z.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-icf2eCTx.js → statusUtils-9S83ezxw.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-qKvgmBhE.js → styleChecker-7yQmQGZ4.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-1_UhiyJZ.js → useFlexGapSupport-DpGnMmPc.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-CFGLXDWh.js → useFs-C4zDZIXH.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-UafOXvQ6.js → usePersonalDataHub-CoLICdUZ.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-BfPqSx79.js → vnode-CKyH-35X.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-CXsVcRB6.js → zoom-DRDGit17.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/commands/agent.js +4 -1
  156. package/src/data/changelog.json +7 -2
  157. package/src/lib/chrome-connector.js +39 -4
  158. package/src/lib/remote-control.js +35 -0
  159. package/src/lib/web-ui-server.js +120 -7
  160. package/src/repl/agent-repl.js +231 -58
  161. package/src/repl/remote-approval.js +111 -0
  162. package/src/runtime/agent-core.js +36 -0
  163. package/src/runtime/coding-agent-contract-shared.cjs +53 -0
  164. package/src/runtime/coding-agent-policy.cjs +16 -0
  165. package/src/runtime/policies/agent-policy.js +3 -0
  166. package/src/assets/web-panel/assets/Artifacts-BbN5V7au.js +0 -1
  167. package/src/assets/web-panel/assets/Artifacts-CRkepl4t.css +0 -1
  168. package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
  169. package/src/assets/web-panel/assets/OrderTableRenderer-iFKeNf6Y.js +0 -1
  170. package/src/assets/web-panel/assets/RemoteSession-CIGk7gk-.js +0 -4
  171. package/src/assets/web-panel/assets/devWarning-BBwWehQ7.js +0 -1
  172. package/src/assets/web-panel/assets/index-DJ8etkBT.js +0 -1
  173. package/src/assets/web-panel/assets/index-OeaK-zXZ.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;
664
656
  }
665
- return true;
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;
668
+ }
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.
@@ -1377,6 +1485,7 @@ export async function startAgentRepl(options = {}) {
1377
1485
  "/release-notes",
1378
1486
  "/reload-plugins",
1379
1487
  "/reload-skills",
1488
+ "/remote-control",
1380
1489
  "/review",
1381
1490
  "/rewind",
1382
1491
  "/search",
@@ -1909,6 +2018,9 @@ export async function startAgentRepl(options = {}) {
1909
2018
  logger.log(
1910
2019
  ` ${chalk.cyan("/permissions")} Allow/ask/deny rules; set/cycle tier (/permissions <tier> · Shift+Tab); /permissions denials to review blocked calls`,
1911
2020
  );
2021
+ logger.log(
2022
+ ` ${chalk.cyan("/remote-control")} Answer permission prompts from a paired phone/web device (on|off|status; alias /rc)`,
2023
+ );
1912
2024
  logger.log(
1913
2025
  ` ${chalk.cyan("/export")} Save this conversation to a Markdown file (/export [path])`,
1914
2026
  );
@@ -4028,6 +4140,57 @@ export async function startAgentRepl(options = {}) {
4028
4140
  return;
4029
4141
  }
4030
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
+
4031
4194
  // `/cost` — running token spend + estimated $ for this session (Claude-Code
4032
4195
  // parity). In-memory accumulation, so it works without session persistence.
4033
4196
  if (trimmed === "/cost" || trimmed.startsWith("/cost ")) {
@@ -4968,6 +5131,16 @@ export async function startAgentRepl(options = {}) {
4968
5131
  }
4969
5132
  }
4970
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
+
4971
5144
  // Kill any background run_shell tasks so a backgrounded command (e.g. a
4972
5145
  // dev server) doesn't outlive the REPL session.
4973
5146
  try {
@@ -0,0 +1,111 @@
1
+ /**
2
+ * REPL-side remote-approval glue (gap-analysis 批26 — 批17/18 REPL 遗留收口).
3
+ *
4
+ * Interactive sessions race the LOCAL terminal prompt against a paired
5
+ * device's decision (headless gates on the remote decision alone):
6
+ *
7
+ * - local answer first → settle the device card too (resolveLocally
8
+ * publishes permission.resolved so the phone/web UI clears it)
9
+ * - remote answer first → cancel the local readline prompt and print
10
+ * the outcome
11
+ * - remote leg times out or the bridge closes mid-ask → NOT a decision:
12
+ * keep waiting on the terminal. A silent phone must never auto-deny a
13
+ * user sitting at the keyboard — the fail-closed timeout is headless
14
+ * semantics only.
15
+ *
16
+ * Pure over an injected bridge + local prompt handle ({promise, cancel}),
17
+ * so the race is unit-testable without a WS server or readline.
18
+ */
19
+
20
+ /**
21
+ * Normalize the shapes the REPL's two confirmers receive (ApprovalGate
22
+ * `{tool, args, riskLevel}` / settings-hook `{tool, args, rule, reason}`)
23
+ * into the bridge's `{tool, action, detail}` ask payload.
24
+ */
25
+ export function describeAskContext({
26
+ tool,
27
+ args,
28
+ rule = null,
29
+ reason = null,
30
+ riskLevel = null,
31
+ } = {}) {
32
+ const detail =
33
+ typeof args?.command === "string"
34
+ ? args.command
35
+ : typeof args?.path === "string"
36
+ ? args.path
37
+ : args
38
+ ? JSON.stringify(args).slice(0, 2000)
39
+ : null;
40
+ return {
41
+ tool: tool || null,
42
+ action: rule
43
+ ? `ask-rule:${rule}`
44
+ : reason || (riskLevel ? `${riskLevel}-risk` : null),
45
+ detail,
46
+ };
47
+ }
48
+
49
+ // Remote outcomes that are NOT a decision for an interactive session: the
50
+ // devices went silent (timeout) or the bridge was torn down mid-ask (closed).
51
+ const NON_DECISIVE_REMOTE = new Set(["timeout", "closed"]);
52
+
53
+ /**
54
+ * Race the local interactive prompt against the paired-device decision.
55
+ *
56
+ * @param {object} opts
57
+ * @param {import("../lib/remote-approval-bridge.js").RemoteApprovalBridge} opts.bridge
58
+ * @param {{tool?, action?, detail?}} opts.ask - describeAskContext() payload
59
+ * @param {{promise: Promise<boolean>, cancel?: () => void}} opts.local -
60
+ * the already-started local prompt; `cancel` closes its readline when the
61
+ * device answers first (implementations must mute their idle-timeout
62
+ * message after cancel).
63
+ * @param {(text: string) => void} [opts.writeOut]
64
+ * @returns {Promise<boolean>} approved
65
+ */
66
+ export async function raceLocalAndRemote({
67
+ bridge,
68
+ ask = {},
69
+ local,
70
+ writeOut = () => {},
71
+ }) {
72
+ let requestId = null;
73
+ const remoteLeg = bridge
74
+ .requestDecision({
75
+ ...ask,
76
+ onRequestId: (id) => {
77
+ requestId = id;
78
+ },
79
+ })
80
+ .then((decision) => ({ src: "remote", decision }));
81
+ const localLeg = local.promise.then(
82
+ (approved) => ({ src: "local", approved: approved === true }),
83
+ () => ({ src: "local", approved: false }),
84
+ );
85
+ let winner = await Promise.race([remoteLeg, localLeg]);
86
+ if (
87
+ winner.src === "remote" &&
88
+ NON_DECISIVE_REMOTE.has(winner.decision?.via)
89
+ ) {
90
+ winner = await localLeg;
91
+ }
92
+ if (winner.src === "local") {
93
+ if (requestId) {
94
+ try {
95
+ bridge.resolveLocally(requestId, winner.approved);
96
+ } catch {
97
+ // clearing the device card is best-effort
98
+ }
99
+ }
100
+ return winner.approved;
101
+ }
102
+ try {
103
+ local.cancel?.();
104
+ } catch {
105
+ // readline already closed
106
+ }
107
+ writeOut(
108
+ `\n ✓ ${winner.decision.approved ? "allowed" : "denied"} from paired device\n`,
109
+ );
110
+ return winner.decision.approved === true;
111
+ }
@@ -3187,6 +3187,40 @@ async function executeToolInner(
3187
3187
  }
3188
3188
  }
3189
3189
 
3190
+ case "browser_state": {
3191
+ // P1 #8: first-class Chrome CDP observation — the agent side of the
3192
+ // "locate error → fix → verify" web loop against the user's real,
3193
+ // logged-in browser (`cc browse chrome launch`). DOM is capped harder
3194
+ // than the CLI command's 150k default so a page dump cannot flood the
3195
+ // conversation; screenshots go to a generated temp file (never an
3196
+ // agent-chosen path) to keep the tool strictly read-only.
3197
+ try {
3198
+ const { captureState } = await import("../lib/chrome-connector.js");
3199
+ const screenshotPath = args.screenshot
3200
+ ? path.join(os.tmpdir(), `cc-browser-state-${Date.now()}.png`)
3201
+ : null;
3202
+ const state = await captureState({
3203
+ port: args.port != null ? Number(args.port) : undefined,
3204
+ tab: args.tab != null ? Number(args.tab) : undefined,
3205
+ reload: args.reload === true,
3206
+ watchMs: args.watch_ms != null ? Number(args.watch_ms) : undefined,
3207
+ includeDom: args.include_dom !== false,
3208
+ domCap: args.dom_cap != null ? Number(args.dom_cap) : 40000,
3209
+ screenshotPath,
3210
+ });
3211
+ if (!state.ok) {
3212
+ return attachDescriptor({
3213
+ error: `browser_state failed: ${state.error}`,
3214
+ });
3215
+ }
3216
+ return attachDescriptor(state);
3217
+ } catch (err) {
3218
+ return attachDescriptor({
3219
+ error: `browser_state failed: ${err.message}`,
3220
+ });
3221
+ }
3222
+ }
3223
+
3190
3224
  case "search_files": {
3191
3225
  // An explicit directory scopes the search to one root; otherwise span
3192
3226
  // cwd plus any --add-dir roots so cross-package searches find matches.
@@ -6523,6 +6557,8 @@ export function formatToolArgs(name, args) {
6523
6557
  return `${args.level || "info"}: ${(args.title || "").substring(0, 50)}`;
6524
6558
  case "publish_artifact":
6525
6559
  return `${args.kind || "other"}: ${(args.title || args.path || "").substring(0, 60)}`;
6560
+ case "browser_state":
6561
+ return `tab=${args.tab ?? 0} port=${args.port ?? 9222}${args.reload ? " reload" : ""}`;
6526
6562
  case "schedule":
6527
6563
  return args.action === "cron"
6528
6564
  ? `cron ${args.cron || ""}`.trim()
@@ -1005,6 +1005,59 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
1005
1005
  ],
1006
1006
  },
1007
1007
  },
1008
+ {
1009
+ name: "browser_state",
1010
+ title: "Browser State",
1011
+ kind: "browser",
1012
+ tier: "extension",
1013
+ description:
1014
+ "Capture live state from the user's real Chrome over CDP: current URL/title, open tabs, console messages and failed/error network requests observed during a short watch window, a capped DOM snapshot, and optionally a screenshot (saved to a temp file whose path is returned). Requires a debuggable Chrome — the user starts one with `cc browse chrome launch`. Console/network capture starts at attach time (CDP keeps no history) — set reload=true to observe load-time errors. Use to verify web changes in the user's logged-in browser.",
1015
+ inputSchema: {
1016
+ type: "object",
1017
+ properties: {
1018
+ port: {
1019
+ type: "number",
1020
+ description: "CDP port of the debuggable Chrome (default 9222)",
1021
+ },
1022
+ tab: {
1023
+ type: "number",
1024
+ description: "Tab index to observe (default 0)",
1025
+ },
1026
+ reload: {
1027
+ type: "boolean",
1028
+ description:
1029
+ "Reload the page first to capture load-time console/network (default false)",
1030
+ },
1031
+ watch_ms: {
1032
+ type: "number",
1033
+ description: "Observation window in milliseconds (default 3000)",
1034
+ },
1035
+ include_dom: {
1036
+ type: "boolean",
1037
+ description: "Include a capped HTML snapshot (default true)",
1038
+ },
1039
+ dom_cap: {
1040
+ type: "number",
1041
+ description: "Max HTML characters to return (default 40000)",
1042
+ },
1043
+ screenshot: {
1044
+ type: "boolean",
1045
+ description:
1046
+ "Save a screenshot to a generated temp file and return its path (default false)",
1047
+ },
1048
+ },
1049
+ required: [],
1050
+ },
1051
+ ...TOOL_POLICY_METADATA.browser_state,
1052
+ permissions: {
1053
+ level: "standard",
1054
+ scopes: ["browser:read"],
1055
+ },
1056
+ telemetry: {
1057
+ category: "browser",
1058
+ tags: ["tool:browser_state", "contract:coding-agent", "tier:extension"],
1059
+ },
1060
+ },
1008
1061
  ]);
1009
1062
 
1010
1063
  const CODING_AGENT_MVP_TOOL_NAMES = Object.freeze(
@@ -304,6 +304,22 @@ const TOOL_POLICY_METADATA = Object.freeze({
304
304
  approvalFlow: "auto",
305
305
  isReadOnly: false,
306
306
  },
307
+ // Attaches to the user's own debuggable Chrome over loopback CDP and
308
+ // observes one tab (console/network/DOM/optional screenshot) for a bounded
309
+ // window. Observation only — it never clicks, types, or navigates; the
310
+ // optional reload re-runs the page the user already has open, and the
311
+ // screenshot goes to a generated temp path (never an agent-chosen one), so
312
+ // the tool stays read-only and plan-mode safe.
313
+ browser_state: {
314
+ riskLevel: RISK_LEVELS.LOW,
315
+ category: TOOL_CATEGORIES.READ,
316
+ availableInPlanMode: true,
317
+ planModeBehavior: "allow",
318
+ requiresPlanApproval: false,
319
+ requiresConfirmation: false,
320
+ approvalFlow: "auto",
321
+ isReadOnly: true,
322
+ },
307
323
  });
308
324
 
309
325
  function normalizeGitCommand(command) {
@@ -56,6 +56,9 @@ export function resolveAgentPolicy({
56
56
  pdh: overrides.pdh,
57
57
  outputStyle: overrides.outputStyle,
58
58
  disableSlashCommands: overrides.disableSlashCommands === true,
59
+ // --remote-control also applies interactively: the REPL starts the
60
+ // paired-device approval bridge at startup (批26).
61
+ remoteControl: overrides.remoteControl === true,
59
62
  };
60
63
  }
61
64
 
@@ -1 +0,0 @@
1
- import{E as V,b as z,r as b,I as w,J as y,U as m,c as i,K as r,S as A,V as S,o as F,f as N,P as d,Q as p,R as v,F as O}from"./vendor-Bn1JzzAn.js";import{u as h,_ as M,d as I}from"./index-DMT8n9vB.js";import{R as U}from"./icons-DG5UD-6K.js";const E=V("artifacts",()=>{const x=b([]),n=b(!1),u=b(""),e=b(""),f=b(null),_=b(!1),C=z(()=>[...new Set(x.value.map(a=>a.kind).filter(Boolean))].sort());async function s(){const t=h();n.value=!0;try{const a={type:"artifact-list"};u.value&&(a.kind=u.value),e.value&&(a.session=e.value);const o=await t.sendRaw(a);o&&Array.isArray(o.artifacts)&&(x.value=[...o.artifacts].reverse())}catch{}finally{n.value=!1}}async function l(t){const a=h();_.value=!0,f.value={entry:t,previewable:!1};try{const o=await a.sendRaw({type:"artifact-content",artifactId:t.id});f.value={entry:t,previewable:o?.previewable===!0,encoding:o?.encoding||null,content:o?.content||"",truncated:o?.truncated===!0,reason:o?.reason||null}}catch(o){f.value={entry:t,previewable:!1,reason:o?.message||"preview failed"}}finally{_.value=!1}}function k(){f.value=null}const g=z(()=>{const t=f.value;return!t?.previewable||t.encoding!=="base64"?null:`data:${t.entry?.mime||"image/png"};base64,${t.content}`});async function R(t){const a=h();if(!t)return!1;try{const o=await a.sendRaw({type:"artifact-remove",artifactId:t});return f.value?.entry?.id===t&&(f.value=null),await s(),o?.found===!0}catch{return!1}}async function B(){const t=h();try{const a=await t.sendRaw({type:"artifact-clean"});return await s(),Number(a?.removed)||0}catch{return 0}}function P(t){const a=Number(t)||0;return a<1024?`${a} B`:a<1024*1024?`${(a/1024).toFixed(1)} KB`:`${(a/(1024*1024)).toFixed(1)} MB`}function c(t){switch(t){case"report":return"blue";case"patch":return"purple";case"screenshot":return"cyan";case"log":return"default";case"data":return"green";default:return"default"}}return{artifacts:x,loading:n,kindFilter:u,sessionFilter:e,kinds:C,preview:f,previewLoading:_,previewImageSrc:g,fetchArtifacts:s,openPreview:l,closePreview:k,removeArtifact:R,cleanExpired:B,formatSize:P,kindColor:c}}),J={__name:"Artifacts",setup(x,{expose:n}){n();const u=E(),e=[{title:"标题",key:"title",dataIndex:"title",ellipsis:!0},{title:"类型",key:"kind",dataIndex:"kind",width:110},{title:"MIME",dataIndex:"mime",width:160,ellipsis:!0},{title:"大小",key:"size",dataIndex:"size",width:90},{title:"发布时间",key:"createdAt",dataIndex:"createdAt",width:170},{title:"会话",dataIndex:"sessionId",width:160,ellipsis:!0},{title:"操作",key:"actions",width:150}],f=z(()=>u.kinds.map(l=>({label:l,value:l})));async function _(l){await u.removeArtifact(l)?I.success("已删除"):I.error("删除失败")}async function C(){const l=await u.cleanExpired();I.info(l>0?`清理了 ${l} 个过期交付物`:"没有过期交付物")}F(()=>{u.fetchArtifacts()}),N(()=>{u.closePreview()});const s={store:u,columns:e,kindOptions:f,onRemove:_,onClean:C,computed:z,onBeforeUnmount:N,onMounted:F,get ReloadOutlined(){return U},get message(){return I},get useArtifactsStore(){return E}};return Object.defineProperty(s,"__isScriptSetup",{enumerable:!1,value:!0}),s}},K={class:"page-header"},L=["onClick"],T=["src"],j={key:1,class:"preview-text"},D={class:"preview-meta"},Q={key:0};function W(x,n,u,e,f,_){const C=w("a-select"),s=w("a-button"),l=w("a-popconfirm"),k=w("a-space"),g=w("a-tag"),R=w("a-table"),B=w("a-empty"),P=w("a-card");return d(),y("div",null,[m("div",K,[n[6]||(n[6]=m("div",null,[m("h2",{class:"page-title"},"交付物"),m("p",{class:"page-sub"}," agent 用 publish_artifact 发布的交付物(报告 / patch / 截图 / 日志),在这里预览与管理(cc artifacts 同源)。 ")],-1)),i(k,null,{default:r(()=>[i(C,{value:e.store.kindFilter,"onUpdate:value":n[0]||(n[0]=c=>e.store.kindFilter=c),style:{"min-width":"120px"},placeholder:"全部类型","allow-clear":"",options:e.kindOptions,onChange:n[1]||(n[1]=c=>e.store.fetchArtifacts())},null,8,["value","options"]),i(l,{title:"删除所有过期交付物?",onConfirm:e.onClean},{default:r(()=>[i(s,null,{default:r(()=>[...n[4]||(n[4]=[p("清理过期",-1)])]),_:1})]),_:1}),i(s,{ghost:"",loading:e.store.loading,onClick:n[2]||(n[2]=c=>e.store.fetchArtifacts())},{icon:r(()=>[i(e.ReloadOutlined)]),default:r(()=>[n[5]||(n[5]=p(" 刷新 ",-1))]),_:1},8,["loading"])]),_:1})]),i(R,{"data-source":e.store.artifacts,columns:e.columns,"row-key":"id",size:"small",pagination:{pageSize:10,hideOnSinglePage:!0}},{bodyCell:r(({column:c,record:t})=>[c.key==="title"?(d(),y("a",{key:0,onClick:a=>e.store.openPreview(t)},v(t.title),9,L)):c.key==="kind"?(d(),A(g,{key:1,color:e.store.kindColor(t.kind)},{default:r(()=>[p(v(t.kind),1)]),_:2},1032,["color"])):c.key==="size"?(d(),y(O,{key:2},[p(v(e.store.formatSize(t.size)),1)],64)):c.key==="createdAt"?(d(),y(O,{key:3},[p(v((t.createdAt||"").replace("T"," ").slice(0,19)),1)],64)):c.key==="actions"?(d(),A(k,{key:4},{default:r(()=>[i(s,{size:"small",onClick:a=>e.store.openPreview(t)},{default:r(()=>[...n[7]||(n[7]=[p("预览",-1)])]),_:1},8,["onClick"]),i(l,{title:"删除该交付物?",onConfirm:a=>e.onRemove(t.id)},{default:r(()=>[i(s,{size:"small",danger:""},{default:r(()=>[...n[8]||(n[8]=[p("删除",-1)])]),_:1})]),_:1},8,["onConfirm"])]),_:2},1024)):S("v-if",!0)]),_:1},8,["data-source"]),e.store.preview?(d(),A(P,{key:0,size:"small",class:"preview-card",loading:e.store.previewLoading},{title:r(()=>[i(k,null,{default:r(()=>[m("span",null,v(e.store.preview.entry?.title),1),i(g,{color:e.store.kindColor(e.store.preview.entry?.kind)},{default:r(()=>[p(v(e.store.preview.entry?.kind),1)]),_:1},8,["color"]),i(g,{color:"default"},{default:r(()=>[p(v(e.store.preview.entry?.mime),1)]),_:1}),e.store.preview.truncated?(d(),A(g,{key:0,color:"warning"},{default:r(()=>[...n[9]||(n[9]=[p("已截断预览",-1)])]),_:1})):S("v-if",!0)]),_:1})]),extra:r(()=>[i(s,{size:"small",onClick:n[3]||(n[3]=c=>e.store.closePreview())},{default:r(()=>[...n[10]||(n[10]=[p("关闭",-1)])]),_:1})]),default:r(()=>[e.store.previewImageSrc?(d(),y("img",{key:0,src:e.store.previewImageSrc,class:"preview-image",alt:"artifact preview"},null,8,T)):e.store.preview.previewable?(d(),y("pre",j,v(e.store.preview.content||"(空文件)"),1)):(d(),A(B,{key:2,description:e.store.preview.reason||"该类型不支持在线预览,可在本机用 cc artifacts open <id> 打开"},null,8,["description"])),m("div",D,[m("span",null,v(e.store.preview.entry?.id),1),e.store.preview.entry?.sessionId?(d(),y("span",Q," · session "+v(e.store.preview.entry.sessionId),1)):S("v-if",!0),m("span",null," · sha256 "+v((e.store.preview.entry?.sha256||"").slice(0,12))+"…",1)])]),_:1},8,["loading"])):S("v-if",!0)])}const X=M(J,[["render",W],["__scopeId","data-v-c8b48f8c"],["__file","/tmp/cc-web-panel-i5N2Ja/repo/packages/web-panel/src/views/Artifacts.vue"]]);export{X as default};
@@ -1 +0,0 @@
1
- .page-header[data-v-c8b48f8c]{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px}.preview-card[data-v-c8b48f8c]{margin-top:16px}.preview-text[data-v-c8b48f8c]{max-height:480px;overflow:auto;white-space:pre-wrap;word-break:break-word;font-size:12px;margin:0}.preview-image[data-v-c8b48f8c]{max-width:100%;max-height:480px;display:block}.preview-meta[data-v-c8b48f8c]{margin-top:8px;color:#808080cc;font-size:12px}