chainlesschain 0.165.5 → 0.165.6
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CEeYTeDB.js → AIOps-vfoDb6HZ.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BeRUQ3V2.js → ActionButton-Cm5f7p93.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DrV718XF.js → Analytics-yRuvgWxa.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cd2Frt7Q.js → AppLayout-BROsC4h1.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CUJMlORU.js → Artifacts-CimHApb1.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CXJcfT6p.js → Audit-DI8kr8zI.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOv6Z7H9.js → BackgroundAgents-DSLzKy-n.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BwdDyBI0.js → Backup-DkH_n2r1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BmLn4kYH.js → BaseInput-CKSrUoRA.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-COg8pAW2.js → Chat-ClwfMXUS.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CjMaqfL7.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BCP4uoU8.js → Checkbox-BYTehcQG.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-pRt6Y9JI.js → Codegen-B8AMcp6J.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DLaprKhS.js → Col-BNSogPo3.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DyATsz5f.js → Community-JxPvGb8O.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BXAaupZK.js → Compact-DF7bFAgE.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DpcJBELr.js → Compliance-Bvhqnwrq.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DG4EFWyd.js → Cowork-BDS0CJvM.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-F6Oy5n2P.js → Cron-DqvRfxoQ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOdgtalX.js → Crosschain-CDcIvnqS.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT1dSkjH.js → DID-D7jaUxU3.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NBUIqWzC.js → Dashboard-B5A18wEV.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CSpHeMYB.js → Dropdown-_6IVkGag.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CEG0xISr.js → EmailListRenderer-C9PeiKCG.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BTZ0BZna.js → FamilyGuardDashboard-DXjuEHrX.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BuuCTn2V.js → Federation-B4IAR9zv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-KAVceevk.js → FormItemContext-CxQeSNx1.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnHpcQvW.js → GenericCardRenderer-B8GS-Y3N.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_B_0HwB.js → Git-BS8SouUX.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DPSZuzuu.js → Governance-Dq9aYb5Q.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BV-06V7q.js → Inference-BrCEe9AJ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-wpDuupn6.js → KnowledgeGraph-DybOpiY2.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Bseca1_I.js → Logs-DBpzbRfx.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-d3kM4DDL.js → Marketplace-Cc7CCSCu.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DQ_xZ23i.js → McpTools-DFaIhOfu.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CRIeYO4L.js → Memory-KCqlp9ol.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BsllIa9l.js → MobileBridge-Bys84ySN.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BUuQy30X.js → MobileProjects-B-6qU-je.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BOFDIzrC.js → Mtc-BgY-fvCq.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BQg-vyvG.js → MtcAudit-B8pCmqQJ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-Bo1jrPdN.js → Multisig-BbOmYutA.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bmod8alw.js → NLProgramming-BocLQz_M.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CGGFL8w-.js → Notes-CSwbxEop.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BsZvCJxC.js → NotificationSettings-CqFNSYG3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C1-Quo_h.js → OrderTableRenderer-C6gMfAk7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BjOeDYOi.js → Organization-QrDXVdMJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DwVV3l5A.js → Overflow-Cxntotjt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cu3DQT9W.js → P2P-CIdBOwNq.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Cg0gR7dH.js → PdhVaultBrowser-CT5aP8rD.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CsAXxRN5.js → Permissions-DH7OM8XU.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C-nNmnYp.js → PersonalDataHub-CnJqbWce.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BOnW7nNc.js → Pipeline-5C3HG1kH.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-ClLSyGKt.js → Privacy-B4yIp7xK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DuLvjjq7.js → ProjectInit-BMvt-2M0.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DQYYbDD9.js → ProjectSettings-CvSTEMRG.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Drw8MBGK.js → Projects-BKBJxEek.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-ckqHQe6K.js → Providers-BdwUqFWN.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DW5BXk-G.js → QrScannerModal-DtnCKlTm.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DDLnhNTp.js → QuickAsk-sTbgwyNr.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-PJQ2C91U.js → Recommend-Bzr14gSb.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-s6wmda34.js → RemoteSession-C_TF5IaJ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DC2sb-I3.js → Reputation-GCAM4H2K.js} +1 -1
- package/src/assets/web-panel/assets/{Row-hPGk1eGj.js → Row-Dir0r6OQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-RIvfZql0.js → RssFeed-CpGnlecX.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DFg1G1eR.js → Search-DMMamC6u.js} +1 -1
- package/src/assets/web-panel/assets/{Security-D6H5p8Dp.js → Security-yqay5yG-.js} +4 -4
- package/src/assets/web-panel/assets/{Services-nxYiFV_w.js → Services-C5i2wh9G.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BmgFNAdB.js → Skeleton-vUqk35yj.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CsaaD2av.js → Skills-DzcSkhot.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yM5y9yvK.js → Sla-CB6qbYNr.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DPy1XMYh.js → SpeechSettings-BHUI7cox.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CFVhEyyr.js → SyncSettings-D-RrsxTJ.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DdO02Inn.js → Tasks-DFxdOJiq.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Eo2d8Llu.js → Templates-SKAob_Aq.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-ChESOXgy.js → Tenant-BPfsfHQ-.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BJ7oDxTI.js → Terminal-8iw7FpUf.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-hWX0fUvz.js → TimelineRenderer-C4Gjj5Su.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DVBZQnd-.js → Tokens-COYhN18e.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D89cQfVq.js → Trigger-B-JzXcf6.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BIVSb4-V.js → Trust-BJ5bg0nI.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B58gWopl.js → UkeySign-BG_ByRMf.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DhHI7bmG.js → VideoEditing-B3kOnSiU.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C4FheWS_.js → Wallet-CcoGFnK7.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-DnPRq-Bx.js → WebAuthn-DdT8vRC_.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BerRPjpJ.js → WorkflowEditor-CZu3M9N8.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BzZ52iKr.js → chat-DxJt3Qba.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BI_E4BAn.js → colors-DdYfwJaB.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-d50mr3yk.js → compact-item-BG3SUzwj.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C6FsQ1IH.js → createContext-By51z2-1.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DorRZcI9.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLZEMgLP.js → hasIn-B0apZ3KJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoNZlzhQ.js → index-3jWkIu5j.js} +1 -1
- package/src/assets/web-panel/assets/{index-AHr7ryWf.js → index-B3rWuPMW.js} +1 -1
- package/src/assets/web-panel/assets/{index-KNGgO4Wm.js → index-B5rLiEo0.js} +1 -1
- package/src/assets/web-panel/assets/{index-IPkwSAW_.js → index-BBPf3TpM.js} +1 -1
- package/src/assets/web-panel/assets/index-BETiL0a3.js +1 -0
- package/src/assets/web-panel/assets/{index-Dec0Kf_Z.js → index-BIrJfqO4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTpIOTfR.js → index-BLIbGOLX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcSUl9fA.js → index-BP94CrBV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B81mo1Et.js → index-BWF8_mkx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoFtCVZ7.js → index-BYINoRLK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY4j5zkF.js → index-BZ_0qm4g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVzuawCX.js → index-BewKYrX9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE0V6cp-.js → index-BfWYsB9t.js} +1 -1
- package/src/assets/web-panel/assets/index-BhAVHuW8.js +1 -0
- package/src/assets/web-panel/assets/{index-BRGx3w6T.js → index-Bissefox.js} +3 -3
- package/src/assets/web-panel/assets/{index-CjuKZBqJ.js → index-BkvcEo4p.js} +1 -1
- package/src/assets/web-panel/assets/{index-9H_M2jju.js → index-Bos5_aMV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqkbHHg8.js → index-C1C9i79A.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y2-q7QLm.js → index-CAhHirVH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q5Tn735s.js → index-CNIxXMmN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CO8DAF7U.js → index-CW93tZDv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZjjdQ62.js → index-CWEuZWCK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3k8kevG.js → index-Cp6e-hGw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUaEHfQ0.js → index-CpjZhgNd.js} +1 -1
- package/src/assets/web-panel/assets/{index-GXE0D8Nx.js → index-CzmQElZy.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bmr_vxXD.js → index-DCn8IcxV.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-aBumtY.js → index-DE76D-kA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeM4hfht.js → index-DaIz0NAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Pvf30m8Q.js → index-Da_WBdLX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAE3hwev.js → index-Dz49VOyQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdKt9fOd.js → index-Q2SCX-MI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRN5TRTQ.js → index-RD6O-0C6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsscwxPE.js → index-SQqOlR3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-TWG-_D1M.js → index-ZFbps9po.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLoTQr0W.js → index-ZxtP8jG7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BB9SqVSt.js → index-bP_VlDb4.js} +1 -1
- package/src/assets/web-panel/assets/{index-A5CDWP7y.js → index-kf57LJ93.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgkoH3_g.js → index-oxRrmFO9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7s9LsB.js → index-p1_8SbI1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D-4Hzgej.js → initDefaultProps-BWJktcg8.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D4Wl4xU7.js → motion-BEXBzUFV.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cd5eQvL0.js → move-BUKwn2sx.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B4ilslCW.js → mtc-parser-FTI7btqQ.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ckarx-tf.js → omit-CsLtSPH3.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-QJ0bpCmc.js → pickAttrs-DWZxsT_S.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CKHm-tq8.js → placementArrow-Bcfs43CX.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CV6a_w_Y.js → responsiveObserve-a9bcC1p-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-mHpeQFgk.js → slide-BDTzgOXN.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIaQNQWA.js → statusUtils-Iq1j_HUg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-PJpzavV8.js → styleChecker-DAZEYxeh.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BH7zTw8S.js → useFlexGapSupport-DVkjyDrh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BDNG3JDd.js → useFs-wXTpNTHt.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CY2VWdUg.js → usePersonalDataHub-CtQM_aZr.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D0kw9jhz.js → vnode-hjEoG85v.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B5g7TN7r.js → zoom-BHVpyKg_.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +14 -2
- package/src/commands/background-session.js +100 -10
- package/src/commands/mcp.js +5 -0
- package/src/commands/plugin.js +145 -29
- package/src/commands/remote-control.js +295 -110
- package/src/commands/session-message.js +171 -0
- package/src/commands/session.js +11 -0
- package/src/data/changelog.json +15 -2
- package/src/harness/mcp-client.js +141 -10
- package/src/lib/background-session-transport.js +153 -27
- package/src/lib/mcp-client.js +2 -0
- package/src/lib/mcp-headers-helper.js +47 -5
- package/src/lib/mcp-oauth.js +55 -6
- package/src/lib/mcp-tls.js +230 -0
- package/src/lib/output-styles.js +12 -0
- package/src/lib/plugin-runtime/install.js +614 -71
- package/src/lib/plugin-runtime/marketplace-archive-source.js +331 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +52 -6
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +163 -0
- package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
- package/src/lib/plugin-runtime/remote-source.js +435 -55
- package/src/lib/plugin-security.js +393 -41
- package/src/lib/plugin-source-identity.js +850 -0
- package/src/lib/remote-approval-bridge.js +136 -46
- package/src/lib/remote-control.js +217 -31
- package/src/lib/session-message-fabric.js +1105 -0
- package/src/lib/session-projection.js +45 -2
- package/src/lib/session-runtime-retention.js +155 -0
- package/src/lib/settings-loader.cjs +29 -0
- package/src/repl/agent-repl.js +51 -14
- package/src/repl/permission-prompt.js +31 -8
- package/src/repl/repl-keybindings.js +55 -0
- package/src/runtime/agent-core.js +51 -2
- package/src/runtime/headless-runner.js +47 -66
- package/src/runtime/headless-stream.js +18 -24
- package/src/runtime/mcp-config.js +2 -0
- package/src/runtime/policies/agent-policy.js +2 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-B4RLp2zs.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CagEcIex.js +0 -1
- package/src/assets/web-panel/assets/index-pzT9PpOU.js +0 -1
- package/src/assets/web-panel/assets/index-z1kFadaX.js +0 -1
|
@@ -43,11 +43,15 @@ import {
|
|
|
43
43
|
REMOTE_MEMBERSHIP_HOST_UNAVAILABLE_CODE,
|
|
44
44
|
} from "./remote-membership-host-store.js";
|
|
45
45
|
import {
|
|
46
|
+
assertDirectWsUrlAllowed,
|
|
46
47
|
buildDirectPairingUri,
|
|
48
|
+
isLoopbackBindHost,
|
|
47
49
|
pickLanAddress,
|
|
48
50
|
renderQrCode,
|
|
49
51
|
resolveRemoteControlOptions,
|
|
52
|
+
resolveRemoteControlWsUrl,
|
|
50
53
|
} from "./remote-control.js";
|
|
54
|
+
import { logger } from "./logger.js";
|
|
51
55
|
|
|
52
56
|
const DEFAULT_DECISION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
53
57
|
const REMOTE_LEASE_AUTHORIZATION_KIND =
|
|
@@ -345,7 +349,8 @@ export class RemoteApprovalBridge {
|
|
|
345
349
|
|
|
346
350
|
/**
|
|
347
351
|
* Pairing descriptor for devices. Relay-configured servers return a ready
|
|
348
|
-
* E2EE URI; otherwise build
|
|
352
|
+
* E2EE URI; otherwise build a direct URI from the explicitly selected
|
|
353
|
+
* loopback/LAN endpoint.
|
|
349
354
|
*/
|
|
350
355
|
pairingInfo({ lanWsUrl = null } = {}) {
|
|
351
356
|
if (!this.pairing) return null;
|
|
@@ -363,6 +368,7 @@ export class RemoteApprovalBridge {
|
|
|
363
368
|
});
|
|
364
369
|
return {
|
|
365
370
|
uri,
|
|
371
|
+
mode: this.pairing.uri ? "relay" : "direct",
|
|
366
372
|
remoteSessionId: this.remoteSessionId,
|
|
367
373
|
scopes: this.pairing.scopes,
|
|
368
374
|
expiresAt: this.pairing.expiresAt,
|
|
@@ -1297,21 +1303,60 @@ export class RemoteApprovalBridge {
|
|
|
1297
1303
|
* self-hosts a lightweight WS server on an OS-assigned port (no sessionManager
|
|
1298
1304
|
* needed — the session stays client-hosted), starts a bridge for this run's
|
|
1299
1305
|
* session, prints the pairing URI (+ optional QR) to stderr, and returns the
|
|
1300
|
-
* gate confirmer + teardown. Relay settings
|
|
1301
|
-
*
|
|
1306
|
+
* gate confirmer + teardown. Relay settings flow through the startup
|
|
1307
|
+
* environment; LAN exposure remains a separate explicit caller authority.
|
|
1302
1308
|
*/
|
|
1303
1309
|
export async function startHeadlessRemoteApproval({
|
|
1304
1310
|
agentSessionId,
|
|
1311
|
+
allowLan = false,
|
|
1305
1312
|
env = process.env,
|
|
1306
|
-
config =
|
|
1313
|
+
config = undefined,
|
|
1307
1314
|
writeErr = () => {},
|
|
1308
1315
|
isText = false,
|
|
1309
1316
|
decisionTimeoutMs = undefined,
|
|
1310
1317
|
deps = {},
|
|
1311
1318
|
} = {}) {
|
|
1319
|
+
let effectiveConfig = config;
|
|
1320
|
+
if (effectiveConfig === undefined) {
|
|
1321
|
+
try {
|
|
1322
|
+
const configModule =
|
|
1323
|
+
deps.configModule || (await import("./config-manager.js"));
|
|
1324
|
+
effectiveConfig = configModule.loadConfig();
|
|
1325
|
+
} catch {
|
|
1326
|
+
effectiveConfig = {};
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const resolved = resolveRemoteControlOptions({
|
|
1330
|
+
flags: { allowApprove: true, allowLan: allowLan === true },
|
|
1331
|
+
env,
|
|
1332
|
+
config: effectiveConfig || {},
|
|
1333
|
+
});
|
|
1334
|
+
if (!resolved.relayUrl && !resolved.lanAccessible) {
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
"remote approval is loopback-only by default; configure a relay, or enable " +
|
|
1337
|
+
"allowLan with a non-loopback host (omit host to use 0.0.0.0) on a trusted network",
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
const lanAddress = resolved.lanAccessible
|
|
1341
|
+
? deps.lanAddress ||
|
|
1342
|
+
(deps.pickLanAddress ? deps.pickLanAddress() : pickLanAddress())
|
|
1343
|
+
: null;
|
|
1344
|
+
const preflightLanWsUrl = resolveRemoteControlWsUrl(
|
|
1345
|
+
{ ...resolved, port: 1 },
|
|
1346
|
+
{ lanAddress },
|
|
1347
|
+
);
|
|
1348
|
+
if (
|
|
1349
|
+
!resolved.relayUrl &&
|
|
1350
|
+
resolved.lanAccessible &&
|
|
1351
|
+
isLoopbackBindHost(new URL(preflightLanWsUrl).hostname)
|
|
1352
|
+
) {
|
|
1353
|
+
throw new Error(
|
|
1354
|
+
"LAN binding is enabled but no private LAN address can be advertised; " +
|
|
1355
|
+
"configure a private host or a relay",
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1312
1358
|
const { ChainlessChainWSServer } =
|
|
1313
1359
|
deps.serverModule || (await import("../gateways/ws/ws-server.js"));
|
|
1314
|
-
const resolved = resolveRemoteControlOptions({ flags: {}, env, config });
|
|
1315
1360
|
const server = new ChainlessChainWSServer({
|
|
1316
1361
|
port: 0,
|
|
1317
1362
|
host: resolved.host,
|
|
@@ -1319,50 +1364,95 @@ export async function startHeadlessRemoteApproval({
|
|
|
1319
1364
|
remoteSessionRelayUrl: resolved.relayUrl,
|
|
1320
1365
|
remoteSessionPeerId: resolved.peerId,
|
|
1321
1366
|
});
|
|
1322
|
-
|
|
1323
|
-
const bridge = new RemoteApprovalBridge({
|
|
1324
|
-
wsUrl: `ws://127.0.0.1:${server.port}`,
|
|
1325
|
-
token: resolved.token,
|
|
1326
|
-
agentSessionId,
|
|
1327
|
-
scopes: ["observe", "approve"],
|
|
1328
|
-
decisionTimeoutMs,
|
|
1329
|
-
});
|
|
1367
|
+
let bridge;
|
|
1330
1368
|
try {
|
|
1331
|
-
await
|
|
1369
|
+
await server.start();
|
|
1370
|
+
const createBridge =
|
|
1371
|
+
deps.createBridge || ((options) => new RemoteApprovalBridge(options));
|
|
1372
|
+
bridge = createBridge({
|
|
1373
|
+
wsUrl: resolveRemoteControlWsUrl(
|
|
1374
|
+
{ ...resolved, port: server.port, allowLan: resolved.lanAccessible },
|
|
1375
|
+
{ lanAddress: null },
|
|
1376
|
+
),
|
|
1377
|
+
token: resolved.token,
|
|
1378
|
+
agentSessionId,
|
|
1379
|
+
scopes: ["observe", "approve"],
|
|
1380
|
+
decisionTimeoutMs,
|
|
1381
|
+
});
|
|
1382
|
+
try {
|
|
1383
|
+
await bridge.start();
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
await server.stop().catch(() => undefined);
|
|
1386
|
+
throw err;
|
|
1387
|
+
}
|
|
1388
|
+
const lanWsUrl = resolveRemoteControlWsUrl(
|
|
1389
|
+
{ ...resolved, port: server.port },
|
|
1390
|
+
{ lanAddress },
|
|
1391
|
+
);
|
|
1392
|
+
const pairing = bridge.pairingInfo({ lanWsUrl });
|
|
1393
|
+
if (resolved.relayUrl && pairing?.mode !== "relay") {
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
"the configured relay did not provide a pairing URI; refusing to fall back to direct pairing",
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
if (
|
|
1399
|
+
!resolved.relayUrl &&
|
|
1400
|
+
pairing?.mode !== "relay" &&
|
|
1401
|
+
!resolved.lanAccessible
|
|
1402
|
+
) {
|
|
1403
|
+
throw new Error(
|
|
1404
|
+
"direct remote approval requires explicit allowLan on a trusted network",
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
if (pairing?.mode === "direct") {
|
|
1408
|
+
if (isLoopbackBindHost(new URL(lanWsUrl).hostname)) {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
"LAN binding is enabled but no private LAN address can be advertised",
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
assertDirectWsUrlAllowed(lanWsUrl);
|
|
1414
|
+
const warn = deps.warn || ((message) => logger.warn(message));
|
|
1415
|
+
warn(
|
|
1416
|
+
`LAN remote approval enabled on ${resolved.host}:${server.port}. ` +
|
|
1417
|
+
"Direct pairing uses plaintext ws:// with bearer credentials; use only on a trusted network.",
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (!pairing?.uri) {
|
|
1421
|
+
throw new Error("remote approval did not produce a pairing URI");
|
|
1422
|
+
}
|
|
1423
|
+
if (isText) {
|
|
1424
|
+
writeErr(
|
|
1425
|
+
" remote-control: approvals can be answered from a paired device\n",
|
|
1426
|
+
);
|
|
1427
|
+
writeErr(` pairing: ${pairing.uri}\n`);
|
|
1428
|
+
const qr = await renderQrCode(pairing.uri, deps);
|
|
1429
|
+
if (qr) writeErr(qr + "\n");
|
|
1430
|
+
}
|
|
1431
|
+
return {
|
|
1432
|
+
pairing,
|
|
1433
|
+
server,
|
|
1434
|
+
bridge,
|
|
1435
|
+
confirmer: bridge.makeConfirmer({
|
|
1436
|
+
onAsk: (ask) => {
|
|
1437
|
+
if (isText) {
|
|
1438
|
+
writeErr(
|
|
1439
|
+
` permission(${ask.tool || "?"}): waiting for remote approval…\n`,
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
}),
|
|
1444
|
+
consumeAuthorization: (authorization, context) =>
|
|
1445
|
+
bridge.consumeAuthorization(authorization, context),
|
|
1446
|
+
close: async () => {
|
|
1447
|
+
await bridge.close().catch(() => undefined);
|
|
1448
|
+
await server.stop().catch(() => undefined);
|
|
1449
|
+
},
|
|
1450
|
+
};
|
|
1332
1451
|
} catch (err) {
|
|
1452
|
+
if (bridge) {
|
|
1453
|
+
await bridge.close().catch(() => undefined);
|
|
1454
|
+
}
|
|
1333
1455
|
await server.stop().catch(() => undefined);
|
|
1334
1456
|
throw err;
|
|
1335
1457
|
}
|
|
1336
|
-
const lanAddress = deps.lanAddress || pickLanAddress() || "127.0.0.1";
|
|
1337
|
-
const pairing = bridge.pairingInfo({
|
|
1338
|
-
lanWsUrl: `ws://${lanAddress}:${server.port}`,
|
|
1339
|
-
});
|
|
1340
|
-
if (isText) {
|
|
1341
|
-
writeErr(
|
|
1342
|
-
" remote-control: approvals can be answered from a paired device\n",
|
|
1343
|
-
);
|
|
1344
|
-
writeErr(` pairing: ${pairing.uri}\n`);
|
|
1345
|
-
const qr = await renderQrCode(pairing.uri, deps);
|
|
1346
|
-
if (qr) writeErr(qr + "\n");
|
|
1347
|
-
}
|
|
1348
|
-
return {
|
|
1349
|
-
pairing,
|
|
1350
|
-
server,
|
|
1351
|
-
bridge,
|
|
1352
|
-
confirmer: bridge.makeConfirmer({
|
|
1353
|
-
onAsk: (ask) => {
|
|
1354
|
-
if (isText) {
|
|
1355
|
-
writeErr(
|
|
1356
|
-
` permission(${ask.tool || "?"}): waiting for remote approval…\n`,
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
},
|
|
1360
|
-
}),
|
|
1361
|
-
consumeAuthorization: (authorization, context) =>
|
|
1362
|
-
bridge.consumeAuthorization(authorization, context),
|
|
1363
|
-
close: async () => {
|
|
1364
|
-
await bridge.close().catch(() => undefined);
|
|
1365
|
-
await server.stop().catch(() => undefined);
|
|
1366
|
-
},
|
|
1367
|
-
};
|
|
1368
1458
|
}
|
|
@@ -6,14 +6,13 @@
|
|
|
6
6
|
* already exists under src/gateways + src/harness; this module only composes
|
|
7
7
|
* it into one entry point:
|
|
8
8
|
*
|
|
9
|
-
* - resolveRemoteControlOptions()
|
|
9
|
+
* - resolveRemoteControlOptions() explicit authority > safe defaults
|
|
10
10
|
* - buildDirectPairingUri() LAN pairing descriptor (no relay needed)
|
|
11
11
|
* - parseDirectPairingUri() inverse, used by clients + tests
|
|
12
12
|
* - pickLanAddress() best-effort non-internal IPv4
|
|
13
13
|
* - state file read/write ~/.chainlesschain/remote-control/<port>.json
|
|
14
14
|
* so `cc remote-control status/stop` can discover a running host process.
|
|
15
|
-
* The file is 0600 and
|
|
16
|
-
* as ~/.chainlesschain/ide/<port>.json (readable only by the OS user).
|
|
15
|
+
* The file is 0600 and deliberately contains no bearer/pairing token.
|
|
17
16
|
* - renderQrCode() lazy OPTIONAL `qrcode` import; returns
|
|
18
17
|
* null when the package is not installed (URI printing is the contract,
|
|
19
18
|
* the QR is progressive enhancement — no hard dependency, trap #6/#27).
|
|
@@ -23,19 +22,123 @@ import fs from "fs";
|
|
|
23
22
|
import os from "os";
|
|
24
23
|
import path from "path";
|
|
25
24
|
import { randomBytes } from "crypto";
|
|
25
|
+
import { isIP } from "net";
|
|
26
26
|
import { REMOTE_SESSION_SCOPES } from "../harness/remote-session-registry.js";
|
|
27
27
|
import { extractHost, isPrivateHost } from "./sandbox-network-policy.js";
|
|
28
28
|
|
|
29
29
|
export const REMOTE_CONTROL_DEFAULT_PORT = 18800;
|
|
30
|
+
export const REMOTE_CONTROL_DEFAULT_HOST = "127.0.0.1";
|
|
30
31
|
export const REMOTE_CONTROL_PAIRING_SCHEME =
|
|
31
32
|
"chainlesschain://remote-control/pair#";
|
|
32
33
|
export const REMOTE_CONTROL_DEFAULT_SCOPES = Object.freeze([
|
|
33
34
|
"observe",
|
|
34
35
|
"prompt",
|
|
35
|
-
"approve",
|
|
36
|
-
"interrupt",
|
|
37
36
|
]);
|
|
38
37
|
|
|
38
|
+
function cleanHost(value) {
|
|
39
|
+
if (value == null) return null;
|
|
40
|
+
const host = String(value).trim();
|
|
41
|
+
return host || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeBindHost(value, label) {
|
|
45
|
+
let host = cleanHost(value);
|
|
46
|
+
if (!host) return null;
|
|
47
|
+
if (host.startsWith("[") && host.endsWith("]")) {
|
|
48
|
+
host = host.slice(1, -1);
|
|
49
|
+
}
|
|
50
|
+
host = host.replace(/\.+$/, "").toLowerCase();
|
|
51
|
+
if (host.includes("%")) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Invalid ${label} "${host}": scoped IPv6 bind hosts are not supported.`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (host === "localhost" || isIP(host) !== 0) return host;
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Invalid ${label} "${host}": use localhost or an IPv4/IPv6 literal without a port.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function mappedIpv4FirstOctet(host) {
|
|
63
|
+
const dotted = host.match(/^::ffff:(\d{1,3})\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i);
|
|
64
|
+
if (dotted) return Number(dotted[1]);
|
|
65
|
+
const hex = host.match(/^::ffff:([0-9a-f]{1,4}):[0-9a-f]{1,4}$/i);
|
|
66
|
+
if (!hex) return null;
|
|
67
|
+
return (parseInt(hex[1], 16) >> 8) & 0xff;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Whether a bind host is restricted to this machine. */
|
|
71
|
+
export function isLoopbackBindHost(value) {
|
|
72
|
+
let host = cleanHost(value)?.toLowerCase();
|
|
73
|
+
if (!host) return false;
|
|
74
|
+
if (host.startsWith("[") && host.endsWith("]")) {
|
|
75
|
+
host = host.slice(1, -1);
|
|
76
|
+
}
|
|
77
|
+
host = host.replace(/\.+$/, "");
|
|
78
|
+
return (
|
|
79
|
+
host === "localhost" ||
|
|
80
|
+
(isIP(host) === 6 && host === "::1") ||
|
|
81
|
+
(isIP(host) === 4 && host.split(".")[0] === "127") ||
|
|
82
|
+
mappedIpv4FirstOctet(host) === 127
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isWildcardBindHost(value) {
|
|
87
|
+
let host = cleanHost(value)?.toLowerCase();
|
|
88
|
+
if (host?.startsWith("[") && host.endsWith("]")) {
|
|
89
|
+
host = host.slice(1, -1);
|
|
90
|
+
}
|
|
91
|
+
return host === "0.0.0.0" || host === "::";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isLanBindHost(value) {
|
|
95
|
+
let host = cleanHost(value)?.toLowerCase();
|
|
96
|
+
if (host?.startsWith("[") && host.endsWith("]")) {
|
|
97
|
+
host = host.slice(1, -1);
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
isWildcardBindHost(host) ||
|
|
101
|
+
isLoopbackBindHost(host) ||
|
|
102
|
+
(isIP(host) !== 0 && isPrivateHost(host))
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isAdvertisableLanAddress(value) {
|
|
107
|
+
const host = cleanHost(value)?.toLowerCase();
|
|
108
|
+
return (
|
|
109
|
+
isIP(host) !== 0 &&
|
|
110
|
+
isPrivateHost(host) &&
|
|
111
|
+
!isLoopbackBindHost(host) &&
|
|
112
|
+
!isWildcardBindHost(host) &&
|
|
113
|
+
host !== "169.254.169.254"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function wsHost(value) {
|
|
118
|
+
const host = cleanHost(value) || REMOTE_CONTROL_DEFAULT_HOST;
|
|
119
|
+
if (host.startsWith("[") && host.endsWith("]")) return host;
|
|
120
|
+
return host.includes(":") ? `[${host}]` : host;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Resolve the direct endpoint that is safe to advertise to pairing clients. */
|
|
124
|
+
export function resolveRemoteControlWsUrl(
|
|
125
|
+
{ host = REMOTE_CONTROL_DEFAULT_HOST, port, allowLan = false } = {},
|
|
126
|
+
{ lanAddress = null } = {},
|
|
127
|
+
) {
|
|
128
|
+
let endpointHost;
|
|
129
|
+
if (allowLan && !isLoopbackBindHost(host)) {
|
|
130
|
+
endpointHost = isWildcardBindHost(host)
|
|
131
|
+
? isAdvertisableLanAddress(lanAddress)
|
|
132
|
+
? cleanHost(lanAddress)
|
|
133
|
+
: REMOTE_CONTROL_DEFAULT_HOST
|
|
134
|
+
: cleanHost(host);
|
|
135
|
+
} else if (isLoopbackBindHost(host)) {
|
|
136
|
+
endpointHost = cleanHost(host);
|
|
137
|
+
} else {
|
|
138
|
+
endpointHost = REMOTE_CONTROL_DEFAULT_HOST;
|
|
139
|
+
}
|
|
140
|
+
return `ws://${wsHost(endpointHost)}:${port}`;
|
|
141
|
+
}
|
|
39
142
|
function b64url(value) {
|
|
40
143
|
return Buffer.from(value, "utf8").toString("base64url");
|
|
41
144
|
}
|
|
@@ -63,22 +166,70 @@ export function parseScopes(raw) {
|
|
|
63
166
|
}
|
|
64
167
|
|
|
65
168
|
/**
|
|
66
|
-
* Resolve effective start options.
|
|
67
|
-
*
|
|
169
|
+
* Resolve effective start options. Remote Control is always command/flag
|
|
170
|
+
* activated by its caller; repository/project configuration is only allowed
|
|
171
|
+
* to disable it. In particular, checked-in config cannot widen the bind host,
|
|
172
|
+
* grant approve/interrupt, select a relay, or install a long-lived token.
|
|
173
|
+
*
|
|
174
|
+
* Direct LAN and privileged scopes are distinct command-line authorities. A
|
|
68
175
|
* missing token is AUTO-GENERATED (32 hex chars) — the WS server must never
|
|
69
|
-
* come up unauthenticated
|
|
70
|
-
* pairing URI embeds the token and pairing is the whole point.
|
|
176
|
+
* come up unauthenticated, even on loopback.
|
|
71
177
|
*/
|
|
72
178
|
export function resolveRemoteControlOptions({
|
|
73
179
|
flags = {},
|
|
74
180
|
env = process.env,
|
|
75
181
|
config = {},
|
|
76
182
|
} = {}) {
|
|
77
|
-
const rc =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
183
|
+
const rc =
|
|
184
|
+
config.remoteControl && typeof config.remoteControl === "object"
|
|
185
|
+
? config.remoteControl
|
|
186
|
+
: {};
|
|
187
|
+
const legacy =
|
|
188
|
+
config.remoteSession && typeof config.remoteSession === "object"
|
|
189
|
+
? config.remoteSession
|
|
190
|
+
: {};
|
|
191
|
+
if (
|
|
192
|
+
config.remoteControl === false ||
|
|
193
|
+
rc.enabled === false ||
|
|
194
|
+
legacy.remoteControlEnabled === false
|
|
195
|
+
) {
|
|
196
|
+
throw new Error("Remote Control is disabled by configuration");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const warnings = [];
|
|
200
|
+
if (
|
|
201
|
+
Object.prototype.hasOwnProperty.call(rc, "host") ||
|
|
202
|
+
Object.prototype.hasOwnProperty.call(rc, "allowLan")
|
|
203
|
+
) {
|
|
204
|
+
warnings.push(
|
|
205
|
+
"Ignoring project Remote Control exposure settings; use explicit --host/--allow-lan for this invocation.",
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const flagHost = normalizeBindHost(flags.host, "--host");
|
|
210
|
+
const allowLan = flags.allowLan === true;
|
|
211
|
+
if (flagHost && !isLoopbackBindHost(flagHost) && !allowLan) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Refusing non-loopback --host "${flagHost}" without --allow-lan. ` +
|
|
214
|
+
"Add --allow-lan on a trusted network, or remove --host and configure --relay-url.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const host = flagHost || (allowLan ? "0.0.0.0" : REMOTE_CONTROL_DEFAULT_HOST);
|
|
218
|
+
if (allowLan && host === "::") {
|
|
219
|
+
throw new Error(
|
|
220
|
+
'Refusing IPv6 wildcard host "::": use an explicit private IPv6 address ' +
|
|
221
|
+
"or 0.0.0.0 so the advertised endpoint matches the listener.",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
if (allowLan && !isLanBindHost(host)) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Refusing --allow-lan with non-private host "${host}". ` +
|
|
227
|
+
"Use a loopback, wildcard, or private LAN address; use a relay for public networks.",
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
const lanAccessible = allowLan && !isLoopbackBindHost(host);
|
|
231
|
+
|
|
232
|
+
const port = Number(flags.port ?? env.CC_REMOTE_CONTROL_PORT ?? undefined);
|
|
82
233
|
const resolvedPort =
|
|
83
234
|
Number.isFinite(port) && port >= 1 && port <= 65535
|
|
84
235
|
? Math.floor(port)
|
|
@@ -86,40 +237,59 @@ export function resolveRemoteControlOptions({
|
|
|
86
237
|
const token =
|
|
87
238
|
flags.token ||
|
|
88
239
|
env.CC_REMOTE_CONTROL_TOKEN ||
|
|
89
|
-
rc.token ||
|
|
90
240
|
randomBytes(16).toString("hex");
|
|
91
|
-
const relayUrl =
|
|
92
|
-
flags.relayUrl ||
|
|
93
|
-
env.CC_REMOTE_SESSION_RELAY_URL ||
|
|
94
|
-
rc.relayUrl ||
|
|
95
|
-
legacy.relayUrl ||
|
|
96
|
-
null;
|
|
241
|
+
const relayUrl = flags.relayUrl || env.CC_REMOTE_SESSION_RELAY_URL || null;
|
|
97
242
|
const peerId =
|
|
98
243
|
flags.peerId ||
|
|
99
244
|
env.CC_REMOTE_SESSION_PEER_ID ||
|
|
100
|
-
rc.peerId ||
|
|
101
|
-
legacy.peerId ||
|
|
102
245
|
(relayUrl
|
|
103
246
|
? `cc-host-${os.hostname()}-${randomBytes(4).toString("hex")}`
|
|
104
247
|
: null);
|
|
248
|
+
|
|
249
|
+
const requestedScopes = parseScopes(flags.scopes ?? null);
|
|
250
|
+
if (requestedScopes.includes("approve") && flags.allowApprove !== true) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'Remote Control scope "approve" requires explicit --allow-approve',
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (requestedScopes.includes("interrupt") && flags.allowInterrupt !== true) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
'Remote Control scope "interrupt" requires explicit --allow-interrupt',
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const scopes = [...requestedScopes];
|
|
261
|
+
if (flags.allowApprove === true && !scopes.includes("approve")) {
|
|
262
|
+
scopes.push("approve");
|
|
263
|
+
}
|
|
264
|
+
if (flags.allowInterrupt === true && !scopes.includes("interrupt")) {
|
|
265
|
+
scopes.push("interrupt");
|
|
266
|
+
}
|
|
267
|
+
|
|
105
268
|
return {
|
|
106
269
|
port: resolvedPort,
|
|
107
|
-
host
|
|
270
|
+
host,
|
|
271
|
+
allowLan,
|
|
272
|
+
exposesLan: lanAccessible,
|
|
273
|
+
lanAccessible,
|
|
274
|
+
warnings,
|
|
108
275
|
token,
|
|
109
276
|
relayUrl,
|
|
110
277
|
peerId,
|
|
111
|
-
scopes
|
|
112
|
-
name: flags.name ||
|
|
278
|
+
scopes,
|
|
279
|
+
name: flags.name || `remote-control @ ${os.hostname()}`,
|
|
113
280
|
};
|
|
114
281
|
}
|
|
115
|
-
|
|
116
282
|
/** First non-internal IPv4 address, or null. Pure over an interfaces map. */
|
|
117
283
|
export function pickLanAddress(interfaces = os.networkInterfaces()) {
|
|
118
284
|
for (const entries of Object.values(interfaces || {})) {
|
|
119
285
|
for (const entry of entries || []) {
|
|
120
286
|
const family =
|
|
121
287
|
entry.family === "IPv4" || entry.family === 4 ? "IPv4" : entry.family;
|
|
122
|
-
if (
|
|
288
|
+
if (
|
|
289
|
+
family === "IPv4" &&
|
|
290
|
+
!entry.internal &&
|
|
291
|
+
isAdvertisableLanAddress(entry.address)
|
|
292
|
+
) {
|
|
123
293
|
return entry.address;
|
|
124
294
|
}
|
|
125
295
|
}
|
|
@@ -226,14 +396,22 @@ function stateFilePath(dir, port) {
|
|
|
226
396
|
}
|
|
227
397
|
|
|
228
398
|
/**
|
|
229
|
-
* Persist a running host's discovery record (0600 file, 0700 dir).
|
|
230
|
-
*
|
|
399
|
+
* Persist a running host's non-secret discovery record (0600 file, 0700 dir).
|
|
400
|
+
* Pairing/server tokens are intentionally rejected so status, crash recovery,
|
|
401
|
+
* diagnostics, and uploaded state artifacts cannot disclose them.
|
|
231
402
|
*/
|
|
232
403
|
export function writeRemoteControlState(state, { dir } = {}) {
|
|
233
404
|
const stateDir = dir || remoteControlStateDir();
|
|
234
405
|
fs.mkdirSync(stateDir, { recursive: true, mode: 0o700 });
|
|
235
406
|
const file = stateFilePath(stateDir, state.port);
|
|
236
|
-
|
|
407
|
+
const {
|
|
408
|
+
token: _token,
|
|
409
|
+
serverToken: _serverToken,
|
|
410
|
+
pairingToken: _pairingToken,
|
|
411
|
+
pairingUri: _pairingUri,
|
|
412
|
+
...safeState
|
|
413
|
+
} = state;
|
|
414
|
+
fs.writeFileSync(file, JSON.stringify(safeState, null, 2), {
|
|
237
415
|
encoding: "utf-8",
|
|
238
416
|
mode: 0o600,
|
|
239
417
|
});
|
|
@@ -279,8 +457,16 @@ export function readRemoteControlStates({ dir } = {}) {
|
|
|
279
457
|
const file = path.join(stateDir, name);
|
|
280
458
|
try {
|
|
281
459
|
const state = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
460
|
+
const legacyExposure = state.exposure == null;
|
|
461
|
+
const exposure = legacyExposure
|
|
462
|
+
? state.host && !isLoopbackBindHost(state.host)
|
|
463
|
+
? "legacy-unknown-lan"
|
|
464
|
+
: "legacy-unknown"
|
|
465
|
+
: state.exposure;
|
|
282
466
|
states.push({
|
|
283
467
|
...state,
|
|
468
|
+
exposure,
|
|
469
|
+
legacyExposure,
|
|
284
470
|
stateFile: file,
|
|
285
471
|
alive: isPidAlive(state.pid),
|
|
286
472
|
});
|