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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DjBXlypQ.js → AIOps-BwpTJTN_.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cv9er9bp.js → ActionButton-BtQOoo3l.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CLKDjSNs.js → Analytics-Duf_rXZa.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-XQK3wjvB.js +9 -0
- package/src/assets/web-panel/assets/Artifacts-CTVt96zW.css +1 -0
- package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +1 -0
- package/src/assets/web-panel/assets/{Audit-CXafD178.js → Audit-DNjOGQLh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CP5c-GSd.js → BackgroundAgents-FH9RAfB5.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BbYLQfyt.js → Backup-ZIbrR4-U.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bqe0MwZL.js → BaseInput-DSt3NaND.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-jNA_yUPH.js → Chat-CY-wvbjv.js} +6 -6
- package/src/assets/web-panel/assets/Chat-KSGPtU0L.css +1 -0
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BOFLF01s.js → ChatBubbleRenderer-BAV9V-t-.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Dlzz-zD1.js → Checkbox-8JVadE_B.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BAsDQjGf.js → Codegen-CN83nPYI.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Ju0HrAuS.js → Col-xPSvqdN8.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DynJd43Z.js → Community-D78cnIjG.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CabbZVww.js → Compact-CSw6Csbp.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DW4BmAoh.js → Compliance-E_HhjcYR.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bk1S_7X-.js → Cowork-DybeQjmm.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-By4sh_NM.js → Cron-DS0BJeLY.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DdZdbS1C.js → Crosschain-BOwVgkbs.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CkL-sQyS.js → DID-DRUr9YuX.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-g_mOpumg.js → Dashboard-Cht-yUKW.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DtHyE7Ao.js → Dropdown-BLr_EJ9X.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Kpvov5Wr.js → EmailListRenderer-Drexuh5W.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-C4kZoePR.js → FamilyGuardDashboard-D9Q2Kq63.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BAwWoK2N.js → Federation-C-Z1xI-T.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DeI0i5f8.js → FormItemContext-DLHPMkQF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CkcKRtos.js → GenericCardRenderer-DxxEdn2r.js} +1 -1
- package/src/assets/web-panel/assets/{Git-kX6jjI9y.js → Git-syATcaBt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXJCBm6x.js → Governance-CYe_r8Zf.js} +1 -1
- package/src/assets/web-panel/assets/Inference-GWxErB7k.js +1 -0
- package/src/assets/web-panel/assets/{KnowledgeGraph-3Dnuf1Wh.js → KnowledgeGraph-qQvVCZoi.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-lVAZCtUB.js → Logs-BenAU2W8.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-7agE3Or1.js +1 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-DbzhFwvz.css +1 -0
- package/src/assets/web-panel/assets/Marketplace-D4Jn5fpD.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-CqjzefRU.js → McpTools-DtAwoqiQ.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BFiPAxk6.js → Memory-BI_O50I8.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D0O43VwF.js → MobileBridge-CQWE2lqx.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B3now48t.js → MobileProjects-CVtTqgtg.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DybhtaCS.js → Mtc-Dnr16qsn.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D7MmY4F0.js → MtcAudit-CQ5Xe4-a.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wZvGJnB8.js → Multisig-Bjr8O-wy.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BNPpgfvn.js → NLProgramming-CEe-KirU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B6FS3KBj.js → Notes-spKJIKF9.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CK0yla9U.js → NotificationSettings-DRp83LFG.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +1 -0
- package/src/assets/web-panel/assets/{Organization-geqgwN1T.js → Organization-BlgB0IYg.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BWJYZFbC.js → Overflow-a_35HqZI.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DnU1505F.js → P2P-CSNHCNPC.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BGY74KnC.js → PdhVaultBrowser-C8M1JH-1.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CgArrWtQ.js → Permissions-DmjJi9Ms.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C2vYE61a.js → PersonalDataHub-DfuwRyf4.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CBYLI4yW.js → Pipeline-B2-S4ekE.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B-PdW9aN.js → Privacy-B1dOiBLC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CuWQegbV.js → ProjectInit-_pNmc-DX.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-4d32m_cS.js → ProjectSettings-DmwG2Ii1.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-VLA7ttwd.js → Projects-COATmHxF.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B_Izb_oa.js → Providers-C6HSjg1d.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C6B_ndXV.js → QrScannerModal-CrDmS7l1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C6tavL-y.js → QuickAsk-CfKvWt-x.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CF-LaZ6x.js → Recommend-DMa6Kuzv.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DrktUo5x.js +4 -0
- package/src/assets/web-panel/assets/Reputation-BTPQ1vjL.js +1 -0
- package/src/assets/web-panel/assets/{Row-DO0fd48A.js → Row-B6Q7QkMG.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BV5LDzFR.js → RssFeed-B1IJhAmX.js} +3 -3
- package/src/assets/web-panel/assets/Search-xbea1kGD.js +1 -0
- package/src/assets/web-panel/assets/{Security-DwSrWRCw.js → Security-7rs0cO3F.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BEf1QyM4.js → Services-0fjixK0S.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-LZXTpovy.js → Skeleton-B_rVq9Cu.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-SKgc0Zug.js → Skills-C5uM90b9.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_QVOtkN.js → Sla-U4pYCIZH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-v5TW2V-X.js → SpeechSettings-Cdz4yTWx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CQPjHdqU.js → SyncSettings-Me3aetHv.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DCd-uYcI.js → Tasks-Cf6aNkL_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-62VDIjtj.js → Templates-CYatk4tU.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BnFQ0pNy.js → Tenant-DIMKLw7o.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D3v0MGSj.js → Terminal-C_cp-uSJ.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BI1Ka-cu.js → TimelineRenderer-cUPVNADp.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BwX1jXYP.js → Tokens-CiaEIXVf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CSA8TqNx.js → Trigger-DB1WU5lQ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bn3IjCx7.js → Trust-Cfht9Ijy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-pUbOAqNi.js → UkeySign-K3P4Vt9S.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Db2RercF.js → VideoEditing-DWjhjXRh.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CpR1BVAl.js → Wallet-D9-Q1Kan.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Yxtkn7Zp.js → WebAuthn-BDszy-oV.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CYLe9a2E.js → WorkflowEditor-DIhij4rA.js} +1 -1
- package/src/assets/web-panel/assets/{chat-gsOGdak0.js → chat-D7VCiKbh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D9fN299a.js → colors-BvOd9pVn.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-vkd7s-ay.js → compact-item-DoqXBiAO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C7YkbMyy.js → createContext-CrKRzUtp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-ig7reRSU.js → hasIn-BWAev4WC.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
- package/src/assets/web-panel/assets/{index-W52sHRdr.js → index-1poMpMU0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZAxCnjS.js → index-8g5TJCZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-9TxdMYju.js → index-B0vGEbJT.js} +1 -1
- package/src/assets/web-panel/assets/index-B1_pRTtB.js +1 -0
- package/src/assets/web-panel/assets/{index-DhdXbsXX.js → index-B30NvR21.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7QRpKif.js → index-BCyP071m.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt-9ZhIu.js → index-BHOcVYrx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYe9HRSt.js → index-BN9m4Ux2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkP17Z95.js → index-BWWsaimP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDWTycrg.js → index-BXyl8O23.js} +1 -1
- package/src/assets/web-panel/assets/{index-CspGymmg.js → index-BZGk8AbZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFfxF7Vt.js → index-Bf9YlbDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ8U7kzC.js → index-Bj7EgmOp.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2yhtk6a.js → index-BkgnGX7v.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzGr6CVk.js → index-BoYrS_L2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMw48d19.js → index-Bq_ctRpz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAkz1f1r.js → index-C5kRmj3P.js} +1 -1
- package/src/assets/web-panel/assets/{index-SDrcQXO1.js → index-CDGLdn12.js} +1 -1
- package/src/assets/web-panel/assets/{index-gj8kxcAi.js → index-CEPHMSih.js} +1 -1
- package/src/assets/web-panel/assets/{index-PkH39qsp.js → index-CEXfKSiI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHHN_KTS.js → index-CSb-tnIo.js} +1 -1
- package/src/assets/web-panel/assets/{index-gpurplyq.js → index-CmJ6xKBO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfpVcXKa.js → index-CoD31Ndm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGzGe5tv.js → index-Cx7M7PSF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhftELHI.js → index-D23L1V76.js} +3 -3
- package/src/assets/web-panel/assets/{index-DPguq3X7.js → index-DV-Rfmfx.js} +4 -4
- package/src/assets/web-panel/assets/{index-BpGlLbRv.js → index-DlGu7As6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8HZMygt.js → index-DvDOpsJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuwbKHoS.js → index-HwHdTusY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7AY39sg.js → index-ISvPZ05r.js} +1 -1
- package/src/assets/web-panel/assets/{index-nX8YE6cj.js → index-JzUmJMvn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvCJXmrx.js → index-KR1-Em9C.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co2NYyew.js → index-PDxGjKRu.js} +1 -1
- package/src/assets/web-panel/assets/index-TMM9FuW-.js +1 -0
- package/src/assets/web-panel/assets/{index-D6s_kbjt.js → index-YGDy7nNq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy_TKZ9h.js → index-gmxqu5Kk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFM1oNe1.js → index-oY45XrkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-bVbgYBhv.js → index-ww1QtLnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvDEukXe.js → index-zH-tuuGZ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BrgfVRg9.js → initDefaultProps-XW-OR3mL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D_3xmwac.js → motion-CeuEHh20.js} +1 -1
- package/src/assets/web-panel/assets/{move-DDDJdlxw.js → move-Dva79Keg.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BK2uEWwn.js → mtc-parser-C0B6bD2j.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BPHKbDcf.js → omit-AkZvO-a2.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-LNnTscuz.js → pickAttrs-B4r55qGc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CvmTZll5.js → placementArrow-BLH-z7YE.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-eBj7qi2J.js → responsiveObserve-ksEGZ2Wf.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D68hHkrM.js → slide-Cth4OD1Z.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-daTlaeo0.js → statusUtils-9S83ezxw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DtyKMtHB.js → styleChecker-7yQmQGZ4.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-ekFwFs9h.js → useFlexGapSupport-DpGnMmPc.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-jRaSEoF4.js → useFs-C4zDZIXH.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DD5nGzjA.js → usePersonalDataHub-CoLICdUZ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-KcgLUiom.js → vnode-CKyH-35X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cyk2PQ-s.js → zoom-DRDGit17.js} +1 -1
- package/src/assets/web-panel/index.html +2 -2
- package/src/command-manifest.json +8 -1
- package/src/commands/agent.js +49 -5
- package/src/commands/artifacts.js +161 -0
- package/src/commands/browse.js +204 -2
- package/src/data/changelog.json +13 -2
- package/src/gateways/ws/artifact-protocol.js +219 -0
- package/src/gateways/ws/message-dispatcher.js +6 -0
- package/src/gateways/ws/ws-server.js +32 -0
- package/src/index.js +6 -0
- package/src/lib/accessibility.js +55 -0
- package/src/lib/artifact-store.js +236 -0
- package/src/lib/chrome-connector.js +349 -0
- package/src/lib/hook-runner.cjs +47 -7
- package/src/lib/ide-context.js +84 -0
- package/src/lib/permission-rules.cjs +26 -3
- package/src/lib/plugin-runtime/scopes.js +12 -0
- package/src/lib/remote-control.js +35 -0
- package/src/lib/safe-mode.js +40 -0
- package/src/lib/shell-selector.cjs +203 -0
- package/src/lib/skill-loader.js +19 -0
- package/src/lib/slash-dispatch.js +34 -0
- package/src/lib/web-ui-server.js +120 -7
- package/src/repl/agent-repl.js +330 -116
- package/src/repl/remote-approval.js +111 -0
- package/src/runtime/agent-core.js +129 -8
- package/src/runtime/coding-agent-contract-shared.cjs +105 -0
- package/src/runtime/coding-agent-policy.cjs +31 -0
- package/src/runtime/headless-stream.js +51 -1
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +0 -9
- package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
- package/src/assets/web-panel/assets/Inference-ujD2JSKo.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-qnzNPm-A.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-DBqTTQ9_.js +0 -4
- package/src/assets/web-panel/assets/Reputation-B01FALUi.js +0 -1
- package/src/assets/web-panel/assets/Search-CTti4_MP.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +0 -1
- package/src/assets/web-panel/assets/index-BEibanjg.js +0 -1
- package/src/assets/web-panel/assets/index-z_25tU3p.js +0 -1
package/src/repl/agent-repl.js
CHANGED
|
@@ -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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
|
639
|
-
|
|
640
|
-
|
|
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
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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.
|
|
661
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
697
|
-
// so
|
|
698
|
-
|
|
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
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
|
773
|
+
`\n ✕ denied without asking (dontAsk mode): ${tool}${rule ? ` (${rule})` : reason ? ` — ${reason}` : ""}\n`,
|
|
729
774
|
),
|
|
730
775
|
);
|
|
731
776
|
return false;
|
|
732
777
|
}
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|