chainlesschain 0.162.159 → 0.162.161
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-ByIiF5bT.js → AIOps-DMn528q-.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-NlgSFZq5.js → ActionButton-D9ZcDCJL.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D17cOUcb.js → Analytics-A_K-8PX0.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B4mEZcbl.js → AppLayout-D4pFW3X6.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CASsmyQw.js → Artifacts-Dz46COAd.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DYAia2e2.js → Audit-cbMjbyg0.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C1lvYUSu.js → BackgroundAgents-Dtf88GAj.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B6agK6bA.js → Backup-C5KRxn52.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DM43R2vy.js → BaseInput-Dd2UK3BN.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Dtdp3eXp.js → Chat-lptKTFdG.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BL5c_OQ1.js → ChatBubbleRenderer-C2KLtwHz.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-D2xf7uZt.js → Checkbox-_yspG6rG.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D3j45jJL.js → Codegen-BGq_GAha.js} +1 -1
- package/src/assets/web-panel/assets/{Col-voPJ9fUZ.js → Col-CePor71g.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DEsyTESF.js → Community-CLOWhIT7.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-OHV_QXLD.js → Compact-CivJt2vT.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-eDTJWC-J.js → Compliance-Btmo1GGE.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BBvy-6d1.js → Cowork-Ct35OlG4.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Dw2eoJxu.js → Cron-CTc2GmhB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DQHms_Bh.js → Crosschain-ZtiBETk1.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cyu7Id1c.js → DID-BNqMQQXw.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-sIDzkG92.js → Dashboard-BmzZd-9V.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DUW3T2ey.js → Dropdown-CMVgvP2M.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CECELXW8.js → EmailListRenderer-CcqQFAfT.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BIZR37ov.js → FamilyGuardDashboard-CTqB1_4V.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BEZIQX84.js → Federation-CiKOOzlb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BcdUMznL.js → FormItemContext-Wk-OT0gr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DsN_fr5H.js → GenericCardRenderer-DbEQT6ue.js} +1 -1
- package/src/assets/web-panel/assets/{Git-63LhjBx2.js → Git-0ZvCPSD9.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYtQtIcH.js → Governance-CmVuKkPb.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BwR6UUox.js → Inference-CTWE3Jwg.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-D9lVNTUA.js → KnowledgeGraph-CBEIF00L.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C4q5h7Ut.js → Logs-DLhxDwVG.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Plksliuw.js → Marketplace-BgsCBrnc.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DZUFfeuG.js → McpTools-ClBUGDS2.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-NoDYt29V.js → Memory-CnG_ZnYw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CEXGTkdh.js → MobileBridge-BUOAbR1Z.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DnbJkbZo.js → MobileProjects-Dnt_9-WB.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DTAa0cwy.js → Mtc-CLjt4kCD.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-CVV7LnNS.js → MtcAudit-f24U9Aes.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BqqGrjt_.js → Multisig-GrmGQbSq.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C4WP8DRp.js → NLProgramming-DADaWn-n.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Zo4DJUVI.js → Notes-xtf5RX6C.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DBcjRaz2.js → NotificationSettings-Cd27Rgtx.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-fXRxlDjl.js +1 -0
- package/src/assets/web-panel/assets/{Organization-BT9WPBOX.js → Organization-joCFlNB0.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D6b4bZ3q.js → Overflow-B1HoURq0.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cw3OsYx-.js → P2P-DiwsKbT7.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C0nyiK2b.js → PdhVaultBrowser-Xe-BsW6S.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-zVBZEB_C.js → Permissions-C23qi8kl.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BOspsFew.js → PersonalDataHub-BS1syX2f.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CQWfZ4XH.js → Pipeline-DRYwwDyh.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-C2oGJ_WL.js → Privacy-BQKaDHPH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BZMR4qY1.js → ProjectInit-BC6F9xiG.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DAujoP63.js → ProjectSettings-ClFPc1Nd.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +1 -0
- package/src/assets/web-panel/assets/{Providers-D_RGHM-v.js → Providers-CLWuOUNt.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Mf6KfL2J.js → QrScannerModal-DiqULTxb.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-5D8URoI_.js → QuickAsk-9A03P-E_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Df4kC8Gy.js → Recommend-CJZAnbq_.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DGi3f3vO.js → RemoteSession-C9A1R77h.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-jmkGFNWb.js → Reputation-BIZc6Rky.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DP6Pcu3m.js → Row-DfIPNtuG.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D60hSuPO.js → RssFeed-C5znFcQ1.js} +3 -3
- package/src/assets/web-panel/assets/{Search-0mf1BQUK.js → Search-DSs2z3OX.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CfZtLFYV.js → Security-CV6LcrLY.js} +4 -4
- package/src/assets/web-panel/assets/{Services-D9zpZLWz.js → Services-cmhoX0Xj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-D1_44eMD.js → Skeleton-tCsg2po7.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-cVFa8zkz.js → Skills-BK1KUDiy.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cinpzc3L.js → Sla-yRgyeA82.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-bqdiJE_P.js → SpeechSettings-c1mo1mlp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dgl_SLgj.js → SyncSettings-koIAfuT3.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DawWAEhy.js → Tasks-DkkreAaB.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Ca0nY651.js → Templates-fy5Ctf9M.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CgaGEhLm.js → Tenant-Bw70Jf6S.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D-9EnpRx.js → Terminal-BKh6Rx-4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CSOxDHZ6.js → TimelineRenderer-Dg54WP0c.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpwLZpL3.js → Tokens-BvvD5SxJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DgowwBK5.js → Trigger-B7Ug9x4e.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BZutapjE.js → Trust-BjvuMORY.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-QZPt8_Sp.js → UkeySign-CZqhkx8p.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DlQx5UDJ.js → VideoEditing-ClR50lhX.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ZfdTgtWA.js → Wallet-DlfppYjx.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CAp0y_Cr.js → WebAuthn-BirgVr8B.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BVITZEmr.js → WorkflowEditor-B8DP_bax.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CC2tPaNg.js → chat-DA8AUZzL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-31brc9CA.js → colors-C38pLGp1.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DLMkC9C-.js → compact-item-81rtr98o.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BMtveI6F.js → createContext-D34ixq8t.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DbD6WHW2.js → hasIn-D4bzqTrZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoO4WEJB.js → index-9qoVpPt6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJhREOlG.js → index-B0QArPQt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnFLzejT.js → index-B8mkLM4x.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbc2JeQG.js → index-BIP62a6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFXKbOek.js → index-BP-zkXK8.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-cGlCDW.js → index-BS3M5H9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-AaIKG60c.js → index-BXd2_LmH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEXa-4zd.js → index-BlxEphl7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZl-UXB6.js → index-BqIhLAbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOM-VFlv.js → index-BtGxuQrO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcVszn2b.js → index-BvD7b2IK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C21pdBqs.js → index-BvL_3J_n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJvxFyIz.js → index-COe_cOLJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-ALvoNTRF.js → index-CP5alL4t.js} +1 -1
- package/src/assets/web-panel/assets/index-CY1NURWZ.js +1 -0
- package/src/assets/web-panel/assets/{index-Dm0byjm1.js → index-CbhE3j5j.js} +1 -1
- package/src/assets/web-panel/assets/{index-WQUO2vvK.js → index-Cd3KuNK5.js} +1 -1
- package/src/assets/web-panel/assets/{index-bdaJwTXJ.js → index-CoVgZw_b.js} +1 -1
- package/src/assets/web-panel/assets/{index-COHHQ9cU.js → index-CuS-UPoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7LHNw_N.js → index-D0Ih7Hwn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cod97Urs.js → index-D3zCCZRS.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cwbqh0KZ.js → index-D6GfvEQG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKAB6-6V.js → index-DBFSo1NF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEwjBA9q.js → index-DFHa1HIg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DndNnYc0.js → index-DFyWKOBD.js} +1 -1
- package/src/assets/web-panel/assets/{index-3oBsXZmF.js → index-DMgnRwrK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_eUKGYP.js → index-DUQLXLQM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUmLMFsy.js → index-D_2VYPr1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPpS-lGb.js → index-DakfFZ-k.js} +1 -1
- package/src/assets/web-panel/assets/{index-ep6JchNG.js → index-Ddk0e_Pi.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7cV0tu0.js → index-DkHqMnn3.js} +1 -1
- package/src/assets/web-panel/assets/{index-jG6h22Qg.js → index-Dmo3dFHb.js} +1 -1
- package/src/assets/web-panel/assets/index-DokUYqpq.js +1 -0
- package/src/assets/web-panel/assets/{index-C1PfeI88.js → index-DvfCEJG-.js} +1 -1
- package/src/assets/web-panel/assets/{index-ntVQqSML.js → index-E1OJRXs4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWgfipcC.js → index-F76w1NiL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiRQUoyE.js → index-ZRJRItm0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFRLmCR3.js → index-fI5sXMLg.js} +1 -1
- package/src/assets/web-panel/assets/{index-D67kIagl.js → index-tKiC8T-4.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D5UoBRhN.js → initDefaultProps-ByyvflIR.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DltOw0CR.js → motion-B3FQm2By.js} +1 -1
- package/src/assets/web-panel/assets/{move-_oPkk5zj.js → move-BSfns6Ef.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-EES8ROCS.js → mtc-parser-CTG1Ieri.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B-_39x_M.js → omit-DObM_2j6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-l6D0yJHz.js → pickAttrs-DOfelQ1t.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DIeUK31L.js → placementArrow-CHZILds_.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dG4EzUQo.js → responsiveObserve-REbucKSx.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWFzYGdH.js → slide-C6EZzD7o.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CECdTA5H.js → statusUtils-BxkWS-g3.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D6kP-mMK.js → styleChecker-Chklyoq6.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BWfna_rd.js → useFlexGapSupport-CRVwKrrI.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Bb-j6rzo.js → useFs-DV24ik_J.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D5OIsYM4.js → usePersonalDataHub-C7vIo4Pg.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNpwzFTM.js → vnode-By-v5GlF.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-vNLpFQBW.js → zoom-B1w9Am3r.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +15 -1
- package/src/commands/agent.js +72 -6
- package/src/commands/background-session.js +46 -2
- package/src/commands/mcp.js +46 -0
- package/src/commands/session.js +70 -0
- package/src/data/changelog.json +12 -2
- package/src/gateways/ws/background-agent-protocol.js +88 -14
- package/src/harness/jsonl-session-store.js +70 -1
- package/src/harness/mcp-client.js +50 -0
- package/src/lib/agent-authority.js +251 -0
- package/src/lib/agent-sandbox.js +68 -0
- package/src/lib/agents.js +12 -4
- package/src/lib/api-key-helper.js +62 -0
- package/src/lib/background-agent-phase.js +100 -0
- package/src/lib/background-agent-supervisor.js +320 -8
- package/src/lib/backpressure-policy.js +105 -0
- package/src/lib/capability-negotiation.js +212 -0
- package/src/lib/credential-guard.js +11 -0
- package/src/lib/credential-proxy.js +182 -0
- package/src/lib/dependency-install-policy.js +130 -0
- package/src/lib/event-seq-replay.js +167 -0
- package/src/lib/exit-codes.cjs +77 -0
- package/src/lib/goal-condition-engine.js +369 -0
- package/src/lib/headless-manifest.js +188 -0
- package/src/lib/hook-runner.cjs +70 -4
- package/src/lib/ide-context-redaction.js +130 -0
- package/src/lib/ide-context.js +128 -13
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/llm-config-defaults.js +24 -8
- package/src/lib/project-instructions.js +98 -1
- package/src/lib/project-mcp-trust.js +87 -0
- package/src/lib/remote-path-mapping.js +229 -0
- package/src/lib/schedule-planner.js +136 -0
- package/src/lib/sensitive-file-guard.js +51 -0
- package/src/lib/sub-agent-context.js +7 -3
- package/src/repl/bg-dashboard.js +13 -12
- package/src/runtime/agent-core.js +113 -10
- package/src/runtime/coding-agent-contract-shared.cjs +19 -2
- package/src/runtime/headless-runner.js +66 -7
- package/src/runtime/headless-stream.js +224 -12
- package/src/runtime/mcp-config.js +32 -1
- package/src/runtime/system-prompt.js +3 -0
- package/src/workers/background-agent-worker.js +37 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DexLlXfN.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BCQel9bD.js +0 -1
- package/src/assets/web-panel/assets/Projects-DvH_P2Uq.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CB8pPOtd.js +0 -1
- package/src/assets/web-panel/assets/index-BhXiaiev.js +0 -1
- package/src/assets/web-panel/assets/index-WpaYaE3D.js +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bidirectional capability negotiation with N / N-1 downgrade for Agent
|
|
3
|
+
* Protocol (agent-sdk docs/PROTOCOL.md §1.3).
|
|
4
|
+
*
|
|
5
|
+
* The CLI advertises what it can speak one-directionally (buildAgentCapabilities
|
|
6
|
+
* → `cc agent --capabilities`, and the `system/init` line's protocol_version).
|
|
7
|
+
* That is not negotiation: a client (VS Code / JetBrains panel) had no way to
|
|
8
|
+
* announce what IT understands, and the CLI had no rule to pick a common level
|
|
9
|
+
* or step DOWN when the two disagree. When the protocol later grows a v2 line
|
|
10
|
+
* shape, a v1-only client would silently mis-parse it.
|
|
11
|
+
*
|
|
12
|
+
* This module is the missing algorithm. A client MAY send a `hello` as its
|
|
13
|
+
* first stream-json input line:
|
|
14
|
+
* {"type":"hello","protocol_version":2,"min_protocol_version":1,
|
|
15
|
+
* "features":["event_seq","trace_id"]}
|
|
16
|
+
* The CLI negotiates it against its own offer and echoes the agreed level as a
|
|
17
|
+
* `system/negotiated` line, then honors it for the rest of the run.
|
|
18
|
+
*
|
|
19
|
+
* Rules:
|
|
20
|
+
* - agreedVersion = min(server.max, client.max) — never above what either
|
|
21
|
+
* can parse.
|
|
22
|
+
* - Incompatible when agreedVersion < max(server.min, client.min): the two
|
|
23
|
+
* version ranges don't overlap. ok:false — the caller keeps a safe baseline
|
|
24
|
+
* or errors out; it never speaks a version the peer can't read.
|
|
25
|
+
* - effective features = the server-offered features the client also accepts
|
|
26
|
+
* (intersection), minus any whose minimum protocol version is above the
|
|
27
|
+
* agreed version (an N-only field is dropped for an N-1 session).
|
|
28
|
+
* - No client offer at all → legacy client that predates negotiation. The
|
|
29
|
+
* additive fields are defined "consumers MUST tolerate absence AND
|
|
30
|
+
* presence" (docs/PROTOCOL.md §1.2.1), so we keep FULL behavior unchanged
|
|
31
|
+
* (byte-for-byte) and mark clientAware:false. Negotiation only ever
|
|
32
|
+
* RESTRICTS when a client explicitly narrows the set.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no fs / process / clock. The same algorithm is mirrored in the Java
|
|
35
|
+
* twin (jetbrains-plugin CapabilityNegotiation.java); both read the shared
|
|
36
|
+
* fixture __tests__/fixtures/capability-negotiation-cases.json.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** The current protocol version the CLI speaks (mirror of PROTOCOL_VERSION). */
|
|
40
|
+
export const PROTOCOL_VERSION = 1;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The oldest protocol version the CLI can still speak end-to-end (the N-1 in
|
|
44
|
+
* "N / N-1"). At v1 there is no older line shape, so min === current; once v2
|
|
45
|
+
* ships this drops to 1 so a v1-only client negotiates a v1 session.
|
|
46
|
+
*/
|
|
47
|
+
export const PROTOCOL_MIN_VERSION = 1;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The wire-protocol features subject to negotiation — the additive per-line
|
|
51
|
+
* fields a client may or may not understand. Runtime capabilities (bare,
|
|
52
|
+
* worktree, mcp, …) are NOT negotiated: they change what the CLI can do, not
|
|
53
|
+
* the shape of a line the client must parse.
|
|
54
|
+
*/
|
|
55
|
+
export const PROTOCOL_FEATURES = ["event_seq", "tool_use_id", "trace_id"];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Minimum protocol version a feature requires. Empty today (every current
|
|
59
|
+
* feature is a v1 additive field). When a v2 field lands, add it here so an
|
|
60
|
+
* N-1 (v1) session drops it automatically.
|
|
61
|
+
* @type {Record<string, number>}
|
|
62
|
+
*/
|
|
63
|
+
export const FEATURE_MIN_VERSION = {};
|
|
64
|
+
|
|
65
|
+
function intOr(value, fallback) {
|
|
66
|
+
const n = Number(value);
|
|
67
|
+
return Number.isInteger(n) && n > 0 ? n : fallback;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Coerce a features declaration into a sorted, de-duplicated array of keys.
|
|
72
|
+
* Accepts an array of strings, or an object whose truthy keys are the features
|
|
73
|
+
* (the nested manifest shape). Anything else → [].
|
|
74
|
+
* @returns {string[]}
|
|
75
|
+
*/
|
|
76
|
+
export function normalizeFeatureList(input) {
|
|
77
|
+
let keys;
|
|
78
|
+
if (Array.isArray(input)) {
|
|
79
|
+
keys = input.filter((k) => typeof k === "string" && k);
|
|
80
|
+
} else if (input && typeof input === "object") {
|
|
81
|
+
keys = Object.keys(input).filter((k) => input[k]);
|
|
82
|
+
} else {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
return [...new Set(keys)].sort();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract the server's negotiation offer from a `cc agent --capabilities`
|
|
90
|
+
* manifest (buildAgentCapabilities output). Only the negotiable wire features
|
|
91
|
+
* the manifest advertises truthy are offered.
|
|
92
|
+
* @param {object} manifest
|
|
93
|
+
* @returns {{protocolVersion:number, minProtocolVersion:number, features:string[]}}
|
|
94
|
+
*/
|
|
95
|
+
export function buildServerOffer(manifest = {}) {
|
|
96
|
+
const protocolVersion = intOr(manifest.protocol_version, PROTOCOL_VERSION);
|
|
97
|
+
const minProtocolVersion = intOr(
|
|
98
|
+
manifest.min_protocol_version,
|
|
99
|
+
Math.min(PROTOCOL_MIN_VERSION, protocolVersion),
|
|
100
|
+
);
|
|
101
|
+
const advertised = normalizeFeatureList(manifest.features);
|
|
102
|
+
const features = PROTOCOL_FEATURES.filter((f) => advertised.includes(f));
|
|
103
|
+
return { protocolVersion, minProtocolVersion, features };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Negotiate the effective protocol version + feature set between a server offer
|
|
108
|
+
* and a client offer.
|
|
109
|
+
*
|
|
110
|
+
* @param {{protocolVersion:number, minProtocolVersion?:number, features:string[]}} server
|
|
111
|
+
* @param {null|{protocolVersion?:number, minProtocolVersion?:number, features?:string[]}} clientOffer
|
|
112
|
+
* @param {object} [opts]
|
|
113
|
+
* @param {Record<string,number>} [opts.featureMinVersion=FEATURE_MIN_VERSION]
|
|
114
|
+
* @returns {{
|
|
115
|
+
* ok:boolean, agreedVersion:(number|null), features:string[],
|
|
116
|
+
* downgraded:boolean, disabledFeatures:string[], clientAware:boolean,
|
|
117
|
+
* reason:(string|null)
|
|
118
|
+
* }}
|
|
119
|
+
*/
|
|
120
|
+
export function negotiateProtocol(server = {}, clientOffer = null, opts = {}) {
|
|
121
|
+
const featureMinVersion = opts.featureMinVersion || FEATURE_MIN_VERSION;
|
|
122
|
+
const serverMax = intOr(server.protocolVersion, PROTOCOL_VERSION);
|
|
123
|
+
const serverMin = intOr(server.minProtocolVersion, serverMax);
|
|
124
|
+
const serverFeatures = normalizeFeatureList(server.features);
|
|
125
|
+
|
|
126
|
+
const versionOk = (f, v) => (featureMinVersion[f] || 0) <= v;
|
|
127
|
+
|
|
128
|
+
// No client offer → legacy peer. Keep full behavior (only version-gate the
|
|
129
|
+
// server's own features against its own max), byte-for-byte unchanged.
|
|
130
|
+
if (clientOffer == null) {
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
agreedVersion: serverMax,
|
|
134
|
+
features: serverFeatures.filter((f) => versionOk(f, serverMax)),
|
|
135
|
+
downgraded: false,
|
|
136
|
+
disabledFeatures: [],
|
|
137
|
+
clientAware: false,
|
|
138
|
+
reason: null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const clientMax = intOr(clientOffer.protocolVersion, serverMax);
|
|
143
|
+
const clientMin = intOr(clientOffer.minProtocolVersion, clientMax);
|
|
144
|
+
const agreedVersion = Math.min(serverMax, clientMax);
|
|
145
|
+
const floor = Math.max(serverMin, clientMin);
|
|
146
|
+
|
|
147
|
+
if (agreedVersion < floor) {
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
agreedVersion: null,
|
|
151
|
+
features: [],
|
|
152
|
+
downgraded: true,
|
|
153
|
+
disabledFeatures: [...serverFeatures].sort(),
|
|
154
|
+
clientAware: true,
|
|
155
|
+
reason:
|
|
156
|
+
`no common protocol version (server ${serverMin}-${serverMax}, ` +
|
|
157
|
+
`client ${clientMin}-${clientMax})`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// A client that omits `features` accepts whatever the agreed version offers;
|
|
162
|
+
// a client that sends the array narrows to the intersection.
|
|
163
|
+
const clientFeatures =
|
|
164
|
+
clientOffer.features === undefined
|
|
165
|
+
? null
|
|
166
|
+
: new Set(normalizeFeatureList(clientOffer.features));
|
|
167
|
+
|
|
168
|
+
const enabled = [];
|
|
169
|
+
const disabled = [];
|
|
170
|
+
for (const f of serverFeatures) {
|
|
171
|
+
const okVersion = versionOk(f, agreedVersion);
|
|
172
|
+
const okClient = clientFeatures == null || clientFeatures.has(f);
|
|
173
|
+
if (okVersion && okClient) enabled.push(f);
|
|
174
|
+
else disabled.push(f);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
ok: true,
|
|
179
|
+
agreedVersion,
|
|
180
|
+
features: enabled.sort(),
|
|
181
|
+
downgraded: agreedVersion < serverMax || disabled.length > 0,
|
|
182
|
+
disabledFeatures: disabled.sort(),
|
|
183
|
+
clientAware: true,
|
|
184
|
+
reason: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Feature key → the stream line field it gates. */
|
|
189
|
+
const FEATURE_TO_FIELD = {
|
|
190
|
+
event_seq: "seq",
|
|
191
|
+
trace_id: "trace_id",
|
|
192
|
+
tool_use_id: "tool_use_id",
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Fold a negotiation result into a live field-gate the emitter reads per line:
|
|
197
|
+
* a field stays stamped only if its feature survived negotiation. On an
|
|
198
|
+
* incompatible (ok:false) result nothing is changed (the caller keeps its safe
|
|
199
|
+
* baseline). Mutates `gate` in place and returns it.
|
|
200
|
+
*
|
|
201
|
+
* @param {{features:string[], ok:boolean}} result
|
|
202
|
+
* @param {{seq?:boolean, trace_id?:boolean, tool_use_id?:boolean}} gate
|
|
203
|
+
*/
|
|
204
|
+
export function applyNegotiationToGate(result, gate = {}) {
|
|
205
|
+
if (!result || result.ok === false) return gate;
|
|
206
|
+
const enabled = new Set(result.features || []);
|
|
207
|
+
for (const feature of PROTOCOL_FEATURES) {
|
|
208
|
+
const field = FEATURE_TO_FIELD[feature];
|
|
209
|
+
if (field) gate[field] = enabled.has(feature);
|
|
210
|
+
}
|
|
211
|
+
return gate;
|
|
212
|
+
}
|
|
@@ -213,6 +213,17 @@ const PRINT_COMMANDS = new Set([
|
|
|
213
213
|
const SECRET_VAR_RE =
|
|
214
214
|
/(?:^|_)(KEY|KEYS|TOKEN|SECRET|SECRETS|PASSWORD|PASSWD|PASSPHRASE|CREDENTIAL|CREDENTIALS|PRIVATE|APIKEY|ACCESSKEY)(?:_|$)/i;
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* True when an environment-variable NAME looks like it holds a secret
|
|
218
|
+
* (ANTHROPIC_API_KEY, GITHUB_TOKEN, DB_PASSWORD, …). Shared single source of
|
|
219
|
+
* truth so the credential READ guard and the credential PROXY
|
|
220
|
+
* ([[credential-proxy.js]] — which keeps secrets out of subprocess envs)
|
|
221
|
+
* classify identically. `MONKEY`/`KEYBOARD`/`TOKENIZER` do NOT match.
|
|
222
|
+
*/
|
|
223
|
+
export function isSecretEnvName(name) {
|
|
224
|
+
return typeof name === "string" && SECRET_VAR_RE.test(name);
|
|
225
|
+
}
|
|
226
|
+
|
|
216
227
|
// Ways a secret var is referenced inside a shell segment.
|
|
217
228
|
const SECRET_REF_PATTERNS = [
|
|
218
229
|
/\$env:([A-Za-z_][A-Za-z0-9_]*)/gi, // PowerShell $env:NAME
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential proxy — keep the agent's real, long-lived credentials out of the
|
|
3
|
+
* environment that run_shell / run_code / hook / plugin subprocesses inherit
|
|
4
|
+
* (P0 §"跨平台沙箱与凭据代理").
|
|
5
|
+
*
|
|
6
|
+
* By default a spawned command inherits the WHOLE parent environment — including
|
|
7
|
+
* the agent's ANTHROPIC_API_KEY, cloud keys and tokens. A compromised or
|
|
8
|
+
* careless command can then echo, log, or exfiltrate them. This module replaces
|
|
9
|
+
* credential-named vars with an opaque SENTINEL before the child starts and
|
|
10
|
+
* keeps the real values in a parent-held vault, to be injected only for an
|
|
11
|
+
* approved host (via the egress proxy) — never handed to the child wholesale.
|
|
12
|
+
* The audit log only ever sees redacted values, never a restored secret.
|
|
13
|
+
*
|
|
14
|
+
* Pairs with credential-guard.js: that guard stops the AGENT reading secrets
|
|
15
|
+
* into model context; this stops the SUBPROCESS inheriting them. Both classify
|
|
16
|
+
* secret var names through the same `isSecretEnvName` so they never drift.
|
|
17
|
+
*
|
|
18
|
+
* Opt-in for now (`CC_CREDENTIAL_PROXY=1` or `config.credentialProxy.enabled`)
|
|
19
|
+
* so existing workflows that legitimately read a token from env keep working;
|
|
20
|
+
* default-on is the eventual goal once per-host injection is wired everywhere.
|
|
21
|
+
* Pure + dependency-light so every spawn seam can share it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { isSecretEnvName } from "./credential-guard.js";
|
|
25
|
+
|
|
26
|
+
/** Opaque replacement a masked credential var carries into the child env. */
|
|
27
|
+
export const CREDENTIAL_SENTINEL_PREFIX = "cc-cred-redacted:";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Well-known credential vars whose NAME does not obviously match the generic
|
|
31
|
+
* KEY/TOKEN/SECRET/PASSWORD pattern. Most real ones already match; this is a
|
|
32
|
+
* small safety net, extendable per-project via `deny`.
|
|
33
|
+
*/
|
|
34
|
+
const EXTRA_CREDENTIAL_NAMES = new Set([
|
|
35
|
+
"AWS_SESSION_TOKEN", // matches TOKEN, kept explicit
|
|
36
|
+
"GOOGLE_APPLICATION_CREDENTIALS", // path to a key file
|
|
37
|
+
"CLOUDSDK_AUTH_ACCESS_TOKEN",
|
|
38
|
+
"DIGITALOCEAN_ACCESS_TOKEN",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
function toSet(value) {
|
|
42
|
+
if (value instanceof Set) return value;
|
|
43
|
+
return new Set(Array.isArray(value) ? value : value ? [value] : []);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Classify an env var NAME as credential-bearing. `opts.allow` forces
|
|
48
|
+
* pass-through (never masked); `opts.deny` forces masking; otherwise the shared
|
|
49
|
+
* secret-name classifier + the curated extra set decide.
|
|
50
|
+
*/
|
|
51
|
+
export function isCredentialEnvName(name, opts = {}) {
|
|
52
|
+
if (typeof name !== "string" || !name) return false;
|
|
53
|
+
if (toSet(opts.allow).has(name)) return false;
|
|
54
|
+
if (toSet(opts.deny).has(name)) return true;
|
|
55
|
+
return isSecretEnvName(name) || EXTRA_CREDENTIAL_NAMES.has(name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The sentinel a child sees in place of a credential's real value. */
|
|
59
|
+
export function makeSentinel(name) {
|
|
60
|
+
return CREDENTIAL_SENTINEL_PREFIX + String(name ?? "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True when a value is a proxy sentinel (carries no secret). */
|
|
64
|
+
export function isSentinel(value) {
|
|
65
|
+
return (
|
|
66
|
+
typeof value === "string" && value.startsWith(CREDENTIAL_SENTINEL_PREFIX)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Mask credential-named vars in `env`. Returns a NEW object (never mutates the
|
|
72
|
+
* input):
|
|
73
|
+
* - `env` — credential vars replaced with a sentinel (mode "mask", the
|
|
74
|
+
* default) or removed entirely (mode "deny"); everything else
|
|
75
|
+
* copied through verbatim.
|
|
76
|
+
* - `masked` — sorted list of the var names that were masked/removed.
|
|
77
|
+
* - `vault` — Map<name, realValue> the PARENT keeps for approved injection;
|
|
78
|
+
* the child never receives it.
|
|
79
|
+
* Values that are already sentinels, null, or undefined are left as-is (a
|
|
80
|
+
* sentinel is never double-masked; an absent var is not invented).
|
|
81
|
+
*/
|
|
82
|
+
export function maskCredentialEnv(env = {}, opts = {}) {
|
|
83
|
+
const mode = opts.mode === "deny" ? "deny" : "mask";
|
|
84
|
+
const out = {};
|
|
85
|
+
const masked = [];
|
|
86
|
+
const vault = new Map();
|
|
87
|
+
for (const [name, value] of Object.entries(env || {})) {
|
|
88
|
+
if (
|
|
89
|
+
value != null &&
|
|
90
|
+
!isSentinel(value) &&
|
|
91
|
+
isCredentialEnvName(name, opts)
|
|
92
|
+
) {
|
|
93
|
+
masked.push(name);
|
|
94
|
+
vault.set(name, String(value));
|
|
95
|
+
if (mode === "mask") out[name] = makeSentinel(name);
|
|
96
|
+
// mode "deny": drop the var so the child does not even see a sentinel
|
|
97
|
+
} else {
|
|
98
|
+
out[name] = value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
masked.sort();
|
|
102
|
+
return { env: out, masked, vault };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Replace a secret value with a fixed marker — never the real value. */
|
|
106
|
+
export function redactSecretValue(value) {
|
|
107
|
+
if (value == null) return value;
|
|
108
|
+
return String(value) ? "***" : "";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A log-safe projection of an env for the audit trail: credential values →
|
|
113
|
+
* "***", sentinels kept verbatim (they hold no secret), everything else
|
|
114
|
+
* untouched. NEVER emits a restored/real credential value.
|
|
115
|
+
*/
|
|
116
|
+
export function redactEnvForAudit(env = {}, opts = {}) {
|
|
117
|
+
const out = {};
|
|
118
|
+
for (const [name, value] of Object.entries(env || {})) {
|
|
119
|
+
if (isSentinel(value)) out[name] = value;
|
|
120
|
+
else if (isCredentialEnvName(name, opts))
|
|
121
|
+
out[name] = redactSecretValue(value);
|
|
122
|
+
else out[name] = value;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the real value for a masked credential — but ONLY when the target
|
|
129
|
+
* host is on the approved list. The whole point of the proxy is that the child
|
|
130
|
+
* never gets the raw secret; the parent injects it just-in-time for an approved
|
|
131
|
+
* destination (e.g. the egress proxy adding Authorization for an allowed API
|
|
132
|
+
* host). Returns null (fail closed) for an unknown var, an empty host, or a
|
|
133
|
+
* non-approved host.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveApprovedInjection(vault, name, opts = {}) {
|
|
136
|
+
if (!(vault instanceof Map) || !vault.has(name)) return null;
|
|
137
|
+
const host = String(opts.host || "").toLowerCase();
|
|
138
|
+
if (!host) return null;
|
|
139
|
+
const approved = (opts.approvedHosts || []).map((h) =>
|
|
140
|
+
String(h || "").toLowerCase(),
|
|
141
|
+
);
|
|
142
|
+
return approved.includes(host) ? vault.get(name) : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve whether the credential proxy is enabled. Env var wins so a spawn
|
|
147
|
+
* seam without config access can still honor it; otherwise the config flag.
|
|
148
|
+
*/
|
|
149
|
+
export function credentialProxyEnabled(config = {}, env = process.env) {
|
|
150
|
+
const raw = env && env.CC_CREDENTIAL_PROXY;
|
|
151
|
+
if (raw != null) {
|
|
152
|
+
const v = String(raw).toLowerCase();
|
|
153
|
+
return v === "1" || v === "true" || v === "on" || v === "yes";
|
|
154
|
+
}
|
|
155
|
+
return config?.credentialProxy?.enabled === true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Apply the credential proxy to a child env when enabled; otherwise return the
|
|
160
|
+
* env UNCHANGED (same reference — the default path stays byte-identical). The
|
|
161
|
+
* returned `vault` lets the caller inject approved creds later.
|
|
162
|
+
*
|
|
163
|
+
* @returns {{env:object, masked:string[], vault:Map, enabled:boolean}}
|
|
164
|
+
*/
|
|
165
|
+
export function applyCredentialProxy(env, options = {}) {
|
|
166
|
+
const procEnv = options.env || process.env;
|
|
167
|
+
const config = options.config || {};
|
|
168
|
+
if (!credentialProxyEnabled(config, procEnv)) {
|
|
169
|
+
return { env, masked: [], vault: new Map(), enabled: false };
|
|
170
|
+
}
|
|
171
|
+
const allow = new Set([
|
|
172
|
+
...toSet(options.allow),
|
|
173
|
+
...(config?.credentialProxy?.allow || []),
|
|
174
|
+
]);
|
|
175
|
+
const deny = new Set([
|
|
176
|
+
...toSet(options.deny),
|
|
177
|
+
...(config?.credentialProxy?.deny || []),
|
|
178
|
+
]);
|
|
179
|
+
const mode = options.mode || config?.credentialProxy?.mode || "mask";
|
|
180
|
+
const result = maskCredentialEnv(env, { allow, deny, mode });
|
|
181
|
+
return { ...result, enabled: true };
|
|
182
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency-install policy for `run_code` (gap-analysis 2026-07-11,
|
|
3
|
+
* docs/CLAUDE_CODE_CLI_GAP_ANALYSIS.md P0 "依赖安装与凭据").
|
|
4
|
+
*
|
|
5
|
+
* Auto `pip install` on a Python import error used to be unconditional. It is
|
|
6
|
+
* now OPT-IN:
|
|
7
|
+
* - env `CC_RUN_CODE_AUTO_INSTALL=1|0` wins outright (kill-switch friendly)
|
|
8
|
+
* - settings.json `runCode.autoInstall: true` opts in per project/user
|
|
9
|
+
* - default: DISABLED — the tool result explains how to enable it
|
|
10
|
+
*
|
|
11
|
+
* When enabled, an optional `runCode.installAllowlist` (array of package
|
|
12
|
+
* names, PEP 503-normalized) restricts WHICH packages may be installed, and
|
|
13
|
+
* every attempt — including blocked/disabled ones — is appended to an audit
|
|
14
|
+
* log at ~/.chainlesschain/audit/dependency-install.jsonl.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import os from "node:os";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
|
|
22
|
+
const _require = createRequire(import.meta.url);
|
|
23
|
+
|
|
24
|
+
/** PEP 503 name normalization: lowercase, runs of -_. collapse to "-". */
|
|
25
|
+
export function normalizePackageName(name) {
|
|
26
|
+
return String(name || "")
|
|
27
|
+
.trim()
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[-_.]+/g, "-");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the effective auto-install policy.
|
|
34
|
+
* @param {{cwd?:string, env?:object, settingsFile?:string|null}} opts
|
|
35
|
+
* @returns {{enabled:boolean, allowlist:string[]|null, source:string}}
|
|
36
|
+
*/
|
|
37
|
+
export function resolveAutoInstallPolicy({
|
|
38
|
+
cwd = process.cwd(),
|
|
39
|
+
env = process.env,
|
|
40
|
+
settingsFile = null,
|
|
41
|
+
} = {}) {
|
|
42
|
+
const raw = env.CC_RUN_CODE_AUTO_INSTALL;
|
|
43
|
+
if (raw === "1" || raw === "0") {
|
|
44
|
+
return {
|
|
45
|
+
enabled: raw === "1",
|
|
46
|
+
allowlist: raw === "1" ? readAllowlist({ cwd, settingsFile }) : null,
|
|
47
|
+
source: "env",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let enabled = false;
|
|
51
|
+
try {
|
|
52
|
+
const { readBooleanSetting } = _require("./settings-loader.cjs");
|
|
53
|
+
enabled =
|
|
54
|
+
readBooleanSetting("runCode.autoInstall", { cwd, settingsFile }) === true;
|
|
55
|
+
} catch {
|
|
56
|
+
enabled = false; // fail-closed: no settings → stay disabled
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
enabled,
|
|
60
|
+
allowlist: enabled ? readAllowlist({ cwd, settingsFile }) : null,
|
|
61
|
+
source: enabled ? "settings" : "default",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readAllowlist({ cwd = process.cwd(), settingsFile }) {
|
|
66
|
+
try {
|
|
67
|
+
const { settingsPaths, readSettingsFile, loadManagedSettings } = _require(
|
|
68
|
+
"./settings-loader.cjs",
|
|
69
|
+
);
|
|
70
|
+
let value;
|
|
71
|
+
for (const file of settingsPaths(cwd, settingsFile)) {
|
|
72
|
+
const node = readSettingsFile(file)?.runCode?.installAllowlist;
|
|
73
|
+
if (Array.isArray(node)) value = node; // closest layer wins
|
|
74
|
+
}
|
|
75
|
+
const managed = loadManagedSettings({});
|
|
76
|
+
const mNode = managed?.settings?.runCode?.installAllowlist;
|
|
77
|
+
if (Array.isArray(mNode)) value = mNode; // managed policy wins outright
|
|
78
|
+
if (!Array.isArray(value)) return null;
|
|
79
|
+
const list = value.map(normalizePackageName).filter(Boolean);
|
|
80
|
+
return list.length ? list : null;
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a package may be installed under the policy's allowlist.
|
|
88
|
+
* No allowlist configured → any (validity is checked separately).
|
|
89
|
+
*/
|
|
90
|
+
export function isPackageAllowed(name, allowlist) {
|
|
91
|
+
if (!allowlist) return true;
|
|
92
|
+
return allowlist.includes(normalizePackageName(name));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Human hint for the disabled-by-default path (returned in the tool result). */
|
|
96
|
+
export function autoInstallDisabledHint(packageName) {
|
|
97
|
+
return (
|
|
98
|
+
`Missing Python package "${packageName}". Auto-install is disabled by ` +
|
|
99
|
+
`default; install it yourself (pip install ${packageName}), or opt in via ` +
|
|
100
|
+
`settings.json {"runCode":{"autoInstall":true}} or CC_RUN_CODE_AUTO_INSTALL=1.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Append an install-attempt audit record (best-effort, never throws).
|
|
106
|
+
* @param {{package:string, interpreter?:string, cwd?:string,
|
|
107
|
+
* outcome:"installed"|"failed"|"blocked"|"disabled", detail?:string}} entry
|
|
108
|
+
* @param {{baseDir?:string, now?:()=>number}} [opts] injection seams
|
|
109
|
+
*/
|
|
110
|
+
export function recordInstallAudit(entry, opts = {}) {
|
|
111
|
+
try {
|
|
112
|
+
const baseDir =
|
|
113
|
+
opts.baseDir ||
|
|
114
|
+
process.env.CC_AUDIT_DIR ||
|
|
115
|
+
path.join(os.homedir(), ".chainlesschain", "audit");
|
|
116
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
117
|
+
const line = JSON.stringify({
|
|
118
|
+
ts: new Date(opts.now ? opts.now() : Date.now()).toISOString(),
|
|
119
|
+
kind: "dependency-install",
|
|
120
|
+
...entry,
|
|
121
|
+
});
|
|
122
|
+
fs.appendFileSync(
|
|
123
|
+
path.join(baseDir, "dependency-install.jsonl"),
|
|
124
|
+
line + "\n",
|
|
125
|
+
"utf-8",
|
|
126
|
+
);
|
|
127
|
+
} catch {
|
|
128
|
+
/* audit is best-effort — never affect the run */
|
|
129
|
+
}
|
|
130
|
+
}
|