chainlesschain 0.162.188 → 0.162.193
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 +61 -11
- package/bin/chainlesschain.js +7 -52
- package/completions/_cc +209 -0
- package/completions/cc.bash +18 -0
- package/completions/cc.fish +13 -0
- package/completions/cc.ps1 +226 -0
- package/package.json +10 -2
- package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
- package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
- package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/cli-help.js +181 -0
- package/src/command-help-index.json +181 -0
- package/src/command-manifest.json +1107 -175
- package/src/command-surface-policy.js +197 -0
- package/src/commands/agent.js +98 -33
- package/src/commands/artifacts.js +400 -2
- package/src/commands/background-session.js +41 -3
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint-restore-recovery.js +1476 -0
- package/src/commands/checkpoint.js +950 -22
- package/src/commands/compact.js +101 -25
- package/src/commands/config.js +506 -20
- package/src/commands/doctor.js +4 -1
- package/src/commands/hub.js +1 -1
- package/src/commands/mcp.js +688 -24
- package/src/commands/pack.js +16 -2
- package/src/commands/plugin.js +20 -12
- package/src/commands/session-mcp-recovery.js +218 -0
- package/src/commands/session.js +470 -110
- package/src/commands/setup.js +36 -10
- package/src/commands/skill.js +181 -110
- package/src/commands/status.js +21 -5
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/commands/update.js +68 -6
- package/src/constants.js +11 -0
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/action-protocol.js +10 -0
- package/src/gateways/ws/session-protocol.js +555 -27
- package/src/gateways/ws/ws-agent-handler.js +541 -14
- package/src/gateways/ws/ws-session-gateway.js +129 -22
- package/src/gateways/ws/ws-session-state.js +431 -0
- package/src/harness/background-task-manager.js +233 -36
- package/src/harness/jsonl-session-store.js +2551 -167
- package/src/harness/mcp-client.js +802 -61
- package/src/harness/prompt-compressor.js +227 -27
- package/src/harness/session-index.js +89 -38
- package/src/harness/session-list-index.js +254 -0
- package/src/harness/structured-handoff.js +410 -0
- package/src/lazy-dispatch.js +747 -84
- package/src/lib/__tests__/logger.test.js +50 -2
- package/src/lib/advisor-runtime.js +1047 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +120 -26
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +543 -24
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/artifact-store.js +131 -8
- package/src/lib/background-agent-supervisor.js +185 -13
- package/src/lib/background-interaction-journal.js +37 -9
- package/src/lib/background-launch-profile.js +1058 -0
- package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
- package/src/lib/checkpoint-restore-orchestrator.js +987 -0
- package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
- package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
- package/src/lib/checkpoint-restore-recovery.js +884 -0
- package/src/lib/checkpoint-restore-retention.js +126 -0
- package/src/lib/checkpoint-restore-saga.js +5079 -0
- package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
- package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
- package/src/lib/checkpoint-store.js +1688 -97
- package/src/lib/checkpoint-timeline-authority.js +522 -0
- package/src/lib/checkpoint-timeline.js +642 -0
- package/src/lib/config-keys.js +18 -119
- package/src/lib/config-manager.js +446 -27
- package/src/lib/config-redaction.js +57 -0
- package/src/lib/config-schema.js +853 -0
- package/src/lib/cowork-mcp-tools.js +5 -0
- package/src/lib/cowork-task-runner.js +458 -63
- package/src/lib/delivery-coordinator.js +1234 -0
- package/src/lib/delivery-evidence.js +450 -0
- package/src/lib/doctor-checkup.js +199 -14
- package/src/lib/executable-path.js +34 -0
- package/src/lib/fatal-handler.js +16 -0
- package/src/lib/file-checkpoint.js +4909 -186
- package/src/lib/file-lines.js +125 -0
- package/src/lib/headless-config-command.js +16 -5
- package/src/lib/hook-runner.cjs +139 -38
- package/src/lib/hooks-v2-workspace-context.js +10 -0
- package/src/lib/impacted-gate-selector.js +424 -0
- package/src/lib/interaction-adapter.js +62 -0
- package/src/lib/jsonl-session-store.js +1 -35
- package/src/lib/logger.js +66 -14
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/mcp-call-ledger-store.js +1069 -0
- package/src/lib/mcp-call-ledger.js +747 -0
- package/src/lib/mcp-client.js +5 -0
- package/src/lib/mcp-conflict-scheduler.js +468 -0
- package/src/lib/mcp-effect-contract.js +102 -0
- package/src/lib/mcp-headers-helper-trust.js +145 -0
- package/src/lib/mcp-headers-helper.js +616 -0
- package/src/lib/mcp-host-recovery-runtime.js +131 -0
- package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
- package/src/lib/mcp-recovery-adjudication.js +443 -0
- package/src/lib/output-context.js +86 -0
- package/src/lib/packer/file-identity.js +170 -0
- package/src/lib/packer/index.js +3 -0
- package/src/lib/packer/native-update-state.js +277 -0
- package/src/lib/packer/pack-update-applier.js +1983 -136
- package/src/lib/packer/pack-update-checker.js +34 -0
- package/src/lib/packer/pack-update-downloader.js +875 -86
- package/src/lib/packer/pack-update-signature.js +86 -0
- package/src/lib/packer/pkg-config-generator.js +6 -0
- package/src/lib/paths.js +65 -10
- package/src/lib/plan-mode.js +831 -14
- package/src/lib/plan-persistence.js +540 -0
- package/src/lib/plugin-runtime/hooks.js +227 -69
- package/src/lib/plugin-runtime/plugin-options.js +189 -44
- package/src/lib/pr-automation-policy.js +146 -8
- package/src/lib/process-execution-broker/credential-agent.js +7 -0
- package/src/lib/process-execution-broker/index.js +491 -13
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
- package/src/lib/project-instructions.js +165 -29
- package/src/lib/project-mcp-trust.js +67 -0
- package/src/lib/public-ide-capability-manifest.js +272 -0
- package/src/lib/public-ide-capability-validation.js +351 -0
- package/src/lib/secret-store.js +267 -43
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/secure-fs.js +1089 -0
- package/src/lib/security-audit.js +58 -0
- package/src/lib/service-manager.js +34 -13
- package/src/lib/session-budget-runtime.js +1452 -0
- package/src/lib/session-host-snapshot.js +388 -0
- package/src/lib/session-message-provenance.js +429 -0
- package/src/lib/session-projection.js +804 -0
- package/src/lib/session-resource-budget.js +1495 -0
- package/src/lib/settings-hooks.cjs +138 -22
- package/src/lib/settings-loader.cjs +25 -2
- package/src/lib/side-effect-ledger-store.js +45 -23
- package/src/lib/skill-execution-identity.js +217 -0
- package/src/lib/skill-loader.js +299 -18
- package/src/lib/sub-agent-context.js +236 -28
- package/src/lib/sub-agent-registry.js +5 -0
- package/src/lib/subagent-contract.js +94 -0
- package/src/lib/todo-manager.js +185 -18
- package/src/lib/todo-persistence.js +417 -0
- package/src/lib/turn-binding-store.js +41 -23
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +165 -24
- package/src/lib/ws-chat-handler.js +21 -0
- package/src/program-base.js +18 -0
- package/src/repl/advisor-command.js +103 -0
- package/src/repl/agent-repl.js +2175 -146
- package/src/repl/btw-command.js +93 -10
- package/src/repl/clipboard-image.js +119 -0
- package/src/repl/config-summary.js +64 -108
- package/src/repl/prompt-editor.js +233 -0
- package/src/repl/prompt-interactions.js +344 -0
- package/src/repl/prompt-stash.js +354 -0
- package/src/repl/prompt-suggestions.js +312 -0
- package/src/repl/repl-keybindings.js +227 -0
- package/src/repl/session-recap.js +217 -0
- package/src/repl/slash-command-registry.js +22 -0
- package/src/repl/terminal-layout.js +88 -0
- package/src/runtime/agent-core.js +1630 -447
- package/src/runtime/coding-agent-contract-shared.cjs +13 -1
- package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
- package/src/runtime/diagnostics.js +32 -13
- package/src/runtime/headless-runner.js +310 -118
- package/src/runtime/headless-stream.js +356 -95
- package/src/runtime/mcp-config.js +148 -14
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/runtime/status-diagnostics-lite.js +137 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -2,35 +2,214 @@ import {
|
|
|
2
2
|
readFileSync,
|
|
3
3
|
writeFileSync,
|
|
4
4
|
existsSync,
|
|
5
|
-
mkdirSync,
|
|
6
5
|
renameSync,
|
|
7
6
|
unlinkSync,
|
|
8
7
|
copyFileSync,
|
|
8
|
+
lstatSync,
|
|
9
|
+
constants as fsConstants,
|
|
9
10
|
} from "node:fs";
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
12
|
import { dirname } from "node:path";
|
|
11
13
|
import { getConfigPath } from "./paths.js";
|
|
12
14
|
import { DEFAULT_CONFIG } from "../constants.js";
|
|
13
15
|
import { withFileLock } from "./with-file-lock.js";
|
|
16
|
+
import {
|
|
17
|
+
coerceConfigValue,
|
|
18
|
+
isSecretConfigKey,
|
|
19
|
+
validateConfigValue,
|
|
20
|
+
} from "./config-schema.js";
|
|
21
|
+
import { createSecretStore, isSecretRef, secretRef } from "./secret-store.js";
|
|
22
|
+
import {
|
|
23
|
+
ensurePrivateDirectory,
|
|
24
|
+
ensurePrivateFile,
|
|
25
|
+
PRIVATE_FILE_MODE,
|
|
26
|
+
} from "./secure-fs.js";
|
|
14
27
|
|
|
15
28
|
// Warn at most once per config path per process. Injectable seam: the default
|
|
16
29
|
// stays silent under vitest so test output isn't polluted; tests override
|
|
17
30
|
// `_deps.warn` to assert it fired.
|
|
18
31
|
const _warnedConfigPaths = new Set();
|
|
32
|
+
const _loadedSecretRefs = new Map();
|
|
19
33
|
export const _deps = {
|
|
34
|
+
createSecretStore: () => createSecretStore(),
|
|
20
35
|
warn: (msg) => {
|
|
21
36
|
if (!process.env.VITEST) process.stderr.write(msg);
|
|
22
37
|
},
|
|
23
38
|
};
|
|
24
39
|
|
|
25
|
-
|
|
40
|
+
const FORBIDDEN_CONFIG_SEGMENTS = new Set([
|
|
41
|
+
"__proto__",
|
|
42
|
+
"prototype",
|
|
43
|
+
"constructor",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function assertSafeConfigObject(value, prefix = "") {
|
|
47
|
+
if (!value || typeof value !== "object") return;
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
value.forEach((entry, index) =>
|
|
50
|
+
assertSafeConfigObject(entry, `${prefix}.${index}`),
|
|
51
|
+
);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const [key, child] of Object.entries(value)) {
|
|
55
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
56
|
+
if (FORBIDDEN_CONFIG_SEGMENTS.has(key)) {
|
|
57
|
+
const error = new Error(`Invalid configuration key: ${path}`);
|
|
58
|
+
error.code = "CONFIG_KEY_INVALID";
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
assertSafeConfigObject(child, path);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function assertNotSymbolicLink(target, label = "configuration path") {
|
|
66
|
+
const stat = lstatSync(target);
|
|
67
|
+
if (stat.isSymbolicLink()) {
|
|
68
|
+
const error = new Error(`Refusing symbolic link for ${label}: ${target}`);
|
|
69
|
+
error.code = "CONFIG_PATH_SYMLINK";
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
return stat;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readConfigDocument({ failIfUnavailable = false } = {}) {
|
|
76
|
+
const configPath = getConfigPath();
|
|
77
|
+
if (!existsSync(configPath)) return {};
|
|
78
|
+
try {
|
|
79
|
+
assertNotSymbolicLink(configPath, "config.json");
|
|
80
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
81
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
82
|
+
throw new Error("configuration root must be an object");
|
|
83
|
+
}
|
|
84
|
+
assertSafeConfigObject(parsed);
|
|
85
|
+
return parsed;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
if (failIfUnavailable) {
|
|
88
|
+
const error = new Error(
|
|
89
|
+
`Could not read durable config at ${configPath}: ${err.message}`,
|
|
90
|
+
{ cause: err },
|
|
91
|
+
);
|
|
92
|
+
error.code = "CONFIG_STORE_UNAVAILABLE";
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function loadUserConfig(options = {}) {
|
|
100
|
+
return readConfigDocument(options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function secretRefCacheKey(configPath, key) {
|
|
104
|
+
return `${configPath}\0${key}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createSecretStorageRef(configPath, key) {
|
|
108
|
+
const namespace = createHash("sha256")
|
|
109
|
+
.update(String(configPath))
|
|
110
|
+
.digest("hex")
|
|
111
|
+
.slice(0, 24);
|
|
112
|
+
const encodedKey = Buffer.from(String(key), "utf8").toString("base64url");
|
|
113
|
+
return `config/${namespace}/${encodedKey}/${randomUUID()}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function deleteSecretRefBestEffort(store, ref, context) {
|
|
117
|
+
if (!store || !ref || typeof store.delete !== "function") return;
|
|
118
|
+
try {
|
|
119
|
+
if (store.delete(ref) === false) {
|
|
120
|
+
_deps.warn(
|
|
121
|
+
`Warning: could not remove stale OS secret reference (${context}).\n`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
_deps.warn(
|
|
126
|
+
`Warning: could not remove stale OS secret reference (${context}: ${error.message}).\n`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveSecretReferences(value, prefix = "", state = {}) {
|
|
132
|
+
if (isSecretRef(value)) {
|
|
133
|
+
if (!isSecretConfigKey(prefix)) {
|
|
134
|
+
const error = new Error(
|
|
135
|
+
`Secret reference is not allowed at non-secret key: ${prefix || "(root)"}`,
|
|
136
|
+
);
|
|
137
|
+
error.code = "CONFIG_SECRET_REF_INVALID";
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
const ref = value.__cc_secret_ref;
|
|
141
|
+
try {
|
|
142
|
+
state.store ||= _deps.createSecretStore();
|
|
143
|
+
const resolved = state.store.get(ref);
|
|
144
|
+
if (resolved == null && !state.warned) {
|
|
145
|
+
state.warned = true;
|
|
146
|
+
_deps.warn(
|
|
147
|
+
`Warning: an OS-backed configuration secret is missing (${prefix}).\n`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
_loadedSecretRefs.set(secretRefCacheKey(state.configPath, prefix), {
|
|
151
|
+
ref,
|
|
152
|
+
resolved,
|
|
153
|
+
});
|
|
154
|
+
return resolved;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
_loadedSecretRefs.set(secretRefCacheKey(state.configPath, prefix), {
|
|
157
|
+
ref,
|
|
158
|
+
resolved: null,
|
|
159
|
+
});
|
|
160
|
+
if (!state.warned) {
|
|
161
|
+
state.warned = true;
|
|
162
|
+
_deps.warn(
|
|
163
|
+
`Warning: an OS-backed configuration secret could not be read (${error.message}).\n`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (Array.isArray(value)) {
|
|
170
|
+
return value.map((entry, index) =>
|
|
171
|
+
resolveSecretReferences(entry, `${prefix}.${index}`, state),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (value && typeof value === "object") {
|
|
175
|
+
const out = {};
|
|
176
|
+
for (const [key, child] of Object.entries(value)) {
|
|
177
|
+
const childKey = prefix ? `${prefix}.${key}` : key;
|
|
178
|
+
out[key] = resolveSecretReferences(child, childKey, state);
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function restoreSecretReferences(config) {
|
|
186
|
+
const out = structuredClone(config);
|
|
187
|
+
const prefix = `${getConfigPath()}\0`;
|
|
188
|
+
for (const [cacheKey, saved] of _loadedSecretRefs) {
|
|
189
|
+
if (!cacheKey.startsWith(prefix)) continue;
|
|
190
|
+
const key = cacheKey.slice(prefix.length);
|
|
191
|
+
const current = getNestedValue(out, key);
|
|
192
|
+
if (current === saved.resolved || isSecretRef(current)) {
|
|
193
|
+
setNestedValue(out, key, secretRef(saved.ref));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function loadConfig({
|
|
200
|
+
failIfUnavailable = false,
|
|
201
|
+
resolveSecrets = true,
|
|
202
|
+
} = {}) {
|
|
26
203
|
const configPath = getConfigPath();
|
|
27
204
|
if (!existsSync(configPath)) {
|
|
28
205
|
return { ...structuredClone(DEFAULT_CONFIG) };
|
|
29
206
|
}
|
|
30
207
|
try {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
return
|
|
208
|
+
const parsed = readConfigDocument({ failIfUnavailable });
|
|
209
|
+
const effective = deepMerge(structuredClone(DEFAULT_CONFIG), parsed);
|
|
210
|
+
return resolveSecrets
|
|
211
|
+
? resolveSecretReferences(effective, "", { configPath })
|
|
212
|
+
: effective;
|
|
34
213
|
} catch (err) {
|
|
35
214
|
if (failIfUnavailable) {
|
|
36
215
|
const error = new Error(
|
|
@@ -55,8 +234,18 @@ export function loadConfig({ failIfUnavailable = false } = {}) {
|
|
|
55
234
|
// Fixed sibling name (no timestamp) so repeated runs don't accumulate.
|
|
56
235
|
let backupNote = "";
|
|
57
236
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
237
|
+
assertNotSymbolicLink(configPath, "corrupted config source");
|
|
238
|
+
const backupPath = `${configPath}.corrupted`;
|
|
239
|
+
if (existsSync(backupPath)) {
|
|
240
|
+
assertNotSymbolicLink(backupPath, "corrupted config backup");
|
|
241
|
+
} else {
|
|
242
|
+
copyFileSync(configPath, backupPath, fsConstants.COPYFILE_EXCL);
|
|
243
|
+
}
|
|
244
|
+
ensurePrivateFile(backupPath, {
|
|
245
|
+
applyWindowsAcl: true,
|
|
246
|
+
failIfUnavailable: true,
|
|
247
|
+
});
|
|
248
|
+
backupNote = ` A copy is available at ${backupPath}.`;
|
|
60
249
|
} catch {
|
|
61
250
|
/* backup is best-effort — the warning still fires without it */
|
|
62
251
|
}
|
|
@@ -70,9 +259,12 @@ export function loadConfig({ failIfUnavailable = false } = {}) {
|
|
|
70
259
|
}
|
|
71
260
|
}
|
|
72
261
|
|
|
73
|
-
export function saveConfig(config) {
|
|
262
|
+
export function saveConfig(config, options = {}) {
|
|
74
263
|
const configPath = getConfigPath();
|
|
75
|
-
|
|
264
|
+
ensurePrivateDirectory(dirname(configPath), {
|
|
265
|
+
applyWindowsAcl: true,
|
|
266
|
+
failIfUnavailable: true,
|
|
267
|
+
});
|
|
76
268
|
// Atomic write: config.json holds API keys + LLM settings, so a crash (or two
|
|
77
269
|
// concurrent `cc config set`) mid-write must never leave a truncated/corrupt
|
|
78
270
|
// file — that would break every cc command and could lose credentials. Write a
|
|
@@ -81,8 +273,26 @@ export function saveConfig(config) {
|
|
|
81
273
|
// one, never a half-written one.
|
|
82
274
|
const tmp = `${configPath}.${process.pid}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
83
275
|
try {
|
|
84
|
-
|
|
276
|
+
const persisted =
|
|
277
|
+
options.preserveSecretRefs === false
|
|
278
|
+
? structuredClone(config)
|
|
279
|
+
: restoreSecretReferences(config);
|
|
280
|
+
assertSafeConfigObject(persisted);
|
|
281
|
+
writeFileSync(tmp, JSON.stringify(persisted, null, 2) + "\n", {
|
|
282
|
+
encoding: "utf-8",
|
|
283
|
+
mode: PRIVATE_FILE_MODE,
|
|
284
|
+
});
|
|
85
285
|
renameWithRetry(tmp, configPath);
|
|
286
|
+
// POSIX needs an explicit 0600 chmod. On Windows the temp file was created
|
|
287
|
+
// after the parent DACL was repaired above, so it inherits that owner-only
|
|
288
|
+
// ACL through the atomic rename without a second PowerShell round-trip.
|
|
289
|
+
ensurePrivateFile(configPath);
|
|
290
|
+
if (options.preserveSecretRefs === false) {
|
|
291
|
+
const prefix = `${configPath}\0`;
|
|
292
|
+
for (const cacheKey of _loadedSecretRefs.keys()) {
|
|
293
|
+
if (cacheKey.startsWith(prefix)) _loadedSecretRefs.delete(cacheKey);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
86
296
|
} catch (err) {
|
|
87
297
|
try {
|
|
88
298
|
if (existsSync(tmp)) unlinkSync(tmp);
|
|
@@ -93,6 +303,32 @@ export function saveConfig(config) {
|
|
|
93
303
|
}
|
|
94
304
|
}
|
|
95
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Create a stable, owner-only snapshot before an in-place schema migration.
|
|
308
|
+
* The first snapshot wins so a repeated `validate --fix` cannot overwrite the
|
|
309
|
+
* last known pre-migration document with an already-migrated copy.
|
|
310
|
+
*/
|
|
311
|
+
export function backupConfigForMigration(
|
|
312
|
+
suffix = `.before-schema-migration`,
|
|
313
|
+
options = {},
|
|
314
|
+
) {
|
|
315
|
+
const configPath = getConfigPath();
|
|
316
|
+
if (!existsSync(configPath)) return null;
|
|
317
|
+
assertNotSymbolicLink(configPath, "config.json migration source");
|
|
318
|
+
const backupPath = `${configPath}${suffix}`;
|
|
319
|
+
if (existsSync(backupPath)) {
|
|
320
|
+
assertNotSymbolicLink(backupPath, "config migration backup");
|
|
321
|
+
} else {
|
|
322
|
+
copyFileSync(configPath, backupPath, fsConstants.COPYFILE_EXCL);
|
|
323
|
+
}
|
|
324
|
+
ensurePrivateFile(backupPath, {
|
|
325
|
+
applyWindowsAcl: options.applyWindowsAcl !== false,
|
|
326
|
+
failIfUnavailable: options.failIfUnavailable !== false,
|
|
327
|
+
...(options.secureOptions || {}),
|
|
328
|
+
});
|
|
329
|
+
return backupPath;
|
|
330
|
+
}
|
|
331
|
+
|
|
96
332
|
// On Windows, `rename` over a target that another process currently has open
|
|
97
333
|
// (e.g. a concurrent `loadConfig` reader) fails with EPERM/EACCES/EBUSY instead
|
|
98
334
|
// of POSIX's silent atomic replace. Under bursts of concurrent `cc config`/`cc
|
|
@@ -134,23 +370,36 @@ function sleepSync(ms) {
|
|
|
134
370
|
}
|
|
135
371
|
}
|
|
136
372
|
|
|
137
|
-
export function getConfigValue(key) {
|
|
138
|
-
const config = loadConfig();
|
|
373
|
+
export function getConfigValue(key, options = {}) {
|
|
374
|
+
const config = loadConfig(options);
|
|
139
375
|
return getNestedValue(config, key);
|
|
140
376
|
}
|
|
141
377
|
|
|
142
|
-
export function setConfigValue(key, value) {
|
|
378
|
+
export function setConfigValue(key, value, options = {}) {
|
|
379
|
+
if (isSecretConfigKey(key) && options.allowSecret !== true) {
|
|
380
|
+
const error = new Error(
|
|
381
|
+
`Refusing a secret on the command line for ${key}; use "cc config set-secret ${key}"`,
|
|
382
|
+
);
|
|
383
|
+
error.code = "CONFIG_SECRET_REQUIRES_SECURE_INPUT";
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
const parsedValue = coerceConfigValue(key, value, {
|
|
387
|
+
allowUnknown: options.allowUnknown === true,
|
|
388
|
+
});
|
|
143
389
|
// Serialize the read-modify-write across processes: two concurrent
|
|
144
390
|
// `cc config set` invocations would otherwise each load, mutate, and write
|
|
145
391
|
// back, silently losing one update. Configuration can carry credentials and
|
|
146
392
|
// permission policy, so bounded lock failure must never write unlocked.
|
|
147
393
|
const configPath = getConfigPath();
|
|
148
|
-
|
|
394
|
+
ensurePrivateDirectory(dirname(configPath), {
|
|
395
|
+
applyWindowsAcl: true,
|
|
396
|
+
failIfUnavailable: true,
|
|
397
|
+
});
|
|
149
398
|
return withFileLock(
|
|
150
399
|
configPath,
|
|
151
400
|
() => {
|
|
152
401
|
const config = loadConfig({ failIfUnavailable: true });
|
|
153
|
-
setNestedValue(config, key,
|
|
402
|
+
setNestedValue(config, key, parsedValue);
|
|
154
403
|
saveConfig(config);
|
|
155
404
|
return config;
|
|
156
405
|
},
|
|
@@ -158,10 +407,189 @@ export function setConfigValue(key, value) {
|
|
|
158
407
|
);
|
|
159
408
|
}
|
|
160
409
|
|
|
410
|
+
/** Run a synchronous operation while holding the cross-process config lock. */
|
|
411
|
+
export function withConfigLock(callback) {
|
|
412
|
+
if (typeof callback !== "function") {
|
|
413
|
+
throw new TypeError("Config lock callback must be a function");
|
|
414
|
+
}
|
|
415
|
+
const configPath = getConfigPath();
|
|
416
|
+
ensurePrivateDirectory(dirname(configPath), {
|
|
417
|
+
applyWindowsAcl: true,
|
|
418
|
+
failIfUnavailable: true,
|
|
419
|
+
});
|
|
420
|
+
return withFileLock(configPath, callback, { failIfUnavailable: true });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** Serialize an arbitrary in-process config mutation under the config lock. */
|
|
424
|
+
export function updateConfigAtomically(mutator) {
|
|
425
|
+
if (typeof mutator !== "function") {
|
|
426
|
+
throw new TypeError("Config mutator must be a function");
|
|
427
|
+
}
|
|
428
|
+
return withConfigLock(() => {
|
|
429
|
+
const config = loadConfig({ failIfUnavailable: true });
|
|
430
|
+
mutator(config);
|
|
431
|
+
saveConfig(config);
|
|
432
|
+
return config;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Persist a secret without ever accepting it as a positional CLI argument.
|
|
438
|
+
* `auto` prefers the OS store and falls back to the owner-only config file.
|
|
439
|
+
*/
|
|
440
|
+
export function setSecretConfigValue(key, value, options = {}) {
|
|
441
|
+
if (!isSecretConfigKey(key)) {
|
|
442
|
+
const error = new Error(
|
|
443
|
+
`${key} is not declared as a secret configuration key`,
|
|
444
|
+
);
|
|
445
|
+
error.code = "CONFIG_KEY_NOT_SECRET";
|
|
446
|
+
throw error;
|
|
447
|
+
}
|
|
448
|
+
const issues = validateConfigValue(key, value);
|
|
449
|
+
if (issues.length)
|
|
450
|
+
throw Object.assign(new Error(issues[0].message), issues[0]);
|
|
451
|
+
const storage = options.storage || "auto";
|
|
452
|
+
if (!new Set(["auto", "keychain", "file"]).has(storage)) {
|
|
453
|
+
throw new Error("Secret storage must be auto, keychain, or file");
|
|
454
|
+
}
|
|
455
|
+
const configPath = getConfigPath();
|
|
456
|
+
ensurePrivateDirectory(dirname(configPath), {
|
|
457
|
+
applyWindowsAcl: true,
|
|
458
|
+
failIfUnavailable: true,
|
|
459
|
+
});
|
|
460
|
+
return withFileLock(
|
|
461
|
+
configPath,
|
|
462
|
+
() => {
|
|
463
|
+
const config = loadConfig({
|
|
464
|
+
failIfUnavailable: true,
|
|
465
|
+
resolveSecrets: false,
|
|
466
|
+
});
|
|
467
|
+
const previousValue = getNestedValue(config, key);
|
|
468
|
+
const previousRef = isSecretRef(previousValue)
|
|
469
|
+
? previousValue.__cc_secret_ref
|
|
470
|
+
: null;
|
|
471
|
+
if (typeof options.configMutator === "function") {
|
|
472
|
+
options.configMutator(config);
|
|
473
|
+
}
|
|
474
|
+
if (storage !== "file") {
|
|
475
|
+
// A new immutable reference makes the keychain write + config rename a
|
|
476
|
+
// small transaction: if persistence fails, the still-active old config
|
|
477
|
+
// can only resolve its old value, never the just-written one.
|
|
478
|
+
const ref = createSecretStorageRef(configPath, key);
|
|
479
|
+
let store;
|
|
480
|
+
try {
|
|
481
|
+
store = options.secretStore || _deps.createSecretStore();
|
|
482
|
+
store.set(ref, String(value));
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (storage === "keychain") {
|
|
485
|
+
const wrapped = new Error(
|
|
486
|
+
`OS secret store is unavailable for ${key}: ${error.message}`,
|
|
487
|
+
{ cause: error },
|
|
488
|
+
);
|
|
489
|
+
wrapped.code = "CONFIG_SECRET_STORE_UNAVAILABLE";
|
|
490
|
+
throw wrapped;
|
|
491
|
+
}
|
|
492
|
+
store = null;
|
|
493
|
+
}
|
|
494
|
+
if (store) {
|
|
495
|
+
// Persistence failures are not evidence that the OS store is
|
|
496
|
+
// unavailable. Keep them outside the fallback catch so a disk or
|
|
497
|
+
// atomic-rename failure can never silently downgrade the just-stored
|
|
498
|
+
// secret to plaintext configuration.
|
|
499
|
+
setNestedValue(config, key, secretRef(ref));
|
|
500
|
+
try {
|
|
501
|
+
// This load kept every secret as its persisted reference, so bypass
|
|
502
|
+
// the resolved-value restoration cache and persist this exact raw
|
|
503
|
+
// document.
|
|
504
|
+
saveConfig(config, { preserveSecretRefs: false });
|
|
505
|
+
} catch (error) {
|
|
506
|
+
deleteSecretRefBestEffort(
|
|
507
|
+
store,
|
|
508
|
+
ref,
|
|
509
|
+
"rollback after config write",
|
|
510
|
+
);
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
_loadedSecretRefs.set(secretRefCacheKey(configPath, key), {
|
|
514
|
+
ref,
|
|
515
|
+
resolved: String(value),
|
|
516
|
+
});
|
|
517
|
+
if (previousRef && previousRef !== ref) {
|
|
518
|
+
deleteSecretRefBestEffort(
|
|
519
|
+
store,
|
|
520
|
+
previousRef,
|
|
521
|
+
"replaced config secret",
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
return { key, storage: "keychain", backend: store.name };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
setNestedValue(config, key, String(value));
|
|
528
|
+
saveConfig(config, { preserveSecretRefs: false });
|
|
529
|
+
if (previousRef) {
|
|
530
|
+
let previousStore = options.secretStore || null;
|
|
531
|
+
if (!previousStore) {
|
|
532
|
+
try {
|
|
533
|
+
previousStore = _deps.createSecretStore();
|
|
534
|
+
} catch {
|
|
535
|
+
previousStore = null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
deleteSecretRefBestEffort(
|
|
539
|
+
previousStore,
|
|
540
|
+
previousRef,
|
|
541
|
+
"moved config secret to file storage",
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
return { key, storage: "file", backend: null };
|
|
545
|
+
},
|
|
546
|
+
{ failIfUnavailable: true },
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
161
550
|
export function resetConfig() {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
551
|
+
const configPath = getConfigPath();
|
|
552
|
+
ensurePrivateDirectory(dirname(configPath), {
|
|
553
|
+
applyWindowsAcl: true,
|
|
554
|
+
failIfUnavailable: true,
|
|
555
|
+
});
|
|
556
|
+
return withFileLock(
|
|
557
|
+
configPath,
|
|
558
|
+
() => {
|
|
559
|
+
const current = loadConfig({
|
|
560
|
+
failIfUnavailable: true,
|
|
561
|
+
resolveSecrets: false,
|
|
562
|
+
});
|
|
563
|
+
const refs = [];
|
|
564
|
+
const collectRefs = (value) => {
|
|
565
|
+
if (isSecretRef(value)) {
|
|
566
|
+
refs.push(value.__cc_secret_ref);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (!value || typeof value !== "object") return;
|
|
570
|
+
for (const child of Object.values(value)) collectRefs(child);
|
|
571
|
+
};
|
|
572
|
+
collectRefs(current);
|
|
573
|
+
|
|
574
|
+
const config = structuredClone(DEFAULT_CONFIG);
|
|
575
|
+
saveConfig(config, { preserveSecretRefs: false });
|
|
576
|
+
if (refs.length > 0) {
|
|
577
|
+
let store = null;
|
|
578
|
+
try {
|
|
579
|
+
store = _deps.createSecretStore();
|
|
580
|
+
} catch (error) {
|
|
581
|
+
_deps.warn(
|
|
582
|
+
`Warning: config reset could not open the OS secret store (${error.message}).\n`,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
for (const ref of new Set(refs)) {
|
|
586
|
+
deleteSecretRefBestEffort(store, ref, "config reset");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return config;
|
|
590
|
+
},
|
|
591
|
+
{ failIfUnavailable: true },
|
|
592
|
+
);
|
|
165
593
|
}
|
|
166
594
|
|
|
167
595
|
export function listConfig() {
|
|
@@ -190,15 +618,6 @@ function setNestedValue(obj, key, value) {
|
|
|
190
618
|
current[parts[parts.length - 1]] = value;
|
|
191
619
|
}
|
|
192
620
|
|
|
193
|
-
function parseValue(value) {
|
|
194
|
-
if (value === "true") return true;
|
|
195
|
-
if (value === "false") return false;
|
|
196
|
-
if (value === "null") return null;
|
|
197
|
-
const num = Number(value);
|
|
198
|
-
if (!isNaN(num) && value.trim() !== "") return num;
|
|
199
|
-
return value;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
621
|
function deepMerge(target, source) {
|
|
203
622
|
const result = { ...target };
|
|
204
623
|
for (const key of Object.keys(source)) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** One redaction boundary for every configuration output surface. */
|
|
2
|
+
import { isSecretConfigKey } from "./config-schema.js";
|
|
3
|
+
import { redactSecrets } from "./secret-scan.js";
|
|
4
|
+
import { isSecretRef } from "./secret-store.js";
|
|
5
|
+
|
|
6
|
+
export const CONFIG_REDACTED = "[REDACTED]";
|
|
7
|
+
export const CONFIG_CONFIGURED = "<configured>";
|
|
8
|
+
|
|
9
|
+
export function redactConfigValue(key, value) {
|
|
10
|
+
const leaf = String(key || "")
|
|
11
|
+
.split(".")
|
|
12
|
+
.at(-1);
|
|
13
|
+
if (/^headersHelper$/i.test(leaf || "")) {
|
|
14
|
+
return value == null || value === "" ? value : CONFIG_CONFIGURED;
|
|
15
|
+
}
|
|
16
|
+
if (isSecretConfigKey(key) || isSecretRef(value)) {
|
|
17
|
+
return value == null || value === "" ? value : CONFIG_REDACTED;
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
return value.map((entry, index) =>
|
|
22
|
+
redactConfigValue(`${key}.${index}`, entry),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === "object") {
|
|
26
|
+
return redactConfigObject(value, key);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function redactConfigObject(config, prefix = "") {
|
|
32
|
+
if (!config || typeof config !== "object") return config;
|
|
33
|
+
if (Array.isArray(config)) {
|
|
34
|
+
return config.map((entry, index) =>
|
|
35
|
+
redactConfigValue(`${prefix}.${index}`, entry),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [name, value] of Object.entries(config)) {
|
|
40
|
+
const key = prefix ? `${prefix}.${name}` : name;
|
|
41
|
+
// Defining an own data property avoids the legacy Object.prototype
|
|
42
|
+
// __proto__ setter while retaining a normal serializable object.
|
|
43
|
+
Object.defineProperty(out, name, {
|
|
44
|
+
value: redactConfigValue(key, value),
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function stringifyRedactedConfig(config, space = 2) {
|
|
54
|
+
return JSON.stringify(redactConfigObject(config), null, space);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { isSecretConfigKey } from "./config-schema.js";
|