chainlesschain 0.162.123 → 0.162.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/AIOps-DcqNhZhA.js +1 -0
- package/src/assets/web-panel/assets/{ActionButton-BugJtiJc.js → ActionButton-BigtmPoq.js} +1 -1
- package/src/assets/web-panel/assets/Analytics-1J_X_HF4.js +3 -0
- package/src/assets/web-panel/assets/{AppLayout-Cb0eHetM.js → AppLayout-DlcDG_a_.js} +5 -5
- package/src/assets/web-panel/assets/Audit-Do-y8_3w.js +1 -0
- package/src/assets/web-panel/assets/Backup-S2Df-50Y.js +1 -0
- package/src/assets/web-panel/assets/{BaseInput-CZ0c7QHk.js → BaseInput-D5kgWJfk.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CaKDT3wV.js → Chat-aUcp3kN1.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6-Iu3bG.js → Checkbox-DTg1U7Xs.js} +1 -1
- package/src/assets/web-panel/assets/Codegen-YO9ZZ4Ky.js +1 -0
- package/src/assets/web-panel/assets/{Col-DVBrO7AN.js → Col-BHonE9fU.js} +1 -1
- package/src/assets/web-panel/assets/Community-DA2AlEFh.js +1 -0
- package/src/assets/web-panel/assets/{Compact-DM4ZgFSE.js → Compact-DULxLRNg.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-BZqfuuZL.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-CJwiWkUY.js → Cowork-CJe3vyMS.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-dsdXHvrO.js → Cron-CeV8AtRu.js} +2 -2
- package/src/assets/web-panel/assets/Crosschain-CAg66zS5.js +1 -0
- package/src/assets/web-panel/assets/{DID-BB_LBLYT.js → DID-Dtup5JZm.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-DAR_8PNy.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-dTFtVnKk.js → Dropdown-K5bTaCYN.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-yqdttzXV.js → EmailListRenderer-C890uErx.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DfX3YpUU.js → FamilyGuardDashboard-B-Tj_8yM.js} +1 -1
- package/src/assets/web-panel/assets/Federation-C6WZ98ni.js +1 -0
- package/src/assets/web-panel/assets/{FormItemContext-DNMwIh7X.js → FormItemContext-D-LTfGWd.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C-T8UQf0.js → GenericCardRenderer-C80DK4W7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-SURFhkLi.js → Git-Cjvvv3zW.js} +2 -2
- package/src/assets/web-panel/assets/Governance-C0BND77H.js +1 -0
- package/src/assets/web-panel/assets/Inference-BL9kTtSu.js +1 -0
- package/src/assets/web-panel/assets/KnowledgeGraph-CnVTBmJf.js +1 -0
- package/src/assets/web-panel/assets/{Logs-DjuWREBJ.js → Logs-CbCbg7K6.js} +2 -2
- package/src/assets/web-panel/assets/Marketplace-CG5On-fH.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-D0waMomb.js → McpTools-AYYDZZK7.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Ci768GQ3.js → Memory-tMWbMDQq.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-BAOsf4wB.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-RpFuAiNz.js → MobileProjects-BT-2komQ.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DJdw8btT.js → Mtc-BFwfC63n.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Dc596XgH.js → MtcAudit-DYG3CWdH.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig--Qs7eGsH.js → Multisig-KJwd0yWT.js} +3 -3
- package/src/assets/web-panel/assets/NLProgramming-BmL5dNWm.js +1 -0
- package/src/assets/web-panel/assets/{Notes-uMxjj66_.js → Notes-DmkaVaMc.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C4ikBOsm.js → NotificationSettings-tmVQszDZ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BpmdunAz.js → OrderTableRenderer-BtrR7anf.js} +1 -1
- package/src/assets/web-panel/assets/Organization-pppktQyW.js +4 -0
- package/src/assets/web-panel/assets/{Overflow-B-4_IpbB.js → Overflow-D3lBoQDA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DdOf9BZs.js → P2P-BgE2qGlZ.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-D26Bz5gS.js +7 -0
- package/src/assets/web-panel/assets/Permissions-BR8no2Xe.js +4 -0
- package/src/assets/web-panel/assets/{PersonalDataHub-B3WcvKni.js → PersonalDataHub-DabbyQEF.js} +2 -2
- package/src/assets/web-panel/assets/Pipeline-n4sNpEz8.js +1 -0
- package/src/assets/web-panel/assets/Privacy-CGNp4n8M.js +1 -0
- package/src/assets/web-panel/assets/{ProjectInit-BQjMDkML.js → ProjectInit-CAVA5VsX.js} +2 -2
- package/src/assets/web-panel/assets/ProjectSettings-GCgkfM7A.js +2 -0
- package/src/assets/web-panel/assets/Projects-BYK17p52.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bl0dw6cI.js → Providers-DpUT5W-d.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BDrk7l3t.js → QuickAsk-ZJxTb7vi.js} +1 -1
- package/src/assets/web-panel/assets/Recommend-CIzFRDk1.js +1 -0
- package/src/assets/web-panel/assets/Reputation-lQ_WDNZn.js +1 -0
- package/src/assets/web-panel/assets/Row-1MEtrgtF.js +1 -0
- package/src/assets/web-panel/assets/{RssFeed-9-Fxh3_w.js → RssFeed-SSgcPPl4.js} +2 -2
- package/src/assets/web-panel/assets/Search-CcTMOGNY.js +1 -0
- package/src/assets/web-panel/assets/{Security-D6CySV7r.js → Security-BA1KMaDx.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Co8Mk8qE.js → Services-_aj7czZn.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BdON8M-1.js → Skeleton-BWgg_0Zr.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CEJE1Go1.js → Skills-C2ZIziYM.js} +1 -1
- package/src/assets/web-panel/assets/Sla-CYxp4axY.js +1 -0
- package/src/assets/web-panel/assets/SpeechSettings-C7Csp1jV.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-52NXsfyj.js → SyncSettings-svGeswiw.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-He7CweW6.js → Tasks-ClISj6oK.js} +1 -1
- package/src/assets/web-panel/assets/Templates-CmO-Fp7r.js +1 -0
- package/src/assets/web-panel/assets/Tenant-a3Ac3lxz.js +1 -0
- package/src/assets/web-panel/assets/Terminal-DyJIRh81.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BdfqBQmM.js → TimelineRenderer-acXS5N5h.js} +1 -1
- package/src/assets/web-panel/assets/Tokens-DJW0KqV4.js +1 -0
- package/src/assets/web-panel/assets/{Trigger-Dt_N_rvo.js → Trigger-0HeTi4r6.js} +1 -1
- package/src/assets/web-panel/assets/Trust-aFym34eo.js +1 -0
- package/src/assets/web-panel/assets/{UkeySign-CW8YdFxg.js → UkeySign-CYhszHJv.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CU6o7qvp.js → VideoEditing-BbJxPF9X.js} +1 -1
- package/src/assets/web-panel/assets/Wallet-DDthEwiu.js +4 -0
- package/src/assets/web-panel/assets/WebAuthn-DQ6McYPg.js +5 -0
- package/src/assets/web-panel/assets/{WorkflowEditor-BcCA2U8H.js → WorkflowEditor-CnF8zRsi.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Cxl5DGsc.js → chat-Dzkx2gTP.js} +1 -1
- package/src/assets/web-panel/assets/{colors-FS_7Xggs.js → colors-35T51Oo5.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BzXjd3wJ.js → compact-item-TtzNrlu1.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DhF9bi_u.js → createContext-Y1LkWrYb.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-D_lwLZ6O.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-Y1a0aV9N.js → hasIn-BOYw1BgS.js} +1 -1
- package/src/assets/web-panel/assets/{index-JUukFpxi.js → index-8jffdb6b.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBtULjKY.js → index-B7aKAZzS.js} +1 -1
- package/src/assets/web-panel/assets/index-BGPEaeB1.js +1 -0
- package/src/assets/web-panel/assets/index-BKRlWHUp.js +1 -0
- package/src/assets/web-panel/assets/{index-Dmjxpu0L.js → index-BSpFe6j5.js} +1 -1
- package/src/assets/web-panel/assets/{index-1iacQgo5.js → index-BWgNn9As.js} +28 -26
- package/src/assets/web-panel/assets/{index-Na733kBa.js → index-Bs69SI96.js} +2 -2
- package/src/assets/web-panel/assets/{index-B48PZ-xM.js → index-BtlJWaSP.js} +2 -2
- package/src/assets/web-panel/assets/index-Bw1iOGhM.js +1 -0
- package/src/assets/web-panel/assets/{index-DVTex5M8.js → index-BxWUEFKy.js} +1 -1
- package/src/assets/web-panel/assets/index-C7Wt5feN.js +1 -0
- package/src/assets/web-panel/assets/index-CEwuCM44.js +1 -0
- package/src/assets/web-panel/assets/index-CLqC2sRT.js +3 -0
- package/src/assets/web-panel/assets/{index-DxGVbuqr.js → index-CPGNc2tF.js} +2 -2
- package/src/assets/web-panel/assets/{index-DfaSKEVD.js → index-CVrUs6rf.js} +1 -1
- package/src/assets/web-panel/assets/index-CZ16GlOs.js +1 -0
- package/src/assets/web-panel/assets/{index-CELVZPNt.js → index-CdyFg4FI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CETvk0NN.js → index-CqhwG1ox.js} +4 -4
- package/src/assets/web-panel/assets/index-Cs82BHAj.js +1 -0
- package/src/assets/web-panel/assets/{index-D3IVAG9l.js → index-D2od7Bgx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Sl-sLj3T.js → index-D47robkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci7PSoQn.js → index-D4BgCBa3.js} +2 -2
- package/src/assets/web-panel/assets/index-D6tG4B25.js +55 -0
- package/src/assets/web-panel/assets/index-D7dCBw_M.js +1 -0
- package/src/assets/web-panel/assets/index-DAizH273.js +21 -0
- package/src/assets/web-panel/assets/{index-GCd5hs58.js → index-DFhlelzN.js} +2 -2
- package/src/assets/web-panel/assets/index-DhMSOd_2.js +1 -0
- package/src/assets/web-panel/assets/index-Dhfw-BXs.js +12 -0
- package/src/assets/web-panel/assets/index-DjeUZxE5.js +1 -0
- package/src/assets/web-panel/assets/{index-BnvaUocg.js → index-DmkG479D.js} +2 -2
- package/src/assets/web-panel/assets/index-Dof7lWA_.js +13 -0
- package/src/assets/web-panel/assets/{index-BNmWkPqm.js → index-Dx0hIfC-.js} +1 -1
- package/src/assets/web-panel/assets/index-DzzgU4IU.js +1 -0
- package/src/assets/web-panel/assets/index-G4ClSmJc.js +1 -0
- package/src/assets/web-panel/assets/{index-Cn9lbqaS.js → index-KgXrlfNF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUcVF8e6.js → index-NM9Oke2k.js} +2 -2
- package/src/assets/web-panel/assets/{index-DLyIxnjc.js → index-XIuZV9by.js} +3 -3
- package/src/assets/web-panel/assets/{index-BTltEtoG.js → index-hyqEKkBT.js} +1 -1
- package/src/assets/web-panel/assets/{index-uLoKviUT.js → index-zF77jnI2.js} +2 -2
- package/src/assets/web-panel/assets/{initDefaultProps-D6QmlnQ-.js → initDefaultProps-DD5y5msp.js} +1 -1
- package/src/assets/web-panel/assets/motion-BuoutMia.js +11 -0
- package/src/assets/web-panel/assets/{move-Bpph6B_N.js → move-DkpjqOXg.js} +1 -1
- package/src/assets/web-panel/assets/mtc-parser-BmGJD-v-.js +1 -0
- package/src/assets/web-panel/assets/{omit-Dx0YMbWn.js → omit-DftUE0Sz.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D-a8Fm6C.js → pickAttrs-DDRb9FIu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DXFSVgxv.js → placementArrow-DPgtxOOD.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CxhQ1EaK.js → responsiveObserve-BQFRzFeZ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-IlBI0XU8.js → slide-DQ4lmUSz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-COoXVzje.js → statusUtils-C1TR4ZiQ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCuq89M_.js → styleChecker-B-Suj978.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-tfNE1ALO.js → useFlexGapSupport-Q7bDZ3EI.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DD4bzBkU.js → useFs-DHjRLyQH.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DVkH1hbT.js → usePersonalDataHub-lQOvCvCr.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D1ZmXyxM.js → vnode-Ie0g4-p3.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DblBUHfi.js → zoom-CHP0YEoH.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +10 -0
- package/src/commands/ask.js +24 -1
- package/src/commands/ide.js +60 -5
- package/src/commands/incentive.js +22 -22
- package/src/commands/init.js +5 -4
- package/src/commands/instinct.js +18 -5
- package/src/commands/mcp.js +30 -3
- package/src/commands/memory.js +18 -5
- package/src/commands/mtc.js +5 -1
- package/src/gateways/http/envelope-http-server.js +17 -2
- package/src/gateways/terminal/PtyManager.js +15 -2
- package/src/gateways/ws/message-dispatcher.js +4 -1
- package/src/gateways/ws/ws-server.js +20 -0
- package/src/gateways/ws/ws-session-gateway.js +72 -4
- package/src/harness/background-task-manager.js +5 -1
- package/src/harness/jsonl-session-store.js +51 -0
- package/src/harness/mcp-client.js +119 -21
- package/src/harness/prompt-compressor.js +24 -5
- package/src/harness/worktree-isolator.js +25 -10
- package/src/lib/agent-core.js +4 -0
- package/src/lib/autonomous-agent.js +36 -6
- package/src/lib/chat-intent-service.js +18 -4
- package/src/lib/checkpoint-store.js +54 -4
- package/src/lib/claude-code-bridge.js +48 -3
- package/src/lib/cost-budget.js +13 -2
- package/src/lib/credential-guard.js +45 -0
- package/src/lib/git-integration.js +68 -3
- package/src/lib/goal-store.js +11 -0
- package/src/lib/hook-manager.js +4 -0
- package/src/lib/hook-runner.cjs +11 -1
- package/src/lib/interactive-planner.js +4 -3
- package/src/lib/jetbrains-bridge.js +147 -0
- package/src/lib/json-schema-output.js +47 -0
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/learning/reflection-engine.js +4 -3
- package/src/lib/learning/skill-improver.js +4 -3
- package/src/lib/learning/skill-synthesizer.js +4 -3
- package/src/lib/llm-pricing.js +11 -4
- package/src/lib/llm-providers.js +11 -1
- package/src/lib/mcp-oauth.js +220 -20
- package/src/lib/mcp-serve.js +85 -6
- package/src/lib/orchestrator.js +42 -4
- package/src/lib/process-manager.js +31 -3
- package/src/lib/repl-bang-memorize.js +9 -1
- package/src/lib/repl-completer.js +29 -0
- package/src/lib/runnable-provider.js +7 -1
- package/src/lib/session-insights.js +13 -6
- package/src/lib/session-usage.js +21 -3
- package/src/lib/slot-filler.js +4 -3
- package/src/lib/status-line.cjs +4 -1
- package/src/lib/sub-agent-context.js +15 -0
- package/src/lib/web-fetch.js +54 -3
- package/src/lib/workflow-engine.js +35 -11
- package/src/repl/agent-repl.js +29 -0
- package/src/runtime/agent-core.js +252 -28
- package/src/runtime/headless-runner.js +13 -0
- package/src/runtime/headless-stream.js +133 -17
- package/src/runtime/mcp-config.js +72 -3
- package/src/runtime/pipe-safety.js +51 -0
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/AIOps-C5CSd8pY.js +0 -1
- package/src/assets/web-panel/assets/Analytics-CzxpcV9E.js +0 -3
- package/src/assets/web-panel/assets/Audit-qjq04wh8.js +0 -1
- package/src/assets/web-panel/assets/Backup-C2Y_XNBA.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-TeKnMwPj.js +0 -1
- package/src/assets/web-panel/assets/Codegen-TGLFk6P1.js +0 -1
- package/src/assets/web-panel/assets/Community-tg6-Rik1.js +0 -1
- package/src/assets/web-panel/assets/Compliance-DFeWryxt.js +0 -1
- package/src/assets/web-panel/assets/Crosschain-p7_5Gq-7.js +0 -1
- package/src/assets/web-panel/assets/Dashboard-Bh_HkNSD.js +0 -3
- package/src/assets/web-panel/assets/Federation-BJApfPV4.js +0 -1
- package/src/assets/web-panel/assets/Governance-B4u_KsfE.js +0 -1
- package/src/assets/web-panel/assets/Inference-2kr_rKQd.js +0 -1
- package/src/assets/web-panel/assets/KnowledgeGraph-h-nL7J6x.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-W9Iz8b2m.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-4jAehfcz.js +0 -3
- package/src/assets/web-panel/assets/NLProgramming-D3y71FDX.js +0 -1
- package/src/assets/web-panel/assets/Organization-BBEmFNGM.js +0 -4
- package/src/assets/web-panel/assets/PdhVaultBrowser-DDU2Zc9Q.js +0 -7
- package/src/assets/web-panel/assets/Permissions-B1ebgevF.js +0 -4
- package/src/assets/web-panel/assets/Pipeline-DASuPDc3.js +0 -1
- package/src/assets/web-panel/assets/Privacy-DnixwmhJ.js +0 -1
- package/src/assets/web-panel/assets/ProjectSettings-dHyZ3SxW.js +0 -2
- package/src/assets/web-panel/assets/Projects-BGWEJIVT.js +0 -1
- package/src/assets/web-panel/assets/Recommend-BPKof_CA.js +0 -1
- package/src/assets/web-panel/assets/Reputation-6MNJLklK.js +0 -1
- package/src/assets/web-panel/assets/Row-DmGJwpfd.js +0 -1
- package/src/assets/web-panel/assets/Search-CCHjey_D.js +0 -1
- package/src/assets/web-panel/assets/Sla-BSxFvFX9.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C3rShYlV.js +0 -1
- package/src/assets/web-panel/assets/Templates-UtjBlJIO.js +0 -1
- package/src/assets/web-panel/assets/Tenant-Dd5XNbYF.js +0 -1
- package/src/assets/web-panel/assets/Terminal-pFyh42cu.js +0 -3
- package/src/assets/web-panel/assets/Tokens-CsgNsrdQ.js +0 -1
- package/src/assets/web-panel/assets/Trust-B1lJzsPn.js +0 -1
- package/src/assets/web-panel/assets/Wallet-DPxjEhbN.js +0 -4
- package/src/assets/web-panel/assets/WebAuthn-CuJ29YAF.js +0 -5
- package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
- package/src/assets/web-panel/assets/devWarning-B1j78JeH.js +0 -1
- package/src/assets/web-panel/assets/index--fcP34eT.js +0 -1
- package/src/assets/web-panel/assets/index-1khPb4ZS.js +0 -1
- package/src/assets/web-panel/assets/index-B_Z0PHC0.js +0 -12
- package/src/assets/web-panel/assets/index-BaN6wKWx.js +0 -1
- package/src/assets/web-panel/assets/index-BepInmSi.js +0 -1
- package/src/assets/web-panel/assets/index-BllceSdr.js +0 -1
- package/src/assets/web-panel/assets/index-Bt_0ygjA.js +0 -1
- package/src/assets/web-panel/assets/index-BzQkdgQW.js +0 -1
- package/src/assets/web-panel/assets/index-C2S8McM1.js +0 -55
- package/src/assets/web-panel/assets/index-CRSkKj9M.js +0 -1
- package/src/assets/web-panel/assets/index-Cgo1J_Kf.js +0 -1
- package/src/assets/web-panel/assets/index-CuTKOay7.js +0 -1
- package/src/assets/web-panel/assets/index-CxYHV8nb.js +0 -13
- package/src/assets/web-panel/assets/index-CzlCP4jP.js +0 -3
- package/src/assets/web-panel/assets/index-DZJMsVI1.js +0 -1
- package/src/assets/web-panel/assets/index-DgHWZVjv.js +0 -21
- package/src/assets/web-panel/assets/index-N7JKRLCC.js +0 -1
- package/src/assets/web-panel/assets/motion-BHrTGY1_.js +0 -11
- package/src/assets/web-panel/assets/mtc-parser-CEQffxds.js +0 -1
|
@@ -482,8 +482,8 @@ export function registerIncentiveCommand(program) {
|
|
|
482
482
|
.command("register-account-v2 <account-id>")
|
|
483
483
|
.description("V2: register a tracked account (tags ACTIVE)")
|
|
484
484
|
.option("-m, --metadata <meta>", "Metadata (JSON)")
|
|
485
|
-
.action((accountId, opts) => {
|
|
486
|
-
const ctx = bootstrap();
|
|
485
|
+
.action(async (accountId, opts) => {
|
|
486
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
487
487
|
try {
|
|
488
488
|
const db = _dbFromCtx(ctx);
|
|
489
489
|
const metadata = parseJsonOption(opts.metadata, "--metadata");
|
|
@@ -511,8 +511,8 @@ export function registerIncentiveCommand(program) {
|
|
|
511
511
|
.description("V2: transition account status (active|frozen|closed)")
|
|
512
512
|
.option("-r, --reason <reason>", "Reason")
|
|
513
513
|
.option("-m, --metadata <meta>", "Metadata (JSON)")
|
|
514
|
-
.action((accountId, status, opts) => {
|
|
515
|
-
const ctx = bootstrap();
|
|
514
|
+
.action(async (accountId, status, opts) => {
|
|
515
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
516
516
|
try {
|
|
517
517
|
const db = _dbFromCtx(ctx);
|
|
518
518
|
const metadata = parseJsonOption(opts.metadata, "--metadata");
|
|
@@ -530,8 +530,8 @@ export function registerIncentiveCommand(program) {
|
|
|
530
530
|
.command("freeze-account <account-id>")
|
|
531
531
|
.description("V2: shortcut → frozen")
|
|
532
532
|
.option("-r, --reason <reason>", "Reason")
|
|
533
|
-
.action((accountId, opts) => {
|
|
534
|
-
const ctx = bootstrap();
|
|
533
|
+
.action(async (accountId, opts) => {
|
|
534
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
535
535
|
try {
|
|
536
536
|
const db = _dbFromCtx(ctx);
|
|
537
537
|
const entry = freezeAccount(db, accountId, opts.reason);
|
|
@@ -545,8 +545,8 @@ export function registerIncentiveCommand(program) {
|
|
|
545
545
|
.command("unfreeze-account <account-id>")
|
|
546
546
|
.description("V2: shortcut → active (from frozen)")
|
|
547
547
|
.option("-r, --reason <reason>", "Reason")
|
|
548
|
-
.action((accountId, opts) => {
|
|
549
|
-
const ctx = bootstrap();
|
|
548
|
+
.action(async (accountId, opts) => {
|
|
549
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
550
550
|
try {
|
|
551
551
|
const db = _dbFromCtx(ctx);
|
|
552
552
|
const entry = unfreezeAccount(db, accountId, opts.reason);
|
|
@@ -560,8 +560,8 @@ export function registerIncentiveCommand(program) {
|
|
|
560
560
|
.command("close-account <account-id>")
|
|
561
561
|
.description("V2: shortcut → closed")
|
|
562
562
|
.option("-r, --reason <reason>", "Reason")
|
|
563
|
-
.action((accountId, opts) => {
|
|
564
|
-
const ctx = bootstrap();
|
|
563
|
+
.action(async (accountId, opts) => {
|
|
564
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
565
565
|
try {
|
|
566
566
|
const db = _dbFromCtx(ctx);
|
|
567
567
|
const entry = closeAccount(db, accountId, opts.reason);
|
|
@@ -578,8 +578,8 @@ export function registerIncentiveCommand(program) {
|
|
|
578
578
|
.requiredOption("-a, --amount <amount>", "Amount")
|
|
579
579
|
.option("-c, --contribution <id>", "Contribution ID")
|
|
580
580
|
.option("-m, --metadata <meta>", "Metadata (JSON)")
|
|
581
|
-
.action((claimId, opts) => {
|
|
582
|
-
const ctx = bootstrap();
|
|
581
|
+
.action(async (claimId, opts) => {
|
|
582
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
583
583
|
try {
|
|
584
584
|
const db = _dbFromCtx(ctx);
|
|
585
585
|
const metadata = parseJsonOption(opts.metadata, "--metadata");
|
|
@@ -613,8 +613,8 @@ export function registerIncentiveCommand(program) {
|
|
|
613
613
|
.description("V2: transition claim (pending|approved|paid|rejected)")
|
|
614
614
|
.option("-r, --reason <reason>", "Reason")
|
|
615
615
|
.option("-m, --metadata <meta>", "Metadata (JSON)")
|
|
616
|
-
.action((claimId, status, opts) => {
|
|
617
|
-
const ctx = bootstrap();
|
|
616
|
+
.action(async (claimId, status, opts) => {
|
|
617
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
618
618
|
try {
|
|
619
619
|
const db = _dbFromCtx(ctx);
|
|
620
620
|
const metadata = parseJsonOption(opts.metadata, "--metadata");
|
|
@@ -632,8 +632,8 @@ export function registerIncentiveCommand(program) {
|
|
|
632
632
|
.command("approve-claim <claim-id>")
|
|
633
633
|
.description("V2: shortcut → approved")
|
|
634
634
|
.option("-r, --reason <reason>", "Reason")
|
|
635
|
-
.action((claimId, opts) => {
|
|
636
|
-
const ctx = bootstrap();
|
|
635
|
+
.action(async (claimId, opts) => {
|
|
636
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
637
637
|
try {
|
|
638
638
|
const db = _dbFromCtx(ctx);
|
|
639
639
|
const entry = approveClaim(db, claimId, opts.reason);
|
|
@@ -647,8 +647,8 @@ export function registerIncentiveCommand(program) {
|
|
|
647
647
|
.command("reject-claim <claim-id>")
|
|
648
648
|
.description("V2: shortcut → rejected")
|
|
649
649
|
.option("-r, --reason <reason>", "Reason")
|
|
650
|
-
.action((claimId, opts) => {
|
|
651
|
-
const ctx = bootstrap();
|
|
650
|
+
.action(async (claimId, opts) => {
|
|
651
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
652
652
|
try {
|
|
653
653
|
const db = _dbFromCtx(ctx);
|
|
654
654
|
const entry = rejectClaim(db, claimId, opts.reason);
|
|
@@ -662,8 +662,8 @@ export function registerIncentiveCommand(program) {
|
|
|
662
662
|
.command("pay-claim <claim-id>")
|
|
663
663
|
.description("V2: shortcut → paid (stamps paidAt)")
|
|
664
664
|
.option("-r, --reason <reason>", "Reason")
|
|
665
|
-
.action((claimId, opts) => {
|
|
666
|
-
const ctx = bootstrap();
|
|
665
|
+
.action(async (claimId, opts) => {
|
|
666
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
667
667
|
try {
|
|
668
668
|
const db = _dbFromCtx(ctx);
|
|
669
669
|
const entry = payClaim(db, claimId, opts.reason);
|
|
@@ -676,8 +676,8 @@ export function registerIncentiveCommand(program) {
|
|
|
676
676
|
inc
|
|
677
677
|
.command("auto-expire-unclaimed-claims")
|
|
678
678
|
.description("V2: bulk-reject stale PENDING claims")
|
|
679
|
-
.action(() => {
|
|
680
|
-
const ctx = bootstrap();
|
|
679
|
+
.action(async () => {
|
|
680
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
681
681
|
try {
|
|
682
682
|
const db = _dbFromCtx(ctx);
|
|
683
683
|
const expired = autoExpireUnclaimedClaims(db);
|
package/src/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import chalk from "chalk";
|
|
9
|
+
import { firstBalancedJson } from "../lib/json-schema-output.js";
|
|
9
10
|
import fs from "fs";
|
|
10
11
|
import path from "path";
|
|
11
12
|
import { logger } from "../lib/logger.js";
|
|
@@ -1841,8 +1842,8 @@ with open(r"""\${tmpJson}""", "w", encoding="utf-8") as f:
|
|
|
1841
1842
|
// Parse LLM result
|
|
1842
1843
|
let updatedCells;
|
|
1843
1844
|
try {
|
|
1844
|
-
const
|
|
1845
|
-
updatedCells = JSON.parse(
|
|
1845
|
+
const jsonText = firstBalancedJson(llmResult, "[");
|
|
1846
|
+
updatedCells = JSON.parse(jsonText || llmResult);
|
|
1846
1847
|
} catch (_e) {
|
|
1847
1848
|
return { success: false, error: "LLM 返回格式不正确,无法解析 JSON", hint: "请重试或简化修改指令" };
|
|
1848
1849
|
}
|
|
@@ -1947,8 +1948,8 @@ with open(r"""\${tmpJson}""", "w", encoding="utf-8") as f:
|
|
|
1947
1948
|
|
|
1948
1949
|
let updatedRuns;
|
|
1949
1950
|
try {
|
|
1950
|
-
const
|
|
1951
|
-
updatedRuns = JSON.parse(
|
|
1951
|
+
const jsonText = firstBalancedJson(llmResult, "[");
|
|
1952
|
+
updatedRuns = JSON.parse(jsonText || llmResult);
|
|
1952
1953
|
} catch (_e) {
|
|
1953
1954
|
return { success: false, error: "LLM 返回格式不正确,无法解析 JSON", hint: "请重试或简化修改指令" };
|
|
1954
1955
|
}
|
package/src/commands/instinct.js
CHANGED
|
@@ -86,9 +86,14 @@ export function registerInstinctCommand(program) {
|
|
|
86
86
|
logger.log(chalk.bold(`Learned Instincts (${instincts.length}):\n`));
|
|
87
87
|
for (const inst of instincts) {
|
|
88
88
|
const pct = (inst.confidence * 100).toFixed(0);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
// Clamp to [0,10] so a stored confidence outside [0,1]
|
|
90
|
+
// (recordBatch upserts caller-supplied values unclamped) can't
|
|
91
|
+
// make repeat() go negative and crash the whole list.
|
|
92
|
+
const lvl = Math.max(
|
|
93
|
+
0,
|
|
94
|
+
Math.min(10, Math.round(inst.confidence * 10) || 0),
|
|
95
|
+
);
|
|
96
|
+
const bar = "█".repeat(lvl) + "░".repeat(10 - lvl);
|
|
92
97
|
logger.log(
|
|
93
98
|
` ${chalk.gray(inst.id.slice(0, 8))} ${chalk.cyan(inst.category.padEnd(18))} ${bar} ${pct}%`,
|
|
94
99
|
);
|
|
@@ -124,7 +129,13 @@ export function registerInstinctCommand(program) {
|
|
|
124
129
|
const fs = await import("fs");
|
|
125
130
|
fs.writeFileSync(options.output, JSON.stringify(rows), "utf-8");
|
|
126
131
|
if (options.json) {
|
|
127
|
-
console.log(
|
|
132
|
+
console.log(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
ok: true,
|
|
135
|
+
count: rows.length,
|
|
136
|
+
output: options.output,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
128
139
|
} else {
|
|
129
140
|
logger.log(`exported ${rows.length} instincts → ${options.output}`);
|
|
130
141
|
}
|
|
@@ -141,7 +152,9 @@ export function registerInstinctCommand(program) {
|
|
|
141
152
|
// instinct import — §8.3 restore: idempotent upsert by id from a JSON-array file
|
|
142
153
|
instinct
|
|
143
154
|
.command("import")
|
|
144
|
-
.description(
|
|
155
|
+
.description(
|
|
156
|
+
"Import instincts from a JSON array file (idempotent upsert by id; §8.3)",
|
|
157
|
+
)
|
|
145
158
|
.requiredOption("--input <file>", "JSON array of instincts to import")
|
|
146
159
|
.option("--json", "Output JSON result")
|
|
147
160
|
.action(async (options) => {
|
package/src/commands/mcp.js
CHANGED
|
@@ -42,6 +42,25 @@ export function loginWantsNoBrowser(options = {}) {
|
|
|
42
42
|
return options.open === false || options.browser === false;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Detect a headless environment where opening a browser is futile, so `mcp
|
|
47
|
+
* login` prints the authorize URL for manual opening instead of launching a
|
|
48
|
+
* popup that can never appear (Claude-Code 2.1.191 parity: "headless
|
|
49
|
+
* environments skip the browser popup and go straight to the paste-the-URL
|
|
50
|
+
* prompt"). The localhost callback still catches the code, so login completes.
|
|
51
|
+
*
|
|
52
|
+
* Heuristics: a CI marker forces headless on any OS; macOS/Windows otherwise
|
|
53
|
+
* always have a window server; on Linux/BSD a browser needs a display server
|
|
54
|
+
* (`DISPLAY`/`WAYLAND_DISPLAY`) — its absence (e.g. SSH without X11 forwarding)
|
|
55
|
+
* means headless. `CI=false`/`CI=0` are treated as not-CI.
|
|
56
|
+
*/
|
|
57
|
+
export function isHeadlessEnv(env = process.env, platform = process.platform) {
|
|
58
|
+
const ci = env.CI;
|
|
59
|
+
if (ci && ci !== "false" && ci !== "0") return true;
|
|
60
|
+
if (platform === "darwin" || platform === "win32") return false;
|
|
61
|
+
return !(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
// Singleton MCP client for session reuse
|
|
46
65
|
let mcpClient = null;
|
|
47
66
|
|
|
@@ -146,7 +165,7 @@ export function registerMcpCommand(program) {
|
|
|
146
165
|
mcp
|
|
147
166
|
.command("login <target>")
|
|
148
167
|
.description(
|
|
149
|
-
"Authorize a remote MCP server via OAuth (opens a browser); stores the token. <target> is a URL or a configured server name.",
|
|
168
|
+
"Authorize a remote MCP server via OAuth (opens a browser, or prints the URL in headless/SSH environments); stores the token. <target> is a URL or a configured server name.",
|
|
150
169
|
)
|
|
151
170
|
.option("--scope <scope>", "OAuth scope(s) to request")
|
|
152
171
|
.option(
|
|
@@ -168,8 +187,16 @@ export function registerMcpCommand(program) {
|
|
|
168
187
|
try {
|
|
169
188
|
const url = await resolveAuthTargetUrl(program, target);
|
|
170
189
|
const oauth = await import("../lib/mcp-oauth.js");
|
|
171
|
-
|
|
172
|
-
|
|
190
|
+
const explicitNoBrowser = loginWantsNoBrowser(options);
|
|
191
|
+
if (explicitNoBrowser || isHeadlessEnv()) {
|
|
192
|
+
oauth._deps.openBrowser = () => false; // --no-open / --no-browser / headless
|
|
193
|
+
if (!explicitNoBrowser) {
|
|
194
|
+
logger.log(
|
|
195
|
+
chalk.gray(
|
|
196
|
+
"Headless environment — printing the authorize URL instead of opening a browser.",
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
}
|
|
173
200
|
}
|
|
174
201
|
logger.log(chalk.gray(`Authorizing ${url} …`));
|
|
175
202
|
const rec = await oauth.authorizeInteractive(url, {
|
package/src/commands/memory.js
CHANGED
|
@@ -143,8 +143,11 @@ export function registerMemoryCommand(program) {
|
|
|
143
143
|
} else {
|
|
144
144
|
logger.log(chalk.bold(`Memory (${entries.length} entries):\n`));
|
|
145
145
|
for (const e of entries) {
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
// Clamp to [0,5] so an out-of-range stored importance (rows
|
|
147
|
+
// written outside the clamped `memory add` path) can't make
|
|
148
|
+
// repeat() go negative and crash the whole list.
|
|
149
|
+
const imp = Math.max(0, Math.min(5, Math.round(e.importance) || 0));
|
|
150
|
+
const stars = "★".repeat(imp) + "☆".repeat(5 - imp);
|
|
148
151
|
logger.log(
|
|
149
152
|
` ${chalk.gray(e.id.slice(0, 12))} ${chalk.yellow(stars)} ${chalk.cyan(e.category)}`,
|
|
150
153
|
);
|
|
@@ -180,9 +183,17 @@ export function registerMemoryCommand(program) {
|
|
|
180
183
|
const fs = await import("fs");
|
|
181
184
|
fs.writeFileSync(options.output, JSON.stringify(entries), "utf-8");
|
|
182
185
|
if (options.json) {
|
|
183
|
-
console.log(
|
|
186
|
+
console.log(
|
|
187
|
+
JSON.stringify({
|
|
188
|
+
ok: true,
|
|
189
|
+
count: entries.length,
|
|
190
|
+
output: options.output,
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
184
193
|
} else {
|
|
185
|
-
logger.log(
|
|
194
|
+
logger.log(
|
|
195
|
+
`exported ${entries.length} memory entries → ${options.output}`,
|
|
196
|
+
);
|
|
186
197
|
}
|
|
187
198
|
} else {
|
|
188
199
|
console.log(JSON.stringify(entries)); // data command: emit the array
|
|
@@ -197,7 +208,9 @@ export function registerMemoryCommand(program) {
|
|
|
197
208
|
// memory import — §8.3 restore: idempotent upsert by id from a JSON-array file
|
|
198
209
|
memory
|
|
199
210
|
.command("import")
|
|
200
|
-
.description(
|
|
211
|
+
.description(
|
|
212
|
+
"Import memory entries from a JSON array file (idempotent upsert by id; §8.3)",
|
|
213
|
+
)
|
|
201
214
|
.requiredOption("--input <file>", "JSON array of memory entries to import")
|
|
202
215
|
.option("--json", "Output JSON result")
|
|
203
216
|
.action(async (options) => {
|
package/src/commands/mtc.js
CHANGED
|
@@ -595,7 +595,11 @@ export function registerMtcCommand(program) {
|
|
|
595
595
|
return;
|
|
596
596
|
}
|
|
597
597
|
if (result.ok) {
|
|
598
|
-
|
|
598
|
+
// `verify` reads envelope+landmark from disk and creates no signer,
|
|
599
|
+
// so signerInfo is never defined here — referencing it threw a
|
|
600
|
+
// ReferenceError that the catch reported as "verify failed" on a
|
|
601
|
+
// SUCCESSFUL verify. Use the stopgap banner directly.
|
|
602
|
+
logger.log(STOPGAP_BANNER);
|
|
599
603
|
logger.success(`Envelope verified`);
|
|
600
604
|
logger.log(
|
|
601
605
|
` ${chalk.bold("Subject:")} ${result.leaf.subject || "(no subject)"}`,
|
|
@@ -150,8 +150,23 @@ export function createEnvelopeHttpServer(options = {}) {
|
|
|
150
150
|
}
|
|
151
151
|
if (url === "/v1/health") return handleHealth(req, res);
|
|
152
152
|
const match = SESSION_EVENTS_RE.exec(url);
|
|
153
|
-
if (match)
|
|
154
|
-
|
|
153
|
+
if (match) {
|
|
154
|
+
let sessionId;
|
|
155
|
+
try {
|
|
156
|
+
sessionId = decodeURIComponent(match[1]);
|
|
157
|
+
} catch {
|
|
158
|
+
// Malformed percent-encoding (e.g. `/v1/sessions/%/events`) would
|
|
159
|
+
// otherwise throw URIError synchronously inside this request
|
|
160
|
+
// listener — an uncaught exception that crashes the whole server,
|
|
161
|
+
// unauthenticated. Reject as a bad request instead.
|
|
162
|
+
res.writeHead(400, {
|
|
163
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
164
|
+
});
|
|
165
|
+
res.end(JSON.stringify({ error: "bad_request" }));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
return handleSessionEvents(req, res, sessionId);
|
|
169
|
+
}
|
|
155
170
|
res.writeHead(404, { "Content-Type": "application/json; charset=utf-8" });
|
|
156
171
|
res.end(JSON.stringify({ error: "not_found" }));
|
|
157
172
|
}
|
|
@@ -75,7 +75,14 @@ export class PtyManager extends EventEmitter {
|
|
|
75
75
|
|
|
76
76
|
create(req = {}) {
|
|
77
77
|
if (this._stopped) throw new Error("pty_manager_stopped");
|
|
78
|
-
|
|
78
|
+
// Count only LIVE sessions against the cap. A just-exited session lingers
|
|
79
|
+
// in _sessions for ~60s before the reaper removes it (see onExit), so
|
|
80
|
+
// counting `_sessions.size` would spuriously reject new sessions right
|
|
81
|
+
// after a client closes shells — painful over a reconnecting Android link
|
|
82
|
+
// where open/close churn is normal.
|
|
83
|
+
let aliveCount = 0;
|
|
84
|
+
for (const s of this._sessions.values()) if (s.alive) aliveCount++;
|
|
85
|
+
if (aliveCount >= this.config.maxConcurrentSessions) {
|
|
79
86
|
throw new Error("max_concurrent_sessions_exceeded");
|
|
80
87
|
}
|
|
81
88
|
const shell = req.shell || this.config.defaultShell;
|
|
@@ -97,12 +104,18 @@ export class PtyManager extends EventEmitter {
|
|
|
97
104
|
const rows = Number.isFinite(req.rows) ? req.rows : 24;
|
|
98
105
|
const cmd = resolveShellCmd(shell);
|
|
99
106
|
|
|
107
|
+
// req.env arrives from a remote WS frame; only merge a plain object, else a
|
|
108
|
+
// string/array would spread into garbage numeric env keys.
|
|
109
|
+
const extraEnv =
|
|
110
|
+
req.env && typeof req.env === "object" && !Array.isArray(req.env)
|
|
111
|
+
? req.env
|
|
112
|
+
: {};
|
|
100
113
|
const proc = pty.spawn(cmd, [], {
|
|
101
114
|
name: "xterm-256color",
|
|
102
115
|
cols,
|
|
103
116
|
rows,
|
|
104
117
|
cwd,
|
|
105
|
-
env: { ...process.env, ...
|
|
118
|
+
env: { ...process.env, ...extraEnv },
|
|
106
119
|
});
|
|
107
120
|
|
|
108
121
|
const sessionId = randomUUID();
|
|
@@ -23,7 +23,10 @@ export function createWsMessageDispatcher(server) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const client = server.clients.get(clientId);
|
|
26
|
-
|
|
26
|
+
// client may be undefined if it was removed mid-dispatch (heartbeat sweep
|
|
27
|
+
// / disconnect); treat a missing client as unauthenticated rather than
|
|
28
|
+
// throwing a TypeError (every sibling call site null-checks the client).
|
|
29
|
+
if (server.token && !client?.authenticated && type !== "auth") {
|
|
27
30
|
server._send(ws, {
|
|
28
31
|
id,
|
|
29
32
|
type: "error",
|
|
@@ -184,6 +184,14 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
184
184
|
this.token = options.token || null;
|
|
185
185
|
this.maxConnections = options.maxConnections || 10;
|
|
186
186
|
this.timeout = options.timeout || 30000;
|
|
187
|
+
// Cap inbound message size. The `ws` default is 100 MiB, and every frame is
|
|
188
|
+
// decoded + JSON.parsed in _handleConnection BEFORE the dispatcher's auth
|
|
189
|
+
// gate — so without a cap an UNAUTHENTICATED client could force a ~100 MB
|
|
190
|
+
// allocation per frame (×maxConnections) on a LAN-exposed (Android remote)
|
|
191
|
+
// deployment: a pre-auth memory-DoS. 32 MiB is generous for control frames
|
|
192
|
+
// and base64 image pastes while bounding abuse; `ws` closes oversize frames
|
|
193
|
+
// with code 1009 without buffering past the limit. Override per server.
|
|
194
|
+
this.maxPayloadBytes = options.maxPayloadBytes || 32 * 1024 * 1024;
|
|
187
195
|
|
|
188
196
|
/** Optional Phase-5 envelope bus for fan-out to hosted HTTP SSE. */
|
|
189
197
|
this.envelopeBus = options.envelopeBus || null;
|
|
@@ -214,6 +222,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
214
222
|
this.wss = new WebSocketServer({
|
|
215
223
|
port: this.port,
|
|
216
224
|
host: this.host,
|
|
225
|
+
maxPayload: this.maxPayloadBytes,
|
|
217
226
|
});
|
|
218
227
|
|
|
219
228
|
this.wss.on("listening", () => {
|
|
@@ -341,6 +350,17 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
341
350
|
this.emit("disconnection", { clientId });
|
|
342
351
|
});
|
|
343
352
|
|
|
353
|
+
// A misbehaving client (oversize frame → 1009, malformed protocol, abrupt
|
|
354
|
+
// reset) makes the per-connection socket emit 'error'. With no listener Node
|
|
355
|
+
// treats it as an uncaught exception and could crash the whole server — so
|
|
356
|
+
// handle it here; the 'close' that follows removes the client from the map.
|
|
357
|
+
ws.on("error", (err) => {
|
|
358
|
+
this.emit("client-error", {
|
|
359
|
+
clientId,
|
|
360
|
+
error: (err && err.message) || String(err),
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
344
364
|
ws.on("pong", () => {
|
|
345
365
|
const client = this.clients.get(clientId);
|
|
346
366
|
if (client) client.alive = true;
|
|
@@ -98,6 +98,31 @@ export class WSSessionManager {
|
|
|
98
98
|
|
|
99
99
|
/** @type {Map<string, Session>} */
|
|
100
100
|
this.sessions = new Map();
|
|
101
|
+
|
|
102
|
+
// Cap concurrent in-memory sessions. Each session holds a handler +
|
|
103
|
+
// interaction adapter + agent context, and nothing reaps them on client
|
|
104
|
+
// disconnect (they persist for resume; only closeSession removes one). An
|
|
105
|
+
// authenticated WS client could otherwise spam session-create to exhaust
|
|
106
|
+
// memory — this bounds it (mirrors the PTY manager's cap). Generous enough
|
|
107
|
+
// for legit multi-session use; resume does not create, so reconnecting to
|
|
108
|
+
// an existing session is never blocked by the cap. Override per manager.
|
|
109
|
+
this.maxSessions = Number.isFinite(options.maxSessions)
|
|
110
|
+
? options.maxSessions
|
|
111
|
+
: 100;
|
|
112
|
+
|
|
113
|
+
// Cap patch bookkeeping per session. `patchHistory` is append-only (every
|
|
114
|
+
// applyPatch/rejectPatch pushes, nothing trims) and `pendingPatches` grows
|
|
115
|
+
// with each proposePatch — both are serialized in full by
|
|
116
|
+
// _persistSessionState on every session op, so unbounded growth means
|
|
117
|
+
// O(n) memory AND an O(n²) rewrite cost over a long session (and a tool
|
|
118
|
+
// proposing many large before/after/diff blobs amplifies it). Keep a
|
|
119
|
+
// recent window; oldest entries fall off (FIFO).
|
|
120
|
+
this.maxPatchHistory = Number.isFinite(options.maxPatchHistory)
|
|
121
|
+
? options.maxPatchHistory
|
|
122
|
+
: 200;
|
|
123
|
+
this.maxPendingPatches = Number.isFinite(options.maxPendingPatches)
|
|
124
|
+
? options.maxPendingPatches
|
|
125
|
+
: 50;
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
_normalizeEnabledToolNames(enabledToolNames) {
|
|
@@ -264,6 +289,11 @@ export class WSSessionManager {
|
|
|
264
289
|
* @returns {{ sessionId: string }}
|
|
265
290
|
*/
|
|
266
291
|
createSession(options = {}) {
|
|
292
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
293
|
+
throw new Error(
|
|
294
|
+
`max_sessions_exceeded: ${this.maxSessions} concurrent sessions reached — close some before creating more`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
267
297
|
const sessionId = this._generateId();
|
|
268
298
|
const type = options.type || "agent";
|
|
269
299
|
const baseProjectRoot = options.projectRoot || this.defaultProjectRoot;
|
|
@@ -483,9 +513,7 @@ export class WSSessionManager {
|
|
|
483
513
|
permanentMemory,
|
|
484
514
|
reviewState: metadata.reviewState || null,
|
|
485
515
|
pendingPatches: this._hydratePendingPatches(metadata.pendingPatches),
|
|
486
|
-
patchHistory:
|
|
487
|
-
? metadata.patchHistory
|
|
488
|
-
: [],
|
|
516
|
+
patchHistory: this._boundPatchHistory(metadata.patchHistory),
|
|
489
517
|
taskGraph: this._hydrateTaskGraph(metadata.taskGraph),
|
|
490
518
|
interaction: null,
|
|
491
519
|
createdAt: dbSession.created_at,
|
|
@@ -873,6 +901,15 @@ export class WSSessionManager {
|
|
|
873
901
|
if (!(session.pendingPatches instanceof Map)) {
|
|
874
902
|
session.pendingPatches = new Map();
|
|
875
903
|
}
|
|
904
|
+
// Bound un-reviewed proposals: evict the oldest pending (Map preserves
|
|
905
|
+
// insertion order) once at cap. 50 unresolved patches is already
|
|
906
|
+
// pathological; dropping the stalest keeps memory + persisted state bounded
|
|
907
|
+
// without blocking a legitimate new proposal.
|
|
908
|
+
while (session.pendingPatches.size >= this.maxPendingPatches) {
|
|
909
|
+
const oldestKey = session.pendingPatches.keys().next().value;
|
|
910
|
+
if (oldestKey === undefined) break;
|
|
911
|
+
session.pendingPatches.delete(oldestKey);
|
|
912
|
+
}
|
|
876
913
|
session.pendingPatches.set(patchId, patch);
|
|
877
914
|
session.lastActivity = now;
|
|
878
915
|
this._persistSessionState(sessionId);
|
|
@@ -901,11 +938,35 @@ export class WSSessionManager {
|
|
|
901
938
|
session.patchHistory = [];
|
|
902
939
|
}
|
|
903
940
|
session.patchHistory.push(patch);
|
|
941
|
+
this._trimPatchHistory(session);
|
|
904
942
|
session.lastActivity = patch.resolvedAt;
|
|
905
943
|
this._persistSessionState(sessionId);
|
|
906
944
|
return patch;
|
|
907
945
|
}
|
|
908
946
|
|
|
947
|
+
/**
|
|
948
|
+
* Keep `patchHistory` bounded to the most recent maxPatchHistory entries.
|
|
949
|
+
* Oldest fall off the front (FIFO) so memory + persisted JSON stay bounded.
|
|
950
|
+
*/
|
|
951
|
+
_trimPatchHistory(session) {
|
|
952
|
+
if (!Array.isArray(session.patchHistory)) return;
|
|
953
|
+
const overflow = session.patchHistory.length - this.maxPatchHistory;
|
|
954
|
+
if (overflow > 0) {
|
|
955
|
+
session.patchHistory.splice(0, overflow);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Bound a (possibly persisted/tampered) patchHistory array on load, keeping
|
|
961
|
+
* the most recent entries. Returns a fresh array (never the input).
|
|
962
|
+
*/
|
|
963
|
+
_boundPatchHistory(list) {
|
|
964
|
+
if (!Array.isArray(list)) return [];
|
|
965
|
+
return list.length > this.maxPatchHistory
|
|
966
|
+
? list.slice(list.length - this.maxPatchHistory)
|
|
967
|
+
: list.slice();
|
|
968
|
+
}
|
|
969
|
+
|
|
909
970
|
/**
|
|
910
971
|
* Discard a pending patch. Same bookkeeping as applyPatch but records a
|
|
911
972
|
* "rejected" decision instead.
|
|
@@ -928,6 +989,7 @@ export class WSSessionManager {
|
|
|
928
989
|
session.patchHistory = [];
|
|
929
990
|
}
|
|
930
991
|
session.patchHistory.push(patch);
|
|
992
|
+
this._trimPatchHistory(session);
|
|
931
993
|
session.lastActivity = patch.resolvedAt;
|
|
932
994
|
this._persistSessionState(sessionId);
|
|
933
995
|
return patch;
|
|
@@ -1323,7 +1385,13 @@ export class WSSessionManager {
|
|
|
1323
1385
|
_hydratePendingPatches(list) {
|
|
1324
1386
|
const map = new Map();
|
|
1325
1387
|
if (Array.isArray(list)) {
|
|
1326
|
-
|
|
1388
|
+
// Trim from the front so the most recent proposals survive a reload even
|
|
1389
|
+
// if a prior (or tampered) persisted state exceeded the cap.
|
|
1390
|
+
const bounded =
|
|
1391
|
+
list.length > this.maxPendingPatches
|
|
1392
|
+
? list.slice(list.length - this.maxPendingPatches)
|
|
1393
|
+
: list;
|
|
1394
|
+
for (const patch of bounded) {
|
|
1327
1395
|
if (patch && patch.patchId) {
|
|
1328
1396
|
map.set(patch.patchId, patch);
|
|
1329
1397
|
}
|
|
@@ -194,7 +194,11 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
194
194
|
return history;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
// offset + null === offset, so an offset-only page (no limit) used to
|
|
198
|
+
// slice(offset, offset) === [] and lose every item past the offset. Use
|
|
199
|
+
// history.length as the end when limit is null.
|
|
200
|
+
const end = limit === null ? history.length : offset + limit;
|
|
201
|
+
const items = history.slice(offset, end);
|
|
198
202
|
return {
|
|
199
203
|
items,
|
|
200
204
|
total: history.length,
|
|
@@ -21,7 +21,32 @@ function getSessionsDir() {
|
|
|
21
21
|
return dir;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A session id must be a single safe path segment. Ids are generated as
|
|
26
|
+
* `session-<ts>-<hex>`, but also arrive from CLI args (`cc agent --resume <id>`,
|
|
27
|
+
* `cc insights <id>`, `cc session show <id>`), so an id like `../../etc/x` would
|
|
28
|
+
* otherwise let sessionPath() read / append / delete a .jsonl OUTSIDE the
|
|
29
|
+
* sessions dir. Reject any separator or `..` (mirrors goal-store's
|
|
30
|
+
* isUnsafeGoalId / FileUploadService.isUnsafeSegment).
|
|
31
|
+
*/
|
|
32
|
+
export function isUnsafeSessionId(id) {
|
|
33
|
+
return (
|
|
34
|
+
id == null ||
|
|
35
|
+
id === "" ||
|
|
36
|
+
typeof id !== "string" ||
|
|
37
|
+
id.includes("/") ||
|
|
38
|
+
id.includes("\\") ||
|
|
39
|
+
id.includes("..")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
export function sessionPath(sessionId) {
|
|
44
|
+
// Fail closed for path building: every write/delete goes through here, so a
|
|
45
|
+
// traversal id can never escape the sessions dir. Reads guard separately and
|
|
46
|
+
// degrade to not-found instead of throwing.
|
|
47
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
48
|
+
throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
|
|
49
|
+
}
|
|
25
50
|
return join(getSessionsDir(), `${sessionId}.jsonl`);
|
|
26
51
|
}
|
|
27
52
|
|
|
@@ -69,6 +94,7 @@ export function appendCompactEvent(sessionId, stats) {
|
|
|
69
94
|
}
|
|
70
95
|
|
|
71
96
|
export function readEvents(sessionId) {
|
|
97
|
+
if (isUnsafeSessionId(sessionId)) return []; // traversal id → treat as empty
|
|
72
98
|
const filePath = sessionPath(sessionId);
|
|
73
99
|
if (!existsSync(filePath)) return [];
|
|
74
100
|
|
|
@@ -184,6 +210,7 @@ export function forkSession(sourceId) {
|
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
export function sessionExists(sessionId) {
|
|
213
|
+
if (isUnsafeSessionId(sessionId)) return false; // never resolve a traversal id
|
|
187
214
|
return existsSync(sessionPath(sessionId));
|
|
188
215
|
}
|
|
189
216
|
|
|
@@ -266,6 +293,15 @@ export function migrateLegacySessionFile(filePath, options = {}) {
|
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
export function validateJsonlSession(sessionId) {
|
|
296
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
297
|
+
return {
|
|
298
|
+
sessionId,
|
|
299
|
+
valid: false,
|
|
300
|
+
reason: "invalid session id",
|
|
301
|
+
malformedLines: 0,
|
|
302
|
+
eventCount: 0,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
269
305
|
const filePath = sessionPath(sessionId);
|
|
270
306
|
if (!existsSync(filePath)) {
|
|
271
307
|
return {
|
|
@@ -335,6 +371,21 @@ function performLegacySessionMigration(sourcePath, options) {
|
|
|
335
371
|
const legacy = normalizeLegacySession(parsed, basename(sourcePath, ".json"));
|
|
336
372
|
const sessionId = legacy.id;
|
|
337
373
|
|
|
374
|
+
// A legacy file carries its OWN `id` (payload.id), so a crafted file could
|
|
375
|
+
// name a traversal target like "../../evil". sessionPath() throws on write
|
|
376
|
+
// (the backstop), but fail-fast HERE with a clear reason so the migration
|
|
377
|
+
// doesn't burn retry attempts on a deterministic error.
|
|
378
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
379
|
+
return {
|
|
380
|
+
file: sourcePath,
|
|
381
|
+
sessionId,
|
|
382
|
+
migrated: false,
|
|
383
|
+
failed: true,
|
|
384
|
+
dryRun: Boolean(options.dryRun),
|
|
385
|
+
reason: "unsafe session id in legacy file",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
338
389
|
if (!options.force && sessionExists(sessionId)) {
|
|
339
390
|
return {
|
|
340
391
|
file: sourcePath,
|