chainlesschain 0.165.4 → 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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-C__nnraW.js → AIOps-vfoDb6HZ.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CsKWHEZ-.js → ActionButton-Cm5f7p93.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BhIjfjWO.js → Analytics-yRuvgWxa.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BjuXFzWZ.js → AppLayout-BROsC4h1.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-k6whDkZJ.js → Artifacts-CimHApb1.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C4FBMYRh.js → Audit-DI8kr8zI.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C_vRrSXk.js → BackgroundAgents-DSLzKy-n.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CngFZeVp.js → Backup-DkH_n2r1.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B_H9EBRm.js → BaseInput-CKSrUoRA.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DLEC5kt7.js → Chat-ClwfMXUS.js} +3 -3
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CjMaqfL7.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-m3ClR4xh.js → Checkbox-BYTehcQG.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CmLSFrgp.js → Codegen-B8AMcp6J.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DFGQteHN.js → Col-BNSogPo3.js} +1 -1
- package/src/assets/web-panel/assets/{Community-EUUVORHE.js → Community-JxPvGb8O.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-2PSg0Yr7.js → Compact-DF7bFAgE.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CcKur7gB.js → Compliance-Bvhqnwrq.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DK_Fezfa.js → Cowork-BDS0CJvM.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Dbw89xl7.js → Cron-DqvRfxoQ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CH66h_Ec.js → Crosschain-CDcIvnqS.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D1igRb17.js → DID-D7jaUxU3.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DVAsrobk.js → Dashboard-B5A18wEV.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Dl7W8AQs.js → Dropdown-_6IVkGag.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D5tpaEQC.js → EmailListRenderer-C9PeiKCG.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-O_VQf8gq.js → FamilyGuardDashboard-DXjuEHrX.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BTVcgS9n.js → Federation-B4IAR9zv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BRnkJAIz.js → FormItemContext-CxQeSNx1.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-8OWSCkDj.js → GenericCardRenderer-B8GS-Y3N.js} +1 -1
- package/src/assets/web-panel/assets/{Git-WXLYTQj2.js → Git-BS8SouUX.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BezaUhtc.js → Governance-Dq9aYb5Q.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CuRay3Gu.js → Inference-BrCEe9AJ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DfWLDW1m.js → KnowledgeGraph-DybOpiY2.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C55y2pPY.js → Logs-DBpzbRfx.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CFyt8iUd.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Dk08Uycg.js → Marketplace-Cc7CCSCu.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C11fSB5V.js → McpTools-DFaIhOfu.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-mr0eZc97.js → Memory-KCqlp9ol.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C0iCSRbb.js → MobileBridge-Bys84ySN.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CY-xMG8Z.js → MobileProjects-B-6qU-je.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Bplqc_Wc.js → Mtc-BgY-fvCq.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BlzwsoSM.js → MtcAudit-B8pCmqQJ.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BBLmTty6.js → Multisig-BbOmYutA.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DtT1uf1W.js → NLProgramming-BocLQz_M.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DcqQAxTK.js → Notes-CSwbxEop.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-cb0kWoZe.js → NotificationSettings-CqFNSYG3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-kw9HZQNN.js → OrderTableRenderer-C6gMfAk7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DlTlbkUN.js → Organization-QrDXVdMJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-pp1qs-oY.js → Overflow-Cxntotjt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BESLnyYz.js → P2P-CIdBOwNq.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B-J1mGuf.js → PdhVaultBrowser-CT5aP8rD.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B36PeT1_.js → Permissions-DH7OM8XU.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CTK2ad-3.js → PersonalDataHub-CnJqbWce.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-zJNGXsac.js → Pipeline-5C3HG1kH.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-b9fz0Qf0.js → Privacy-B4yIp7xK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DjZbZgiB.js → ProjectInit-BMvt-2M0.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C-hHJXlD.js → ProjectSettings-CvSTEMRG.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BKBJxEek.js +1 -0
- package/src/assets/web-panel/assets/Providers-BdwUqFWN.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-A0yoQ3-3.js → QrScannerModal-DtnCKlTm.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrFgFJNC.js → QuickAsk-sTbgwyNr.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-KC8hF1g4.js → Recommend-Bzr14gSb.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DGTPIk6u.js → RemoteSession-C_TF5IaJ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-QmbwM7Yk.js → Reputation-GCAM4H2K.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BxjJylia.js → Row-Dir0r6OQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B7Wda1J6.js → RssFeed-CpGnlecX.js} +2 -2
- package/src/assets/web-panel/assets/{Search-_plQ_YSA.js → Search-DMMamC6u.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CD2YJZfx.js → Security-yqay5yG-.js} +4 -4
- package/src/assets/web-panel/assets/{Services-qQE7NmwE.js → Services-C5i2wh9G.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DgQqkFf7.js → Skeleton-vUqk35yj.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C4C0oFFt.js → Skills-DzcSkhot.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Px2N2B5c.js → Sla-CB6qbYNr.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DN6C3lQd.js → SpeechSettings-BHUI7cox.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-jm5aH_m7.js → SyncSettings-D-RrsxTJ.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DlkJcv67.js → Tasks-DFxdOJiq.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DyrTHUBs.js → Templates-SKAob_Aq.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-mC434PtL.js → Tenant-BPfsfHQ-.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-o0_s3NgK.js → Terminal-8iw7FpUf.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DAGlAXOw.js → TimelineRenderer-C4Gjj5Su.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BnulS_bs.js → Tokens-COYhN18e.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-suQbVXYM.js → Trigger-B-JzXcf6.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DbIJAPo5.js → Trust-BJ5bg0nI.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CQNpbIHi.js → UkeySign-BG_ByRMf.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D8aWCERk.js → VideoEditing-B3kOnSiU.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CuLpYfZc.js → Wallet-CcoGFnK7.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CsfG8ZMc.js → WebAuthn-DdT8vRC_.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BYsEAZbd.js → WorkflowEditor-CZu3M9N8.js} +1 -1
- package/src/assets/web-panel/assets/{chat-FT4Um3-I.js → chat-DxJt3Qba.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CKbS_Wlt.js → colors-DdYfwJaB.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CFJtzuJM.js → compact-item-BG3SUzwj.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B9kSRW6K.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-DR-x0Xpi.js → hasIn-B0apZ3KJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjEP7WkH.js → index-3jWkIu5j.js} +1 -1
- package/src/assets/web-panel/assets/{index-7jHr_A_k.js → index-B3rWuPMW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcfxhKXR.js → index-B5rLiEo0.js} +1 -1
- package/src/assets/web-panel/assets/{index-tqSEHPRT.js → index-BBPf3TpM.js} +1 -1
- package/src/assets/web-panel/assets/index-BETiL0a3.js +1 -0
- package/src/assets/web-panel/assets/{index-Cr2FLUsD.js → index-BIrJfqO4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt4fS2eL.js → index-BLIbGOLX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwR7yc2h.js → index-BP94CrBV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVPU43b1.js → index-BWF8_mkx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DShSkww-.js → index-BYINoRLK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzz0C1BZ.js → index-BZ_0qm4g.js} +1 -1
- package/src/assets/web-panel/assets/{index-KqDaCApw.js → index-BewKYrX9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW-t9n-q.js → index-BfWYsB9t.js} +1 -1
- package/src/assets/web-panel/assets/index-BhAVHuW8.js +1 -0
- package/src/assets/web-panel/assets/{index-B7osrXqy.js → index-Bissefox.js} +3 -3
- package/src/assets/web-panel/assets/{index-DW4BWjTk.js → index-BkvcEo4p.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdkIhfi-.js → index-Bos5_aMV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTxNy0ZU.js → index-C1C9i79A.js} +1 -1
- package/src/assets/web-panel/assets/{index-QBSeNVO3.js → index-CAhHirVH.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4LfPvWA.js → index-CNIxXMmN.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_enk9I_.js → index-CW93tZDv.js} +1 -1
- package/src/assets/web-panel/assets/{index-WE08gMJP.js → index-CWEuZWCK.js} +1 -1
- package/src/assets/web-panel/assets/{index-r2VI1Ikk.js → index-Cp6e-hGw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dap9c3eD.js → index-CpjZhgNd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWdqx7zI.js → index-CzmQElZy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGbPnC5x.js → index-DCn8IcxV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiNUEpSQ.js → index-DE76D-kA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtkFqPVA.js → index-DaIz0NAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGaXUsIs.js → index-Da_WBdLX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3ZX81mK.js → index-Dz49VOyQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK3TiKVq.js → index-Q2SCX-MI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJmIQ9XR.js → index-RD6O-0C6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvrCCqJt.js → index-SQqOlR3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI5hP_qy.js → index-ZFbps9po.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeSgw-WV.js → index-ZxtP8jG7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMGmg497.js → index-bP_VlDb4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfFCQHL3.js → index-kf57LJ93.js} +1 -1
- package/src/assets/web-panel/assets/{index-OSzjTzGM.js → index-oxRrmFO9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2JvmYRc.js → index-p1_8SbI1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Dd7n7VgI.js → initDefaultProps-BWJktcg8.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BwxouRw1.js → motion-BEXBzUFV.js} +1 -1
- package/src/assets/web-panel/assets/{move-DObN0PG9.js → move-BUKwn2sx.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B3nOUQc7.js → mtc-parser-FTI7btqQ.js} +1 -1
- package/src/assets/web-panel/assets/{omit-o9WU2Nbl.js → omit-CsLtSPH3.js} +1 -1
- package/src/assets/web-panel/assets/parsers-YNLm7sQ0.js +4 -0
- package/src/assets/web-panel/assets/{pickAttrs-CL_vvfQ_.js → pickAttrs-DWZxsT_S.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CYrM2tyK.js → placementArrow-Bcfs43CX.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-k_06cQ96.js → responsiveObserve-a9bcC1p-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CPKG0pxS.js → slide-BDTzgOXN.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDDEeLTw.js → statusUtils-Iq1j_HUg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cmb7RTh7.js → styleChecker-DAZEYxeh.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-e2IedC4N.js → useFlexGapSupport-DVkjyDrh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CE_gxVnm.js → useFs-wXTpNTHt.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BkMIIKMz.js → usePersonalDataHub-CtQM_aZr.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DW886XV1.js → vnode-hjEoG85v.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Dwo_adaf.js → zoom-BHVpyKg_.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +14 -2
- package/src/commands/artifacts.js +235 -0
- package/src/commands/background-session.js +100 -10
- package/src/commands/cowork.js +86 -57
- 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-location.js +20 -1
- package/src/commands/session-message.js +171 -0
- package/src/commands/session.js +75 -69
- package/src/data/changelog.json +29 -3
- package/src/gateways/ws/message-dispatcher.js +57 -0
- package/src/harness/mcp-client.js +141 -10
- package/src/lib/artifact-access-ledger.js +1 -0
- package/src/lib/artifact-deletion-ledger.js +1 -0
- package/src/lib/artifact-recovery.js +970 -0
- package/src/lib/artifact-workbench-projection.js +171 -0
- package/src/lib/background-session-transport.js +153 -27
- package/src/lib/dynamic-workflow-runtime.js +408 -1
- package/src/lib/execution-location-target.js +4 -2
- 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-stdio-executable-identity.js +16 -0
- package/src/lib/mcp-stdio-native-code-policy.js +74 -0
- package/src/lib/mcp-stdio-package-materialization.js +12 -3
- 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/process-execution-broker/index.js +112 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +57 -3
- package/src/lib/process-execution-broker/workspace-transaction.js +26 -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 +171 -12
- 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 +63 -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-xijcWkJe.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +0 -1
- package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +0 -1
- package/src/assets/web-panel/assets/Providers-Dz-CAYYU.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +0 -1
- package/src/assets/web-panel/assets/index-Bizb4hkw.js +0 -1
- package/src/assets/web-panel/assets/index-OHSl-xCh.js +0 -1
- package/src/assets/web-panel/assets/parsers-UEvh_ShA.js +0 -4
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
* cc remote-control status [--json]
|
|
11
11
|
* cc remote-control stop [--port N]
|
|
12
12
|
*
|
|
13
|
-
* `start` boots the WS server IN-PROCESS, then opens a loopback client
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* `start` boots the WS server IN-PROCESS, then opens a loopback client backed
|
|
14
|
+
* by durable possession-proof membership. A crash detaches transport without
|
|
15
|
+
* silently transferring host authority.
|
|
16
16
|
* With a relay configured you get the E2EE `chainlesschain://remote-session/…`
|
|
17
|
-
* URI
|
|
18
|
-
*
|
|
19
|
-
* contract the web-panel and Android client already speak.
|
|
17
|
+
* URI. Without one, the safe default is a loopback-only direct pairing URI;
|
|
18
|
+
* LAN binding, approval, and interruption are separate explicit opt-ins.
|
|
20
19
|
*/
|
|
21
20
|
|
|
22
21
|
import chalk from "chalk";
|
|
@@ -24,16 +23,24 @@ import { logger } from "../lib/logger.js";
|
|
|
24
23
|
import { loadConfig } from "../lib/config-manager.js";
|
|
25
24
|
import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
|
|
26
25
|
import { WsRpcClient } from "../lib/ws-rpc-client.js";
|
|
26
|
+
import { RemoteApprovalBridge } from "../lib/remote-approval-bridge.js";
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
assertDirectWsUrlAllowed,
|
|
29
|
+
isLoopbackBindHost,
|
|
29
30
|
pickLanAddress,
|
|
30
31
|
readRemoteControlStates,
|
|
31
32
|
removeRemoteControlState,
|
|
32
33
|
renderQrCode,
|
|
33
34
|
resolveRemoteControlOptions,
|
|
35
|
+
resolveRemoteControlWsUrl,
|
|
34
36
|
writeRemoteControlState,
|
|
35
37
|
} from "../lib/remote-control.js";
|
|
36
38
|
|
|
39
|
+
function websocketHost(host) {
|
|
40
|
+
const value = String(host || "127.0.0.1");
|
|
41
|
+
return value.includes(":") && !value.startsWith("[") ? `[${value}]` : value;
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
export function registerRemoteControlCommand(program) {
|
|
38
45
|
const cmd = program
|
|
39
46
|
.command("remote-control")
|
|
@@ -48,17 +55,23 @@ export function registerRemoteControlCommand(program) {
|
|
|
48
55
|
"Start the remote-control host (WS server + remote session) and print the pairing URI/QR",
|
|
49
56
|
)
|
|
50
57
|
.option("-p, --port <port>", "WS server port (default 18800)")
|
|
51
|
-
.option("--host <host>", "Bind host (default
|
|
58
|
+
.option("--host <host>", "Bind host (default 127.0.0.1)")
|
|
59
|
+
.option(
|
|
60
|
+
"--allow-lan",
|
|
61
|
+
"DANGEROUS: allow a private/LAN listener (defaults host to 0.0.0.0)",
|
|
62
|
+
)
|
|
52
63
|
.option("--token <token>", "Server auth token (default: auto-generated)")
|
|
53
64
|
.option(
|
|
54
65
|
"--relay-url <url>",
|
|
55
|
-
"Signaling relay URL for E2EE pairing
|
|
66
|
+
"Signaling relay URL for outbound E2EE pairing",
|
|
56
67
|
)
|
|
57
68
|
.option("--peer-id <id>", "Stable relay peer ID for this host")
|
|
58
69
|
.option(
|
|
59
70
|
"--scopes <csv>",
|
|
60
|
-
"
|
|
71
|
+
"Base device scopes (default observe,prompt; privileged scopes need their opt-ins)",
|
|
61
72
|
)
|
|
73
|
+
.option("--allow-approve", "Grant paired devices the approve scope")
|
|
74
|
+
.option("--allow-interrupt", "Grant paired devices the interrupt scope")
|
|
62
75
|
.option("--name <name>", "Remote session display name")
|
|
63
76
|
.option(
|
|
64
77
|
"--session <agentSessionId>",
|
|
@@ -90,6 +103,37 @@ export function registerRemoteControlCommand(program) {
|
|
|
90
103
|
});
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
/** Preserve an exact validated bind host instead of broadening it via serve. */
|
|
107
|
+
export function buildRemoteControlServerOptions(resolved) {
|
|
108
|
+
return {
|
|
109
|
+
port: resolved.port,
|
|
110
|
+
host: resolved.host,
|
|
111
|
+
token: resolved.token,
|
|
112
|
+
allowRemote: false,
|
|
113
|
+
remoteSessionRelayUrl: resolved.relayUrl,
|
|
114
|
+
remoteSessionPeerId: resolved.peerId,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function redactStatusUrl(value) {
|
|
119
|
+
if (!value) return value || null;
|
|
120
|
+
try {
|
|
121
|
+
const parsed = new URL(String(value));
|
|
122
|
+
const hadCredentials = Boolean(parsed.username || parsed.password);
|
|
123
|
+
const hadQuery = Boolean(parsed.search);
|
|
124
|
+
parsed.username = "";
|
|
125
|
+
parsed.password = "";
|
|
126
|
+
parsed.search = "";
|
|
127
|
+
parsed.hash = "";
|
|
128
|
+
return (
|
|
129
|
+
parsed.toString() +
|
|
130
|
+
(hadCredentials || hadQuery ? "[credentials/query redacted]" : "")
|
|
131
|
+
);
|
|
132
|
+
} catch {
|
|
133
|
+
return "[invalid URL redacted]";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
93
137
|
/**
|
|
94
138
|
* Boot the host: server runtime + loopback host client + remote session +
|
|
95
139
|
* pairing printout + discovery state. Resolves `{code}` (0 = serving; the
|
|
@@ -99,6 +143,7 @@ export function registerRemoteControlCommand(program) {
|
|
|
99
143
|
export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
100
144
|
const log = _deps.log || ((m) => console.log(m));
|
|
101
145
|
const errLog = _deps.err || ((m) => logger.error(m));
|
|
146
|
+
const warnLog = _deps.warn || ((m) => logger.warn(m));
|
|
102
147
|
const env = _deps.env || process.env;
|
|
103
148
|
const stateDir = _deps.stateDir; // undefined → default dir
|
|
104
149
|
let config = {};
|
|
@@ -114,6 +159,9 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
114
159
|
flags: {
|
|
115
160
|
port: options.port,
|
|
116
161
|
host: options.host,
|
|
162
|
+
allowLan: options.allowLan,
|
|
163
|
+
allowApprove: options.allowApprove,
|
|
164
|
+
allowInterrupt: options.allowInterrupt,
|
|
117
165
|
token: options.token,
|
|
118
166
|
relayUrl: options.relayUrl,
|
|
119
167
|
peerId: options.peerId,
|
|
@@ -127,6 +175,40 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
127
175
|
errLog(err.message);
|
|
128
176
|
return { code: 4 };
|
|
129
177
|
}
|
|
178
|
+
const localControlHost =
|
|
179
|
+
resolved.host === "0.0.0.0"
|
|
180
|
+
? "127.0.0.1"
|
|
181
|
+
: resolved.host === "::"
|
|
182
|
+
? "::1"
|
|
183
|
+
: resolved.host;
|
|
184
|
+
const localControlUrl = `ws://${websocketHost(localControlHost)}:${resolved.port}`;
|
|
185
|
+
|
|
186
|
+
for (const warning of resolved.warnings) warnLog(warning);
|
|
187
|
+
if (resolved.lanAccessible) {
|
|
188
|
+
warnLog(
|
|
189
|
+
`LAN access enabled on ${resolved.host}:${resolved.port}. This exposes the ` +
|
|
190
|
+
"authenticated WebSocket endpoint to the local network; use only on a trusted network." +
|
|
191
|
+
(resolved.relayUrl
|
|
192
|
+
? ""
|
|
193
|
+
: " Direct pairing uses plaintext ws:// with bearer credentials; prefer --relay-url when available."),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const lanAddress = resolved.lanAccessible
|
|
197
|
+
? _deps.lanAddress ||
|
|
198
|
+
(_deps.pickLanAddress ? _deps.pickLanAddress() : pickLanAddress())
|
|
199
|
+
: null;
|
|
200
|
+
const preflightWsUrl = resolveRemoteControlWsUrl(resolved, { lanAddress });
|
|
201
|
+
if (
|
|
202
|
+
!resolved.relayUrl &&
|
|
203
|
+
resolved.lanAccessible &&
|
|
204
|
+
isLoopbackBindHost(new URL(preflightWsUrl).hostname)
|
|
205
|
+
) {
|
|
206
|
+
errLog(
|
|
207
|
+
"LAN binding is enabled but no private LAN address can be advertised; " +
|
|
208
|
+
"use --host with a private interface address or configure a relay",
|
|
209
|
+
);
|
|
210
|
+
return { code: 4 };
|
|
211
|
+
}
|
|
130
212
|
|
|
131
213
|
// Refuse to double-start on a port that already has a LIVE host.
|
|
132
214
|
const existing = readRemoteControlStates({ dir: stateDir }).find(
|
|
@@ -146,14 +228,7 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
146
228
|
_deps.startServer ||
|
|
147
229
|
(async () =>
|
|
148
230
|
createAgentRuntimeFactory()
|
|
149
|
-
.createServerRuntime(
|
|
150
|
-
port: resolved.port,
|
|
151
|
-
host: resolved.host,
|
|
152
|
-
token: resolved.token,
|
|
153
|
-
allowRemote: resolved.host !== "127.0.0.1",
|
|
154
|
-
remoteSessionRelayUrl: resolved.relayUrl,
|
|
155
|
-
remoteSessionPeerId: resolved.peerId,
|
|
156
|
-
})
|
|
231
|
+
.createServerRuntime(buildRemoteControlServerOptions(resolved))
|
|
157
232
|
.startServer());
|
|
158
233
|
server = await startServer(resolved);
|
|
159
234
|
} catch (err) {
|
|
@@ -161,19 +236,19 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
161
236
|
return { code: 1 };
|
|
162
237
|
}
|
|
163
238
|
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
new WsRpcClient({ url: `ws://127.0.0.1:${resolved.port}` });
|
|
169
|
-
let remoteSession;
|
|
170
|
-
let pairing;
|
|
239
|
+
// Create an agent session first when the caller did not select one. This
|
|
240
|
+
// bootstrap client is not the Remote Session host and never receives pairing
|
|
241
|
+
// authority.
|
|
242
|
+
let sessionClient = null;
|
|
171
243
|
let agentSessionId = options.session || null;
|
|
172
244
|
try {
|
|
173
|
-
await client.connect();
|
|
174
|
-
await client.auth(resolved.token);
|
|
175
245
|
if (!agentSessionId) {
|
|
176
|
-
|
|
246
|
+
sessionClient = new WsRpcClient({
|
|
247
|
+
url: localControlUrl,
|
|
248
|
+
});
|
|
249
|
+
await sessionClient.connect();
|
|
250
|
+
await sessionClient.auth(resolved.token);
|
|
251
|
+
const created = await sessionClient.request("session-create", {
|
|
177
252
|
sessionType: "agent",
|
|
178
253
|
projectRoot: _deps.cwd || process.cwd(),
|
|
179
254
|
});
|
|
@@ -186,16 +261,42 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
186
261
|
throw new Error("session-create returned no sessionId");
|
|
187
262
|
}
|
|
188
263
|
}
|
|
189
|
-
|
|
190
|
-
|
|
264
|
+
} catch (err) {
|
|
265
|
+
errLog(`Failed to create agent session: ${err.message}`);
|
|
266
|
+
sessionClient?.close();
|
|
267
|
+
try {
|
|
268
|
+
await server.stop?.();
|
|
269
|
+
} catch {
|
|
270
|
+
// best-effort teardown
|
|
271
|
+
}
|
|
272
|
+
return { code: 1 };
|
|
273
|
+
} finally {
|
|
274
|
+
sessionClient?.close();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Standalone start uses the same durable, possession-proof membership host
|
|
278
|
+
// as headless/REPL approvals. A crash detaches the transport; restarting with
|
|
279
|
+
// the same agent session resumes or re-enables the durable membership.
|
|
280
|
+
const createBridge =
|
|
281
|
+
_deps.createBridge ||
|
|
282
|
+
((bridgeOptions) => new RemoteApprovalBridge(bridgeOptions));
|
|
283
|
+
let bridge;
|
|
284
|
+
try {
|
|
285
|
+
bridge = createBridge({
|
|
286
|
+
wsUrl: localControlUrl,
|
|
287
|
+
token: resolved.token,
|
|
288
|
+
agentSessionId,
|
|
191
289
|
name: resolved.name,
|
|
192
290
|
scopes: resolved.scopes,
|
|
291
|
+
approvalStateFile: _deps.approvalStateFile,
|
|
292
|
+
membershipHostStateFile: _deps.membershipHostStateFile,
|
|
293
|
+
membershipHostWitnessFile: _deps.membershipHostWitnessFile,
|
|
294
|
+
membershipHostAuthorityLockFile: _deps.membershipHostAuthorityLockFile,
|
|
193
295
|
});
|
|
194
|
-
|
|
195
|
-
pairing = createdRemote.pairing;
|
|
296
|
+
await bridge.start();
|
|
196
297
|
} catch (err) {
|
|
197
|
-
errLog(`Failed to create remote session: ${err.message}`);
|
|
198
|
-
|
|
298
|
+
errLog(`Failed to create durable remote session: ${err.message}`);
|
|
299
|
+
await bridge?.close?.().catch(() => undefined);
|
|
199
300
|
try {
|
|
200
301
|
await server.stop?.();
|
|
201
302
|
} catch {
|
|
@@ -203,37 +304,63 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
203
304
|
}
|
|
204
305
|
return { code: 1 };
|
|
205
306
|
}
|
|
307
|
+
const client = bridge.client;
|
|
308
|
+
const pairing = bridge.pairing;
|
|
309
|
+
const remoteSession = {
|
|
310
|
+
sessionId: bridge.remoteSessionId,
|
|
311
|
+
};
|
|
206
312
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
313
|
+
let relayMode;
|
|
314
|
+
let wsUrl;
|
|
315
|
+
let pairingUri;
|
|
316
|
+
try {
|
|
317
|
+
relayMode = Boolean(pairing?.uri);
|
|
318
|
+
if (resolved.relayUrl && !relayMode) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
"Relay pairing was requested but no relay URI was issued; refusing direct fallback",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
wsUrl = resolveRemoteControlWsUrl(resolved, { lanAddress });
|
|
324
|
+
if (!relayMode) assertDirectWsUrlAllowed(wsUrl);
|
|
325
|
+
pairingUri = bridge.pairingInfo({ lanWsUrl: wsUrl })?.uri;
|
|
326
|
+
if (!pairingUri) throw new Error("Remote session returned no pairing URI");
|
|
327
|
+
} catch (err) {
|
|
328
|
+
await bridge.close?.().catch(() => undefined);
|
|
329
|
+
await server.stop?.().catch(() => undefined);
|
|
330
|
+
errLog(`Failed to finish remote-control startup: ${err.message}`);
|
|
331
|
+
return { code: 1 };
|
|
332
|
+
}
|
|
221
333
|
|
|
222
334
|
const state = {
|
|
223
335
|
pid: process.pid,
|
|
224
336
|
port: resolved.port,
|
|
225
337
|
host: resolved.host,
|
|
226
338
|
wsUrl,
|
|
227
|
-
token: resolved.token,
|
|
228
339
|
mode: relayMode ? "relay" : "direct",
|
|
229
|
-
|
|
340
|
+
exposure: relayMode
|
|
341
|
+
? "outbound-relay"
|
|
342
|
+
: resolved.lanAccessible
|
|
343
|
+
? "direct-lan"
|
|
344
|
+
: "loopback",
|
|
345
|
+
lanAccessible: resolved.lanAccessible,
|
|
346
|
+
relayUrl: redactStatusUrl(resolved.relayUrl),
|
|
230
347
|
peerId: resolved.peerId || null,
|
|
231
348
|
agentSessionId,
|
|
232
349
|
remoteSessionId: remoteSession.sessionId,
|
|
233
350
|
scopes: pairing.scopes,
|
|
234
351
|
startedAt: new Date().toISOString(),
|
|
235
352
|
};
|
|
236
|
-
|
|
353
|
+
let stateFile;
|
|
354
|
+
try {
|
|
355
|
+
stateFile = (_deps.writeState || writeRemoteControlState)(state, {
|
|
356
|
+
dir: stateDir,
|
|
357
|
+
});
|
|
358
|
+
} catch (err) {
|
|
359
|
+
await bridge.close?.().catch(() => undefined);
|
|
360
|
+
await server.stop?.().catch(() => undefined);
|
|
361
|
+
errLog(`Failed to finish remote-control startup: ${err.message}`);
|
|
362
|
+
return { code: 1 };
|
|
363
|
+
}
|
|
237
364
|
const cleanup = () => {
|
|
238
365
|
removeRemoteControlState(resolved.port, { dir: stateDir });
|
|
239
366
|
};
|
|
@@ -241,70 +368,102 @@ export async function runRemoteControlStart(options = {}, _deps = {}) {
|
|
|
241
368
|
process.once("SIGINT", cleanup);
|
|
242
369
|
process.once("SIGTERM", cleanup);
|
|
243
370
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
371
|
+
try {
|
|
372
|
+
if (options.json) {
|
|
373
|
+
log(
|
|
374
|
+
JSON.stringify(
|
|
375
|
+
{
|
|
376
|
+
mode: state.mode,
|
|
377
|
+
exposure: state.exposure,
|
|
378
|
+
host: resolved.host,
|
|
379
|
+
lanAccessible: resolved.lanAccessible,
|
|
380
|
+
wsUrl,
|
|
381
|
+
port: resolved.port,
|
|
382
|
+
agentSessionId,
|
|
383
|
+
remoteSessionId: remoteSession.sessionId,
|
|
384
|
+
pairingUri,
|
|
385
|
+
pairing: {
|
|
386
|
+
token: pairing.token,
|
|
387
|
+
scopes: pairing.scopes,
|
|
388
|
+
expiresAt: pairing.expiresAt,
|
|
389
|
+
},
|
|
390
|
+
stateFile,
|
|
258
391
|
},
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
`
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
);
|
|
293
|
-
} else {
|
|
392
|
+
null,
|
|
393
|
+
2,
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
} else {
|
|
397
|
+
log("");
|
|
398
|
+
log(chalk.bold(" Remote Control ready"));
|
|
399
|
+
log("");
|
|
400
|
+
log(
|
|
401
|
+
` Mode: ${
|
|
402
|
+
state.mode === "relay"
|
|
403
|
+
? chalk.cyan("relay (E2EE, outbound)")
|
|
404
|
+
: state.exposure === "direct-lan"
|
|
405
|
+
? chalk.yellow("direct LAN (explicit opt-in)")
|
|
406
|
+
: chalk.cyan("direct loopback")
|
|
407
|
+
}`,
|
|
408
|
+
);
|
|
409
|
+
log(` WS server: ${chalk.cyan(wsUrl)}`);
|
|
410
|
+
log(` Agent sess: ${agentSessionId}`);
|
|
411
|
+
log(` Remote sess: ${remoteSession.sessionId}`);
|
|
412
|
+
log(` Scopes: ${pairing.scopes.join(", ")}`);
|
|
413
|
+
log(
|
|
414
|
+
` Token exp: ${pairing.expiresAt ? new Date(pairing.expiresAt).toISOString() : "n/a"} ${chalk.dim("(one-time; re-run start for another device)")}`,
|
|
415
|
+
);
|
|
416
|
+
log("");
|
|
417
|
+
log(
|
|
418
|
+
relayMode || resolved.lanAccessible
|
|
419
|
+
? " Pairing URI (open in ChainlessChain mobile / paste in web panel):"
|
|
420
|
+
: " Pairing URI (local clients on this machine only):",
|
|
421
|
+
);
|
|
422
|
+
log("");
|
|
423
|
+
log(" " + chalk.green(pairingUri));
|
|
424
|
+
if (!relayMode && !resolved.lanAccessible) {
|
|
294
425
|
log("");
|
|
295
426
|
log(
|
|
296
|
-
chalk.
|
|
297
|
-
"
|
|
427
|
+
chalk.yellow(
|
|
428
|
+
" Loopback only: use --allow-lan on a trusted network or configure --relay-url for another device.",
|
|
298
429
|
),
|
|
299
430
|
);
|
|
300
431
|
}
|
|
432
|
+
if (options.qr !== false && (relayMode || resolved.lanAccessible)) {
|
|
433
|
+
const qr = await renderQrCode(pairingUri, _deps);
|
|
434
|
+
if (qr) {
|
|
435
|
+
log("");
|
|
436
|
+
log(
|
|
437
|
+
qr
|
|
438
|
+
.split("\n")
|
|
439
|
+
.map((line) => " " + line)
|
|
440
|
+
.join("\n"),
|
|
441
|
+
);
|
|
442
|
+
} else {
|
|
443
|
+
log("");
|
|
444
|
+
log(
|
|
445
|
+
chalk.dim(
|
|
446
|
+
" (install the optional `qrcode` package for a terminal QR)",
|
|
447
|
+
),
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
log("");
|
|
452
|
+
log(chalk.dim(" Press Ctrl+C to stop"));
|
|
453
|
+
log("");
|
|
301
454
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
455
|
+
} catch (err) {
|
|
456
|
+
process.removeListener("exit", cleanup);
|
|
457
|
+
process.removeListener("SIGINT", cleanup);
|
|
458
|
+
process.removeListener("SIGTERM", cleanup);
|
|
459
|
+
cleanup();
|
|
460
|
+
await bridge.close?.().catch(() => undefined);
|
|
461
|
+
await server.stop?.().catch(() => undefined);
|
|
462
|
+
errLog(`Failed to finish remote-control startup: ${err.message}`);
|
|
463
|
+
return { code: 1 };
|
|
305
464
|
}
|
|
306
465
|
|
|
307
|
-
return { code: 0, client, server, state, pairingUri };
|
|
466
|
+
return { code: 0, client, bridge, server, state, pairingUri };
|
|
308
467
|
}
|
|
309
468
|
|
|
310
469
|
export function runRemoteControlStatus(options = {}, _deps = {}) {
|
|
@@ -320,9 +479,20 @@ export function runRemoteControlStatus(options = {}, _deps = {}) {
|
|
|
320
479
|
if (options.json) {
|
|
321
480
|
log(
|
|
322
481
|
JSON.stringify(
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
states.map(
|
|
482
|
+
// Defense in depth for legacy state files written before tokens were
|
|
483
|
+
// removed from discovery state.
|
|
484
|
+
states.map(
|
|
485
|
+
({
|
|
486
|
+
token: _token,
|
|
487
|
+
serverToken: _serverToken,
|
|
488
|
+
pairingToken: _pairingToken,
|
|
489
|
+
pairingUri: _pairingUri,
|
|
490
|
+
...rest
|
|
491
|
+
}) => ({
|
|
492
|
+
...rest,
|
|
493
|
+
relayUrl: redactStatusUrl(rest.relayUrl),
|
|
494
|
+
}),
|
|
495
|
+
),
|
|
326
496
|
null,
|
|
327
497
|
2,
|
|
328
498
|
),
|
|
@@ -343,7 +513,22 @@ export function runRemoteControlStatus(options = {}, _deps = {}) {
|
|
|
343
513
|
const badge = state.alive ? chalk.green("running") : chalk.yellow("stale");
|
|
344
514
|
log(
|
|
345
515
|
` ${badge} port ${String(state.port).padEnd(6)} pid ${String(state.pid).padEnd(8)} ` +
|
|
346
|
-
`${(state.mode || "?").padEnd(7)}
|
|
516
|
+
`${(state.mode || "?").padEnd(7)} ${(state.exposure || "?").padEnd(18)} ` +
|
|
517
|
+
`host ${state.host || "?"} session ${state.agentSessionId || "?"}`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
states.some(
|
|
522
|
+
(state) =>
|
|
523
|
+
state.alive &&
|
|
524
|
+
state.legacyExposure &&
|
|
525
|
+
state.exposure === "legacy-unknown-lan",
|
|
526
|
+
)
|
|
527
|
+
) {
|
|
528
|
+
log(
|
|
529
|
+
chalk.red(
|
|
530
|
+
" Warning: a legacy host may still be LAN-exposed. Stop and restart it to apply the loopback default.",
|
|
531
|
+
),
|
|
347
532
|
);
|
|
348
533
|
}
|
|
349
534
|
log("");
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
buildExecutionLocationCatalog,
|
|
6
6
|
buildExecutionLocationHandoffPreview,
|
|
7
7
|
computeExecutionLocationTargetFactsDigest,
|
|
8
|
+
createExecutionLocationTargetAttestation,
|
|
8
9
|
} from "../lib/execution-location-contract.js";
|
|
9
10
|
import { captureAmbientExecutionLocation } from "../lib/execution-location-runtime.js";
|
|
10
11
|
import {
|
|
@@ -333,6 +334,24 @@ export function prepareSessionReplicaHandoff(sessionId, options, deps = {}) {
|
|
|
333
334
|
if (targetFactsDigest !== String(options.expectedTargetFactsDigest || "")) {
|
|
334
335
|
throw new Error("target facts changed before location handoff append");
|
|
335
336
|
}
|
|
337
|
+
if (!/^sha256:[a-f0-9]{64}$/u.test(String(options.attestationDigest || ""))) {
|
|
338
|
+
throw new Error("source target attestation digest is invalid");
|
|
339
|
+
}
|
|
340
|
+
// observedAt is intentionally excluded from targetFactsDigest but included
|
|
341
|
+
// in attestationDigest. The source-side probe and this target-side prepare
|
|
342
|
+
// are separate process invocations, so a real target can never reproduce
|
|
343
|
+
// the source probe's timestamp. Bind the handoff event to this fresh target
|
|
344
|
+
// observation; the source rehashes its receipt and immediately verifies the
|
|
345
|
+
// canonical target projection before it is allowed to resume.
|
|
346
|
+
const targetAttestation = createExecutionLocationTargetAttestation({
|
|
347
|
+
profileDigest: options.profileDigest,
|
|
348
|
+
sourceSessionId: sessionId,
|
|
349
|
+
sourceHeadHash: options.expectedHeadHash,
|
|
350
|
+
sourceEventCount: Number(options.expectedEventCount),
|
|
351
|
+
targetEvidenceId: options.targetEvidenceId,
|
|
352
|
+
baseCommit: binding.source.git.commit,
|
|
353
|
+
binding,
|
|
354
|
+
});
|
|
336
355
|
return (
|
|
337
356
|
deps.installSessionReplicaWithLocationHandoff ||
|
|
338
357
|
installSessionReplicaWithLocationHandoff
|
|
@@ -348,7 +367,7 @@ export function prepareSessionReplicaHandoff(sessionId, options, deps = {}) {
|
|
|
348
367
|
profileDigest: options.profileDigest,
|
|
349
368
|
targetEvidenceId: options.targetEvidenceId,
|
|
350
369
|
targetFactsDigest,
|
|
351
|
-
attestationDigest:
|
|
370
|
+
attestationDigest: targetAttestation.attestationDigest,
|
|
352
371
|
binding,
|
|
353
372
|
},
|
|
354
373
|
);
|