chainlesschain 0.162.156 → 0.162.158
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DjBXlypQ.js → AIOps-BwpTJTN_.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Cv9er9bp.js → ActionButton-BtQOoo3l.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CLKDjSNs.js → Analytics-Duf_rXZa.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-XQK3wjvB.js +9 -0
- package/src/assets/web-panel/assets/Artifacts-CTVt96zW.css +1 -0
- package/src/assets/web-panel/assets/Artifacts-DQvJoQ6E.js +1 -0
- package/src/assets/web-panel/assets/{Audit-CXafD178.js → Audit-DNjOGQLh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CP5c-GSd.js → BackgroundAgents-FH9RAfB5.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BbYLQfyt.js → Backup-ZIbrR4-U.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bqe0MwZL.js → BaseInput-DSt3NaND.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-jNA_yUPH.js → Chat-CY-wvbjv.js} +6 -6
- package/src/assets/web-panel/assets/Chat-KSGPtU0L.css +1 -0
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BOFLF01s.js → ChatBubbleRenderer-BAV9V-t-.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Dlzz-zD1.js → Checkbox-8JVadE_B.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BAsDQjGf.js → Codegen-CN83nPYI.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Ju0HrAuS.js → Col-xPSvqdN8.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DynJd43Z.js → Community-D78cnIjG.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CabbZVww.js → Compact-CSw6Csbp.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DW4BmAoh.js → Compliance-E_HhjcYR.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bk1S_7X-.js → Cowork-DybeQjmm.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-By4sh_NM.js → Cron-DS0BJeLY.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DdZdbS1C.js → Crosschain-BOwVgkbs.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CkL-sQyS.js → DID-DRUr9YuX.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-g_mOpumg.js → Dashboard-Cht-yUKW.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DtHyE7Ao.js → Dropdown-BLr_EJ9X.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Kpvov5Wr.js → EmailListRenderer-Drexuh5W.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-C4kZoePR.js → FamilyGuardDashboard-D9Q2Kq63.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BAwWoK2N.js → Federation-C-Z1xI-T.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DeI0i5f8.js → FormItemContext-DLHPMkQF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CkcKRtos.js → GenericCardRenderer-DxxEdn2r.js} +1 -1
- package/src/assets/web-panel/assets/{Git-kX6jjI9y.js → Git-syATcaBt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXJCBm6x.js → Governance-CYe_r8Zf.js} +1 -1
- package/src/assets/web-panel/assets/Inference-GWxErB7k.js +1 -0
- package/src/assets/web-panel/assets/{KnowledgeGraph-3Dnuf1Wh.js → KnowledgeGraph-qQvVCZoi.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-lVAZCtUB.js → Logs-BenAU2W8.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-7agE3Or1.js +1 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-DbzhFwvz.css +1 -0
- package/src/assets/web-panel/assets/Marketplace-D4Jn5fpD.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-CqjzefRU.js → McpTools-DtAwoqiQ.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BFiPAxk6.js → Memory-BI_O50I8.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D0O43VwF.js → MobileBridge-CQWE2lqx.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B3now48t.js → MobileProjects-CVtTqgtg.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DybhtaCS.js → Mtc-Dnr16qsn.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D7MmY4F0.js → MtcAudit-CQ5Xe4-a.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wZvGJnB8.js → Multisig-Bjr8O-wy.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BNPpgfvn.js → NLProgramming-CEe-KirU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B6FS3KBj.js → Notes-spKJIKF9.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CK0yla9U.js → NotificationSettings-DRp83LFG.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-h1A0XB2a.js +1 -0
- package/src/assets/web-panel/assets/{Organization-geqgwN1T.js → Organization-BlgB0IYg.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BWJYZFbC.js → Overflow-a_35HqZI.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DnU1505F.js → P2P-CSNHCNPC.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BGY74KnC.js → PdhVaultBrowser-C8M1JH-1.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CgArrWtQ.js → Permissions-DmjJi9Ms.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C2vYE61a.js → PersonalDataHub-DfuwRyf4.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CBYLI4yW.js → Pipeline-B2-S4ekE.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-B-PdW9aN.js → Privacy-B1dOiBLC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CuWQegbV.js → ProjectInit-_pNmc-DX.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-4d32m_cS.js → ProjectSettings-DmwG2Ii1.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-VLA7ttwd.js → Projects-COATmHxF.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B_Izb_oa.js → Providers-C6HSjg1d.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C6B_ndXV.js → QrScannerModal-CrDmS7l1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C6tavL-y.js → QuickAsk-CfKvWt-x.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CF-LaZ6x.js → Recommend-DMa6Kuzv.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DrktUo5x.js +4 -0
- package/src/assets/web-panel/assets/Reputation-BTPQ1vjL.js +1 -0
- package/src/assets/web-panel/assets/{Row-DO0fd48A.js → Row-B6Q7QkMG.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BV5LDzFR.js → RssFeed-B1IJhAmX.js} +3 -3
- package/src/assets/web-panel/assets/Search-xbea1kGD.js +1 -0
- package/src/assets/web-panel/assets/{Security-DwSrWRCw.js → Security-7rs0cO3F.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BEf1QyM4.js → Services-0fjixK0S.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-LZXTpovy.js → Skeleton-B_rVq9Cu.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-SKgc0Zug.js → Skills-C5uM90b9.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_QVOtkN.js → Sla-U4pYCIZH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-v5TW2V-X.js → SpeechSettings-Cdz4yTWx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CQPjHdqU.js → SyncSettings-Me3aetHv.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DCd-uYcI.js → Tasks-Cf6aNkL_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-62VDIjtj.js → Templates-CYatk4tU.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BnFQ0pNy.js → Tenant-DIMKLw7o.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D3v0MGSj.js → Terminal-C_cp-uSJ.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BI1Ka-cu.js → TimelineRenderer-cUPVNADp.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BwX1jXYP.js → Tokens-CiaEIXVf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CSA8TqNx.js → Trigger-DB1WU5lQ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bn3IjCx7.js → Trust-Cfht9Ijy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-pUbOAqNi.js → UkeySign-K3P4Vt9S.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Db2RercF.js → VideoEditing-DWjhjXRh.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CpR1BVAl.js → Wallet-D9-Q1Kan.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Yxtkn7Zp.js → WebAuthn-BDszy-oV.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CYLe9a2E.js → WorkflowEditor-DIhij4rA.js} +1 -1
- package/src/assets/web-panel/assets/{chat-gsOGdak0.js → chat-D7VCiKbh.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D9fN299a.js → colors-BvOd9pVn.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-vkd7s-ay.js → compact-item-DoqXBiAO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C7YkbMyy.js → createContext-CrKRzUtp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-nDB--2xr.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-ig7reRSU.js → hasIn-BWAev4WC.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DEmcnxBf.js → icons-DG5UD-6K.js} +4 -4
- package/src/assets/web-panel/assets/{index-W52sHRdr.js → index-1poMpMU0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZAxCnjS.js → index-8g5TJCZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-9TxdMYju.js → index-B0vGEbJT.js} +1 -1
- package/src/assets/web-panel/assets/index-B1_pRTtB.js +1 -0
- package/src/assets/web-panel/assets/{index-DhdXbsXX.js → index-B30NvR21.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7QRpKif.js → index-BCyP071m.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bt-9ZhIu.js → index-BHOcVYrx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYe9HRSt.js → index-BN9m4Ux2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkP17Z95.js → index-BWWsaimP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDWTycrg.js → index-BXyl8O23.js} +1 -1
- package/src/assets/web-panel/assets/{index-CspGymmg.js → index-BZGk8AbZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFfxF7Vt.js → index-Bf9YlbDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ8U7kzC.js → index-Bj7EgmOp.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2yhtk6a.js → index-BkgnGX7v.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzGr6CVk.js → index-BoYrS_L2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMw48d19.js → index-Bq_ctRpz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAkz1f1r.js → index-C5kRmj3P.js} +1 -1
- package/src/assets/web-panel/assets/{index-SDrcQXO1.js → index-CDGLdn12.js} +1 -1
- package/src/assets/web-panel/assets/{index-gj8kxcAi.js → index-CEPHMSih.js} +1 -1
- package/src/assets/web-panel/assets/{index-PkH39qsp.js → index-CEXfKSiI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHHN_KTS.js → index-CSb-tnIo.js} +1 -1
- package/src/assets/web-panel/assets/{index-gpurplyq.js → index-CmJ6xKBO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfpVcXKa.js → index-CoD31Ndm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGzGe5tv.js → index-Cx7M7PSF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhftELHI.js → index-D23L1V76.js} +3 -3
- package/src/assets/web-panel/assets/{index-DPguq3X7.js → index-DV-Rfmfx.js} +4 -4
- package/src/assets/web-panel/assets/{index-BpGlLbRv.js → index-DlGu7As6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8HZMygt.js → index-DvDOpsJB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuwbKHoS.js → index-HwHdTusY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7AY39sg.js → index-ISvPZ05r.js} +1 -1
- package/src/assets/web-panel/assets/{index-nX8YE6cj.js → index-JzUmJMvn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvCJXmrx.js → index-KR1-Em9C.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co2NYyew.js → index-PDxGjKRu.js} +1 -1
- package/src/assets/web-panel/assets/index-TMM9FuW-.js +1 -0
- package/src/assets/web-panel/assets/{index-D6s_kbjt.js → index-YGDy7nNq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy_TKZ9h.js → index-gmxqu5Kk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFM1oNe1.js → index-oY45XrkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-bVbgYBhv.js → index-ww1QtLnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvDEukXe.js → index-zH-tuuGZ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BrgfVRg9.js → initDefaultProps-XW-OR3mL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D_3xmwac.js → motion-CeuEHh20.js} +1 -1
- package/src/assets/web-panel/assets/{move-DDDJdlxw.js → move-Dva79Keg.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BK2uEWwn.js → mtc-parser-C0B6bD2j.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BPHKbDcf.js → omit-AkZvO-a2.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-LNnTscuz.js → pickAttrs-B4r55qGc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CvmTZll5.js → placementArrow-BLH-z7YE.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-eBj7qi2J.js → responsiveObserve-ksEGZ2Wf.js} +1 -1
- package/src/assets/web-panel/assets/{slide-D68hHkrM.js → slide-Cth4OD1Z.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-daTlaeo0.js → statusUtils-9S83ezxw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DtyKMtHB.js → styleChecker-7yQmQGZ4.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-ekFwFs9h.js → useFlexGapSupport-DpGnMmPc.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-jRaSEoF4.js → useFs-C4zDZIXH.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DD5nGzjA.js → usePersonalDataHub-CoLICdUZ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-KcgLUiom.js → vnode-CKyH-35X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cyk2PQ-s.js → zoom-DRDGit17.js} +1 -1
- package/src/assets/web-panel/index.html +2 -2
- package/src/command-manifest.json +8 -1
- package/src/commands/agent.js +49 -5
- package/src/commands/artifacts.js +161 -0
- package/src/commands/browse.js +204 -2
- package/src/data/changelog.json +13 -2
- package/src/gateways/ws/artifact-protocol.js +219 -0
- package/src/gateways/ws/message-dispatcher.js +6 -0
- package/src/gateways/ws/ws-server.js +32 -0
- package/src/index.js +6 -0
- package/src/lib/accessibility.js +55 -0
- package/src/lib/artifact-store.js +236 -0
- package/src/lib/chrome-connector.js +349 -0
- package/src/lib/hook-runner.cjs +47 -7
- package/src/lib/ide-context.js +84 -0
- package/src/lib/permission-rules.cjs +26 -3
- package/src/lib/plugin-runtime/scopes.js +12 -0
- package/src/lib/remote-control.js +35 -0
- package/src/lib/safe-mode.js +40 -0
- package/src/lib/shell-selector.cjs +203 -0
- package/src/lib/skill-loader.js +19 -0
- package/src/lib/slash-dispatch.js +34 -0
- package/src/lib/web-ui-server.js +120 -7
- package/src/repl/agent-repl.js +330 -116
- package/src/repl/remote-approval.js +111 -0
- package/src/runtime/agent-core.js +129 -8
- package/src/runtime/coding-agent-contract-shared.cjs +105 -0
- package/src/runtime/coding-agent-policy.cjs +31 -0
- package/src/runtime/headless-stream.js +51 -1
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/assets/web-panel/assets/AppLayout-CfyZNzmJ.js +0 -9
- package/src/assets/web-panel/assets/Chat-DDh58PCf.css +0 -1
- package/src/assets/web-panel/assets/Inference-ujD2JSKo.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-qnzNPm-A.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CXfPHIpy.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-DBqTTQ9_.js +0 -4
- package/src/assets/web-panel/assets/Reputation-B01FALUi.js +0 -1
- package/src/assets/web-panel/assets/Search-CTti4_MP.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEcR2h6k.js +0 -1
- package/src/assets/web-panel/assets/index-BEibanjg.js +0 -1
- package/src/assets/web-panel/assets/index-z_25tU3p.js +0 -1
package/src/lib/safe-mode.js
CHANGED
|
@@ -43,3 +43,43 @@ export function safeModeRequested(opts = {}, env = process.env) {
|
|
|
43
43
|
const raw = env.CC_SAFE_MODE || env.CLAUDE_CODE_SAFE_MODE;
|
|
44
44
|
return raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim());
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `cc agent --bare` — everything --safe-mode disables PLUS skills, plugins and
|
|
49
|
+
* MCP auto-connect: a minimal, fast agent surface for scripted invocations.
|
|
50
|
+
* The two extra env switches are consumed at the single chokepoints all
|
|
51
|
+
* loading funnels through (skill-loader `loadAll`, plugin-runtime
|
|
52
|
+
* `discoverPlugins`); MCP/IDE/PDH/JetBrains auto-connect are flipped off on
|
|
53
|
+
* the parsed options by the agent command itself (an explicit --mcp-config
|
|
54
|
+
* still loads — bare kills ambient auto-connects, not explicit input).
|
|
55
|
+
* Permission rules stay active, same as safe mode.
|
|
56
|
+
*/
|
|
57
|
+
export const BARE_MODE_EXTRA_SWITCHES = Object.freeze({
|
|
58
|
+
CC_SKILLS: "0", // every skill layer (list_skills/run_skill surface empties)
|
|
59
|
+
CC_PLUGINS: "0", // plugin runtime (hooks/monitors/MCP/LSP/bin/env/skills/agents)
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Apply bare mode to an env: all safe-mode switches + the bare extras.
|
|
64
|
+
* @returns {string[]} the switch names applied (for the startup notice)
|
|
65
|
+
*/
|
|
66
|
+
export function applyBareMode(env = process.env) {
|
|
67
|
+
const applied = applySafeMode(env);
|
|
68
|
+
for (const [k, v] of Object.entries(BARE_MODE_EXTRA_SWITCHES)) {
|
|
69
|
+
env[k] = v;
|
|
70
|
+
}
|
|
71
|
+
return [...applied, ...Object.keys(BARE_MODE_EXTRA_SWITCHES)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether bare mode is requested — the explicit `--bare` flag or the ambient
|
|
76
|
+
* `CC_BARE` env var (truthy values: 1 / true / yes / on).
|
|
77
|
+
* @param {{ bare?: boolean }} [opts]
|
|
78
|
+
* @param {object} [env=process.env]
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
export function bareModeRequested(opts = {}, env = process.env) {
|
|
82
|
+
if (opts && opts.bare === true) return true;
|
|
83
|
+
const raw = env.CC_BARE;
|
|
84
|
+
return raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim());
|
|
85
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* shell-selector — Windows / PowerShell first-class shell selection
|
|
5
|
+
* (gap-analysis P1 #8).
|
|
6
|
+
*
|
|
7
|
+
* Decides WHICH shell a `run_shell` command (or a settings hook with a
|
|
8
|
+
* `shell` field) executes under:
|
|
9
|
+
*
|
|
10
|
+
* - per-call: run_shell `{ shell: "powershell" | "pwsh" | "cmd" | "default" }`
|
|
11
|
+
* - configured: settings `shell.windowsDefault` ("cmd" | "powershell" | "pwsh",
|
|
12
|
+
* layered .claude/settings.json, closest wins) or the
|
|
13
|
+
* CC_WINDOWS_SHELL env var (wins over settings)
|
|
14
|
+
* - default: unchanged platform default (cmd.exe via ComSpec on Windows,
|
|
15
|
+
* /bin/sh elsewhere) — the unconfigured path is byte-identical.
|
|
16
|
+
*
|
|
17
|
+
* PowerShell runs go through explicit argv (`powershell.exe -NoProfile
|
|
18
|
+
* [-ExecutionPolicy <p>] -Command <command>`), NOT `shell: "powershell.exe"`,
|
|
19
|
+
* so the enterprise ExecutionPolicy override (settings
|
|
20
|
+
* `shell.powershell.executionPolicy`) can be applied per-invocation without
|
|
21
|
+
* touching machine state. The policy value is validated against the fixed
|
|
22
|
+
* PowerShell enum — a settings typo can NOT smuggle extra argv.
|
|
23
|
+
*
|
|
24
|
+
* Scope notes:
|
|
25
|
+
* - `windowsDefault` only applies on win32 (hence the name). A per-call
|
|
26
|
+
* `pwsh` request is honored on any platform (PowerShell 7 is
|
|
27
|
+
* cross-platform); `powershell`/`cmd` requests outside Windows fall back
|
|
28
|
+
* to the platform default with a note.
|
|
29
|
+
* - `-ExecutionPolicy` is only attached on win32 (POSIX pwsh has no policy).
|
|
30
|
+
* - Hooks opt in per-hook via `{ "shell": "powershell" }` — the configured
|
|
31
|
+
* windowsDefault deliberately does NOT rewrite existing hooks.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const settingsLoader = require("./settings-loader.cjs");
|
|
35
|
+
|
|
36
|
+
const SHELL_KINDS = Object.freeze(["default", "cmd", "powershell", "pwsh"]);
|
|
37
|
+
|
|
38
|
+
/** The fixed PowerShell ExecutionPolicy enum (canonical casing). */
|
|
39
|
+
const EXECUTION_POLICIES = Object.freeze([
|
|
40
|
+
"Restricted",
|
|
41
|
+
"AllSigned",
|
|
42
|
+
"RemoteSigned",
|
|
43
|
+
"Unrestricted",
|
|
44
|
+
"Bypass",
|
|
45
|
+
"Undefined",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
/** Normalize a shell kind; unknown/empty → null (fail to default). */
|
|
49
|
+
function normalizeShellKind(value) {
|
|
50
|
+
if (typeof value !== "string") return null;
|
|
51
|
+
const v = value.trim().toLowerCase();
|
|
52
|
+
if (v === "powershell.exe" || v === "windowspowershell") return "powershell";
|
|
53
|
+
if (v === "pwsh.exe") return "pwsh";
|
|
54
|
+
if (v === "cmd.exe") return "cmd";
|
|
55
|
+
return SHELL_KINDS.includes(v) ? v : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Normalize an ExecutionPolicy to canonical casing; unknown → null. */
|
|
59
|
+
function normalizeExecutionPolicy(value) {
|
|
60
|
+
if (typeof value !== "string") return null;
|
|
61
|
+
const v = value.trim().toLowerCase();
|
|
62
|
+
return EXECUTION_POLICIES.find((p) => p.toLowerCase() === v) || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Layered `shell` settings: `{ windowsDefault, powershell: { executionPolicy } }`.
|
|
67
|
+
* Closest layer wins per key; invalid values are ignored (fail-to-default).
|
|
68
|
+
* CC_WINDOWS_SHELL env overrides the settings-provided windowsDefault.
|
|
69
|
+
*
|
|
70
|
+
* @param {{ cwd?: string, env?: object, settingsFile?: string, onWarn?: Function }} [opts]
|
|
71
|
+
* @returns {{ windowsDefault: string|null, executionPolicy: string|null, files: string[] }}
|
|
72
|
+
*/
|
|
73
|
+
// Per-process memo: run_shell resolves the config on EVERY call, and the
|
|
74
|
+
// layered settings walk is filesystem I/O. A short TTL keeps mid-session
|
|
75
|
+
// settings edits honored without re-reading per tool call.
|
|
76
|
+
const _configCache = new Map(); // key → { at, value }
|
|
77
|
+
const CONFIG_CACHE_TTL_MS = 3000;
|
|
78
|
+
|
|
79
|
+
function loadShellConfig(opts = {}) {
|
|
80
|
+
const cwd = opts.cwd || process.cwd();
|
|
81
|
+
const env = opts.env || process.env;
|
|
82
|
+
const cacheable = !opts.env && !opts.onWarn;
|
|
83
|
+
const cacheKey = cacheable ? `${cwd}|${opts.settingsFile || ""}` : null;
|
|
84
|
+
if (cacheKey) {
|
|
85
|
+
const hit = _configCache.get(cacheKey);
|
|
86
|
+
if (hit && Date.now() - hit.at < CONFIG_CACHE_TTL_MS) return hit.value;
|
|
87
|
+
}
|
|
88
|
+
let windowsDefault = null;
|
|
89
|
+
let executionPolicy = null;
|
|
90
|
+
const files = [];
|
|
91
|
+
try {
|
|
92
|
+
for (const file of settingsLoader.settingsPaths(cwd, opts.settingsFile)) {
|
|
93
|
+
const data = settingsLoader.readSettingsFile(file, {
|
|
94
|
+
onWarn: opts.onWarn,
|
|
95
|
+
});
|
|
96
|
+
const shell = data && typeof data === "object" ? data.shell : null;
|
|
97
|
+
if (!shell || typeof shell !== "object" || Array.isArray(shell)) continue;
|
|
98
|
+
const kind = normalizeShellKind(shell.windowsDefault);
|
|
99
|
+
if (kind) windowsDefault = kind;
|
|
100
|
+
const pol = normalizeExecutionPolicy(shell.powershell?.executionPolicy);
|
|
101
|
+
if (pol) executionPolicy = pol;
|
|
102
|
+
files.push(file);
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
/* settings are best-effort — never fail shell resolution over them */
|
|
106
|
+
}
|
|
107
|
+
const envKind = normalizeShellKind(env.CC_WINDOWS_SHELL);
|
|
108
|
+
if (envKind) windowsDefault = envKind;
|
|
109
|
+
const value = { windowsDefault, executionPolicy, files };
|
|
110
|
+
if (cacheKey) _configCache.set(cacheKey, { at: Date.now(), value });
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Test hook: drop the config memo. */
|
|
115
|
+
function _clearShellConfigCache() {
|
|
116
|
+
_configCache.clear();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Build the explicit argv for a PowerShell invocation of `command`.
|
|
121
|
+
* @param {string} command
|
|
122
|
+
* @param {"powershell"|"pwsh"} kind
|
|
123
|
+
* @param {{ executionPolicy?: string|null, platform?: string }} [opts]
|
|
124
|
+
* @returns {{ file: string, argv: string[] }}
|
|
125
|
+
*/
|
|
126
|
+
function buildPowershellArgv(command, kind, opts = {}) {
|
|
127
|
+
const platform = opts.platform || process.platform;
|
|
128
|
+
const file = kind === "pwsh" ? "pwsh" : "powershell.exe";
|
|
129
|
+
const policy =
|
|
130
|
+
platform === "win32"
|
|
131
|
+
? normalizeExecutionPolicy(opts.executionPolicy)
|
|
132
|
+
: null;
|
|
133
|
+
return {
|
|
134
|
+
file,
|
|
135
|
+
argv: [
|
|
136
|
+
"-NoProfile",
|
|
137
|
+
...(policy ? ["-ExecutionPolicy", policy] : []),
|
|
138
|
+
"-Command",
|
|
139
|
+
String(command ?? ""),
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve how to run one shell command.
|
|
146
|
+
*
|
|
147
|
+
* Precedence: per-call `requested` > configured windowsDefault (win32 only)
|
|
148
|
+
* > platform default. Returns either the default-shell marker (caller keeps
|
|
149
|
+
* its existing exec/spawn `shell: true` path — byte-identical) or an explicit
|
|
150
|
+
* `{ file, argv }` PowerShell invocation.
|
|
151
|
+
*
|
|
152
|
+
* @param {{ command: string, requested?: string, cwd?: string, env?: object,
|
|
153
|
+
* platform?: string, settingsFile?: string,
|
|
154
|
+
* config?: { windowsDefault?: string|null, executionPolicy?: string|null } }} opts
|
|
155
|
+
* @returns {{ kind: string, useDefaultShell: boolean, file?: string,
|
|
156
|
+
* argv?: string[], note?: string }}
|
|
157
|
+
*/
|
|
158
|
+
function resolveShellInvocation(opts = {}) {
|
|
159
|
+
const platform = opts.platform || process.platform;
|
|
160
|
+
const config = opts.config || loadShellConfig(opts);
|
|
161
|
+
const requested = normalizeShellKind(opts.requested);
|
|
162
|
+
let kind =
|
|
163
|
+
requested ||
|
|
164
|
+
(platform === "win32" ? config.windowsDefault : null) ||
|
|
165
|
+
"default";
|
|
166
|
+
let note;
|
|
167
|
+
|
|
168
|
+
if (kind === "cmd") {
|
|
169
|
+
if (platform === "win32") {
|
|
170
|
+
// cmd IS the Windows default shell — take the untouched default path so
|
|
171
|
+
// an explicit `shell:"cmd"` opt-out of a PowerShell windowsDefault stays
|
|
172
|
+
// byte-identical to the historical behavior.
|
|
173
|
+
return { kind: "cmd", useDefaultShell: true };
|
|
174
|
+
}
|
|
175
|
+
note = "cmd is Windows-only; using the platform default shell";
|
|
176
|
+
return { kind: "default", useDefaultShell: true, note };
|
|
177
|
+
}
|
|
178
|
+
if (kind === "powershell" && platform !== "win32") {
|
|
179
|
+
// Windows PowerShell 5.1 doesn't exist off-Windows; pwsh might.
|
|
180
|
+
note =
|
|
181
|
+
"powershell (5.1) is Windows-only; using the platform default shell (request pwsh for PowerShell 7)";
|
|
182
|
+
return { kind: "default", useDefaultShell: true, note };
|
|
183
|
+
}
|
|
184
|
+
if (kind === "powershell" || kind === "pwsh") {
|
|
185
|
+
const { file, argv } = buildPowershellArgv(opts.command, kind, {
|
|
186
|
+
executionPolicy: config.executionPolicy,
|
|
187
|
+
platform,
|
|
188
|
+
});
|
|
189
|
+
return { kind, useDefaultShell: false, file, argv };
|
|
190
|
+
}
|
|
191
|
+
return { kind: "default", useDefaultShell: true };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
SHELL_KINDS,
|
|
196
|
+
EXECUTION_POLICIES,
|
|
197
|
+
normalizeShellKind,
|
|
198
|
+
normalizeExecutionPolicy,
|
|
199
|
+
loadShellConfig,
|
|
200
|
+
buildPowershellArgv,
|
|
201
|
+
resolveShellInvocation,
|
|
202
|
+
_clearShellConfigCache,
|
|
203
|
+
};
|
package/src/lib/skill-loader.js
CHANGED
|
@@ -68,6 +68,19 @@ export function bundledSkillsDisabled(opts = {}) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Whether ALL skill layers are disabled — `cc agent --bare` sets CC_SKILLS=0
|
|
73
|
+
* (safe-mode "feature=0 disables" convention). Single gate: `loadAll` is the
|
|
74
|
+
* one place every layer funnels through, so this empties list_skills /
|
|
75
|
+
* run_skill / persona auto-activation in one check.
|
|
76
|
+
* @param {{ env?: object }} [opts]
|
|
77
|
+
* @returns {boolean}
|
|
78
|
+
*/
|
|
79
|
+
export function allSkillsDisabled(opts = {}) {
|
|
80
|
+
const raw = (opts.env || process.env).CC_SKILLS;
|
|
81
|
+
return raw != null && /^(0|false|no|off)$/i.test(String(raw).trim());
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
/** Layer names in priority order (lowest → highest) */
|
|
72
85
|
export const LAYER_NAMES = [
|
|
73
86
|
"bundled",
|
|
@@ -475,6 +488,12 @@ export class CLISkillLoader {
|
|
|
475
488
|
* @returns {object[]} Resolved skill list
|
|
476
489
|
*/
|
|
477
490
|
loadAll(options = {}) {
|
|
491
|
+
// `cc agent --bare` (CC_SKILLS=0): no skill layer loads at all — cache the
|
|
492
|
+
// empty result so getResolvedSkills stays cheap on every call.
|
|
493
|
+
if (allSkillsDisabled(options)) {
|
|
494
|
+
this._cache = [];
|
|
495
|
+
return this._cache;
|
|
496
|
+
}
|
|
478
497
|
const layers = this.getLayerPaths();
|
|
479
498
|
const skillMap = new Map();
|
|
480
499
|
// Claude-Code `disableBundledSkills`: hide the built-in layer so only
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc agent --disable-slash-commands` — REPL slash-dispatch bypass predicate.
|
|
3
|
+
*
|
|
4
|
+
* When active, a "/"-leading line is NOT dispatched to the built-in slash
|
|
5
|
+
* handlers (nor custom .claude/commands macros nor MCP prompt expansion): it
|
|
6
|
+
* reaches the model verbatim, the same fall-through an unmatched slash line
|
|
7
|
+
* already takes. `/exit` and `/quit` stay live so the session remains
|
|
8
|
+
* closable from the keyboard.
|
|
9
|
+
*
|
|
10
|
+
* The REPL swaps the dispatched string for a sentinel no handler can match
|
|
11
|
+
* (leading NUL — nothing in the dispatch chain starts with a control char)
|
|
12
|
+
* and restores the user's real text for the model turn.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The dispatch sentinel — starts with NUL so no handler branch matches. */
|
|
16
|
+
export function slashBypassSentinel() {
|
|
17
|
+
return String.fromCharCode(0) + "slash-bypassed";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Should this line bypass slash dispatch?
|
|
22
|
+
* @param {string} line the trimmed input line
|
|
23
|
+
* @param {boolean} disabled whether --disable-slash-commands is active
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export function slashDispatchBypassed(line, disabled) {
|
|
27
|
+
return (
|
|
28
|
+
disabled === true &&
|
|
29
|
+
typeof line === "string" &&
|
|
30
|
+
line.startsWith("/") &&
|
|
31
|
+
line !== "/exit" &&
|
|
32
|
+
line !== "/quit"
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/lib/web-ui-server.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import http from "http";
|
|
20
20
|
import fs from "fs";
|
|
21
21
|
import path from "path";
|
|
22
|
+
import crypto from "crypto";
|
|
22
23
|
import { fileURLToPath } from "url";
|
|
23
24
|
import { getInlineSource as getEnvelopeInlineSource } from "./web-ui-envelope.js";
|
|
24
25
|
import { CLISkillLoader } from "./skill-loader.js";
|
|
@@ -1224,28 +1225,140 @@ function findWebPanelDist(staticDir) {
|
|
|
1224
1225
|
return null;
|
|
1225
1226
|
}
|
|
1226
1227
|
|
|
1228
|
+
/** Constant-time token check (same construction as ws-server's): hash both
|
|
1229
|
+
* sides to a fixed length so length differences don't leak, then compare in
|
|
1230
|
+
* constant time. `null`/absent configured token → no auth required. */
|
|
1231
|
+
function downloadTokenOk(configuredToken, req) {
|
|
1232
|
+
if (!configuredToken) return true;
|
|
1233
|
+
let supplied = "";
|
|
1234
|
+
const auth = String(req.headers?.authorization || "");
|
|
1235
|
+
if (auth.toLowerCase().startsWith("bearer ")) {
|
|
1236
|
+
supplied = auth.slice(7).trim();
|
|
1237
|
+
} else {
|
|
1238
|
+
try {
|
|
1239
|
+
supplied =
|
|
1240
|
+
new URL(req.url, "http://localhost").searchParams.get("token") || "";
|
|
1241
|
+
} catch {
|
|
1242
|
+
supplied = "";
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const a = crypto.createHash("sha256").update(String(supplied)).digest();
|
|
1246
|
+
const b = crypto
|
|
1247
|
+
.createHash("sha256")
|
|
1248
|
+
.update(String(configuredToken))
|
|
1249
|
+
.digest();
|
|
1250
|
+
return crypto.timingSafeEqual(a, b);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/** RFC 5987 filename* value for a Content-Disposition header. */
|
|
1254
|
+
function encodeDispositionFilename(name) {
|
|
1255
|
+
const base = String(name || "artifact").replace(/[\r\n"]/g, "_");
|
|
1256
|
+
return `attachment; filename*=UTF-8''${encodeURIComponent(base)}`;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1227
1259
|
/**
|
|
1228
1260
|
* Handle `/api/*` routes. Returns `true` if the request was handled (caller
|
|
1229
1261
|
* must stop further processing) and `false` for everything else (caller
|
|
1230
1262
|
* continues with the static/SPA handler).
|
|
1231
1263
|
*
|
|
1232
|
-
*
|
|
1264
|
+
* Endpoints:
|
|
1233
1265
|
*
|
|
1234
1266
|
* GET /api/skills → 200 { schema: 1, skills: [{name, source, category,
|
|
1235
1267
|
* description, version}] }
|
|
1268
|
+
* GET /api/artifacts/<id>/download → the artifact's stored copy as an
|
|
1269
|
+
* attachment (full file — the WS artifact-content preview
|
|
1270
|
+
* route is capped; this is the uncapped download path the
|
|
1271
|
+
* web-panel's 下载 button uses). When the server was
|
|
1272
|
+
* created with a wsToken, the same token must accompany
|
|
1273
|
+
* the request (`Authorization: Bearer` or `?token=`).
|
|
1236
1274
|
*
|
|
1237
|
-
* The smoke-runner's project-mode probe (packer/smoke-runner.js) hits
|
|
1238
|
-
* to verify bundled skills were materialized and registered. The
|
|
1239
|
-
* deliberately minimal — the Web UI's richer
|
|
1275
|
+
* The smoke-runner's project-mode probe (packer/smoke-runner.js) hits
|
|
1276
|
+
* /api/skills to verify bundled skills were materialized and registered. The
|
|
1277
|
+
* shapes are deliberately minimal — the Web UI's richer views use WS.
|
|
1240
1278
|
*
|
|
1241
1279
|
* @param {import("http").IncomingMessage} req
|
|
1242
1280
|
* @param {import("http").ServerResponse} res
|
|
1281
|
+
* @param {{ wsToken?: string|null }} [ctx] server context (auth for downloads)
|
|
1243
1282
|
* @returns {boolean} true if handled, false if the caller should continue
|
|
1244
1283
|
*/
|
|
1245
|
-
export function handleApiRequest(req, res) {
|
|
1284
|
+
export function handleApiRequest(req, res, ctx = {}) {
|
|
1246
1285
|
const urlPath = req.url.split("?")[0];
|
|
1247
1286
|
if (!urlPath.startsWith("/api/")) return false;
|
|
1248
1287
|
|
|
1288
|
+
const artifactMatch = urlPath.match(
|
|
1289
|
+
/^\/api\/artifacts\/([A-Za-z0-9_]+)\/download$/,
|
|
1290
|
+
);
|
|
1291
|
+
if (artifactMatch) {
|
|
1292
|
+
if (req.method !== "GET") {
|
|
1293
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
1294
|
+
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
1295
|
+
return true;
|
|
1296
|
+
}
|
|
1297
|
+
// The panel authenticates its WS with this token; the download endpoint
|
|
1298
|
+
// serves artifact FILE CONTENT, so it must not be weaker than the WS.
|
|
1299
|
+
if (!downloadTokenOk(ctx.wsToken || null, req)) {
|
|
1300
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1301
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
1302
|
+
return true;
|
|
1303
|
+
}
|
|
1304
|
+
// Async body in a sync router: the route is claimed (return true) and the
|
|
1305
|
+
// response completes when the store import resolves.
|
|
1306
|
+
(async () => {
|
|
1307
|
+
try {
|
|
1308
|
+
const { ArtifactStore } = await import("./artifact-store.js");
|
|
1309
|
+
const store = new ArtifactStore();
|
|
1310
|
+
const entry = store.get(artifactMatch[1]);
|
|
1311
|
+
if (!entry) {
|
|
1312
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1313
|
+
res.end(JSON.stringify({ error: "Not Found" }));
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
// Same tamper guard as the WS content route: the stored filename must
|
|
1317
|
+
// be a plain basename or the index row is refused.
|
|
1318
|
+
if (
|
|
1319
|
+
!entry.file ||
|
|
1320
|
+
entry.file !== path.basename(entry.file) ||
|
|
1321
|
+
entry.file.includes("..")
|
|
1322
|
+
) {
|
|
1323
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1324
|
+
res.end(JSON.stringify({ error: "Bad artifact index row" }));
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const storedPath = store.storedPath(entry);
|
|
1328
|
+
let size;
|
|
1329
|
+
try {
|
|
1330
|
+
size = fs.statSync(storedPath).size;
|
|
1331
|
+
} catch {
|
|
1332
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1333
|
+
res.end(JSON.stringify({ error: "Stored copy missing" }));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
const downloadName =
|
|
1337
|
+
entry.title && path.extname(entry.title)
|
|
1338
|
+
? entry.title
|
|
1339
|
+
: path.basename(entry.sourcePath || entry.file);
|
|
1340
|
+
res.writeHead(200, {
|
|
1341
|
+
"Content-Type": entry.mime || "application/octet-stream",
|
|
1342
|
+
"Content-Length": size,
|
|
1343
|
+
"Content-Disposition": encodeDispositionFilename(downloadName),
|
|
1344
|
+
"Cache-Control": "no-store",
|
|
1345
|
+
"X-Content-Type-Options": "nosniff",
|
|
1346
|
+
});
|
|
1347
|
+
const stream = fs.createReadStream(storedPath);
|
|
1348
|
+
stream.on("error", () => res.destroy());
|
|
1349
|
+
stream.pipe(res);
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
try {
|
|
1352
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1353
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1354
|
+
} catch {
|
|
1355
|
+
/* headers already sent — connection is torn down by the stream */
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
})();
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1249
1362
|
if (urlPath === "/api/skills") {
|
|
1250
1363
|
if (req.method !== "GET") {
|
|
1251
1364
|
res.writeHead(405, { "Content-Type": "application/json" });
|
|
@@ -1328,7 +1441,7 @@ export function createWebUIServer(opts) {
|
|
|
1328
1441
|
return http.createServer((req, res) => {
|
|
1329
1442
|
// /api/* routes are handled before the static file logic so GET /api/skills
|
|
1330
1443
|
// doesn't accidentally resolve to index.html via the SPA fallback.
|
|
1331
|
-
if (handleApiRequest(req, res)) return;
|
|
1444
|
+
if (handleApiRequest(req, res, { wsToken: opts.wsToken || null })) return;
|
|
1332
1445
|
|
|
1333
1446
|
if (req.method !== "GET") {
|
|
1334
1447
|
res.writeHead(405, { "Content-Type": "text/plain" });
|
|
@@ -1415,7 +1528,7 @@ export function createWebUIServer(opts) {
|
|
|
1415
1528
|
// ── Fallback: embedded classic single-page HTML ─────────────────────────
|
|
1416
1529
|
const html = buildHtml(opts);
|
|
1417
1530
|
return http.createServer((req, res) => {
|
|
1418
|
-
if (handleApiRequest(req, res)) return;
|
|
1531
|
+
if (handleApiRequest(req, res, { wsToken: opts.wsToken || null })) return;
|
|
1419
1532
|
|
|
1420
1533
|
const urlPath = req.url.split("?")[0];
|
|
1421
1534
|
if (
|