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
|
@@ -29,6 +29,13 @@ export const AGENT_STATUS = {
|
|
|
29
29
|
TIMEOUT: "timeout",
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
// Bound how much of a child's stdout/stderr we retain in memory. A verbose or
|
|
33
|
+
// runaway `claude -p` can stream unbounded stream-json; without a cap,
|
|
34
|
+
// outputChunks.join("") OOMs the orchestrator. We keep the TAIL (oldest chunks
|
|
35
|
+
// dropped) because stream-json's terminal `result` / last-assistant line — the
|
|
36
|
+
// only thing _parseStreamJson needs — is at the end.
|
|
37
|
+
export const MAX_AGENT_OUTPUT_BYTES = 32 * 1024 * 1024;
|
|
38
|
+
|
|
32
39
|
// ─── Detection ───────────────────────────────────────────────────
|
|
33
40
|
|
|
34
41
|
/**
|
|
@@ -98,6 +105,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
98
105
|
context = "",
|
|
99
106
|
allowedTools = null,
|
|
100
107
|
killGraceMs = 3000,
|
|
108
|
+
maxOutputBytes = MAX_AGENT_OUTPUT_BYTES,
|
|
101
109
|
} = options;
|
|
102
110
|
|
|
103
111
|
const fullPrompt = context
|
|
@@ -126,6 +134,19 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
126
134
|
const outDecoder = new TextDecoder("utf-8");
|
|
127
135
|
const errDecoder = new TextDecoder("utf-8");
|
|
128
136
|
let timedOut = false;
|
|
137
|
+
// Tail-bounded byte counters for the two buffers (see MAX_AGENT_OUTPUT_BYTES).
|
|
138
|
+
let outBytes = 0;
|
|
139
|
+
let errBytes = 0;
|
|
140
|
+
let outputTruncated = false;
|
|
141
|
+
// Drop oldest chunks until the buffer is back under the cap, keeping ≥1
|
|
142
|
+
// chunk (the most recent, where the result line lives).
|
|
143
|
+
const trim = (chunks, bytesRef) => {
|
|
144
|
+
let bytes = bytesRef;
|
|
145
|
+
while (bytes > maxOutputBytes && chunks.length > 1) {
|
|
146
|
+
bytes -= Buffer.byteLength(chunks.shift());
|
|
147
|
+
}
|
|
148
|
+
return bytes;
|
|
149
|
+
};
|
|
129
150
|
|
|
130
151
|
const proc = _deps.spawn(this.cliCommand, args, {
|
|
131
152
|
cwd,
|
|
@@ -155,15 +176,26 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
155
176
|
? data
|
|
156
177
|
: outDecoder.decode(data, { stream: true });
|
|
157
178
|
outputChunks.push(chunk);
|
|
179
|
+
outBytes += Buffer.byteLength(chunk);
|
|
180
|
+
if (outBytes > maxOutputBytes) {
|
|
181
|
+
outputTruncated = true;
|
|
182
|
+
outBytes = trim(outputChunks, outBytes);
|
|
183
|
+
}
|
|
184
|
+
// The live `output` event still streams every chunk verbatim — the cap
|
|
185
|
+
// only bounds what we RETAIN for the final rawOutput.
|
|
158
186
|
this.emit("output", { agentId: this.id, chunk });
|
|
159
187
|
});
|
|
160
188
|
|
|
161
189
|
proc.stderr.on("data", (data) => {
|
|
162
|
-
|
|
190
|
+
const chunk =
|
|
163
191
|
typeof data === "string"
|
|
164
192
|
? data
|
|
165
|
-
: errDecoder.decode(data, { stream: true })
|
|
166
|
-
);
|
|
193
|
+
: errDecoder.decode(data, { stream: true });
|
|
194
|
+
errorChunks.push(chunk);
|
|
195
|
+
errBytes += Buffer.byteLength(chunk);
|
|
196
|
+
if (errBytes > maxOutputBytes) {
|
|
197
|
+
errBytes = trim(errorChunks, errBytes);
|
|
198
|
+
}
|
|
167
199
|
});
|
|
168
200
|
|
|
169
201
|
proc.on("close", (code) => {
|
|
@@ -188,6 +220,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
188
220
|
success: code === 0 && !timedOut,
|
|
189
221
|
output: parsedOutput || rawOutput.slice(-4000), // last 4K chars fallback
|
|
190
222
|
rawOutput,
|
|
223
|
+
outputTruncated, // true when the child exceeded maxOutputBytes
|
|
191
224
|
exitCode: code,
|
|
192
225
|
duration,
|
|
193
226
|
timedOut,
|
|
@@ -257,6 +290,15 @@ export class ClaudeCodePool extends EventEmitter {
|
|
|
257
290
|
this.model = options.model || null;
|
|
258
291
|
this.agentTimeout = options.agentTimeout || 300_000;
|
|
259
292
|
|
|
293
|
+
// Bound retained completion history. dispatch() returns results directly,
|
|
294
|
+
// so _completed is only a recent-history buffer — but it was an unbounded
|
|
295
|
+
// array, so a long-lived pool dispatching many tasks leaked memory forever.
|
|
296
|
+
// Keep a recent window (FIFO). Default 1000; 0/invalid → 1000.
|
|
297
|
+
this.maxCompleted =
|
|
298
|
+
Number.isFinite(options.maxCompleted) && options.maxCompleted > 0
|
|
299
|
+
? options.maxCompleted
|
|
300
|
+
: 1000;
|
|
301
|
+
|
|
260
302
|
/** @type {Map<string, ClaudeCodeAgent>} */
|
|
261
303
|
this._agents = new Map();
|
|
262
304
|
this._completed = [];
|
|
@@ -315,6 +357,9 @@ export class ClaudeCodePool extends EventEmitter {
|
|
|
315
357
|
|
|
316
358
|
this._agents.delete(agent.id);
|
|
317
359
|
this._completed.push({ ...result, taskId: task.id });
|
|
360
|
+
if (this._completed.length > this.maxCompleted) {
|
|
361
|
+
this._completed.splice(0, this._completed.length - this.maxCompleted);
|
|
362
|
+
}
|
|
318
363
|
|
|
319
364
|
this.emit("agent:complete", { taskId: task.id, ...result });
|
|
320
365
|
return { taskId: task.id, ...result };
|
package/src/lib/cost-budget.js
CHANGED
|
@@ -86,8 +86,19 @@ export class CostBudget {
|
|
|
86
86
|
if (est.free) {
|
|
87
87
|
this.sawFree = true;
|
|
88
88
|
} else if (est.matched) {
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Defense-in-depth for a SAFETY control: a non-finite/negative cost (e.g.
|
|
90
|
+
// a malformed price table entry, or a non-numeric token count in a
|
|
91
|
+
// provider's usage event) must NOT poison spentUsd into NaN — `NaN >=
|
|
92
|
+
// limit` is always false, which would silently DISABLE the hard cap and
|
|
93
|
+
// allow unbounded spend on an unattended run. Only fold a clean cost;
|
|
94
|
+
// otherwise treat the record as unpriced.
|
|
95
|
+
const cost = Number(est.totalCost);
|
|
96
|
+
if (Number.isFinite(cost) && cost >= 0) {
|
|
97
|
+
this.spentUsd = round(this.spentUsd + cost);
|
|
98
|
+
this.priced = true;
|
|
99
|
+
} else {
|
|
100
|
+
this.sawUnpriced = true;
|
|
101
|
+
}
|
|
91
102
|
} else if (tokens > 0) {
|
|
92
103
|
this.sawUnpriced = true;
|
|
93
104
|
}
|
|
@@ -25,8 +25,16 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { createRequire } from "node:module";
|
|
28
|
+
import fsDefault from "node:fs";
|
|
29
|
+
import pathDefault from "node:path";
|
|
28
30
|
|
|
29
31
|
const require_ = createRequire(import.meta.url);
|
|
32
|
+
|
|
33
|
+
// Injectable for tests (realpath of a symlink without touching the real fs).
|
|
34
|
+
export const _deps = {
|
|
35
|
+
realpathSync: fsDefault.realpathSync,
|
|
36
|
+
resolve: pathDefault.resolve,
|
|
37
|
+
};
|
|
30
38
|
// Reuse the shell policy's compound-command splitter (separators + $()/backtick
|
|
31
39
|
// extraction). It is regex-based on separators and leaves backslashes intact,
|
|
32
40
|
// so Windows paths survive (unlike the escaping tokenizer, which strips `\`).
|
|
@@ -51,6 +59,12 @@ const CREDENTIAL_BASENAMES = new Set([
|
|
|
51
59
|
"id_dsa",
|
|
52
60
|
"id_ecdsa",
|
|
53
61
|
"id_ed25519", // private SSH keys (the `.pub` siblings have a different basename)
|
|
62
|
+
"id_ecdsa_sk",
|
|
63
|
+
"id_ed25519_sk", // FIDO/security-key SSH keys
|
|
64
|
+
".htpasswd", // web-server basic-auth hashes
|
|
65
|
+
"kubeconfig", // bare KUBECONFIG file (the ~/.kube/config form is matched below)
|
|
66
|
+
".terraformrc", // Terraform registry/API tokens
|
|
67
|
+
"terraform.rc",
|
|
54
68
|
]);
|
|
55
69
|
|
|
56
70
|
// Extensions whose BYTES are the secret (private keys / cert bundles / keyrings).
|
|
@@ -67,6 +81,7 @@ const CREDENTIAL_PATH_RE = [
|
|
|
67
81
|
/[\\/]\.docker[\\/]config\.json$/i,
|
|
68
82
|
/[\\/]\.config[\\/]gh[\\/]hosts\.yml$/i, // GitHub CLI OAuth token
|
|
69
83
|
/[\\/]\.gnupg[\\/]/i, // GPG keyring directory
|
|
84
|
+
/service[-_]account[^\\/]*\.json$/i, // GCP service-account key JSON
|
|
70
85
|
];
|
|
71
86
|
|
|
72
87
|
// secret(s).{json,yml,yaml,env,txt}
|
|
@@ -101,6 +116,36 @@ export function credentialFileReason(targetPath) {
|
|
|
101
116
|
return null;
|
|
102
117
|
}
|
|
103
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Like credentialFileReason, but also follows a symlink to its real target and
|
|
121
|
+
* re-checks. The name-only guard is bypassable: `read_file({path:"notes.txt"})`
|
|
122
|
+
* where notes.txt → ~/.ssh/id_rsa reads the key while the guard sees only the
|
|
123
|
+
* innocent name. fs.readFileSync follows symlinks, so the guard must too —
|
|
124
|
+
* resolve the real path and re-check its basename/patterns. Falls back to the
|
|
125
|
+
* literal check when the path can't be resolved (non-existent → the read fails
|
|
126
|
+
* anyway, nothing to gate). `cwd` anchors a relative target before realpath.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} targetPath
|
|
129
|
+
* @param {object} [opts] { cwd, deps }
|
|
130
|
+
* @returns {string|null}
|
|
131
|
+
*/
|
|
132
|
+
export function credentialFileReasonResolved(targetPath, opts = {}) {
|
|
133
|
+
const direct = credentialFileReason(targetPath);
|
|
134
|
+
if (direct) return direct;
|
|
135
|
+
const deps = opts.deps || _deps;
|
|
136
|
+
const cwd = opts.cwd || process.cwd();
|
|
137
|
+
try {
|
|
138
|
+
const real = deps.realpathSync(
|
|
139
|
+
deps.resolve(cwd, String(targetPath || ".")),
|
|
140
|
+
);
|
|
141
|
+
const viaLink = credentialFileReason(real);
|
|
142
|
+
if (viaLink) return `${viaLink} (via symlink)`;
|
|
143
|
+
} catch {
|
|
144
|
+
// Unresolvable / non-existent — the read itself would fail; nothing to gate.
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
104
149
|
// ── secret ENV-VAR + command detection ──────────────────────────────────────
|
|
105
150
|
|
|
106
151
|
// Content-reader commands: when one of these is aimed at a credential file, the
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Git integration — wraps system git commands for knowledge base versioning.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { execSync } from "child_process";
|
|
5
|
+
import { execSync, spawnSync } from "child_process";
|
|
6
6
|
import { existsSync, writeFileSync, chmodSync } from "fs";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
|
|
@@ -41,6 +41,62 @@ export function assertSafeGitRef(ref, label = "git ref") {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Validate a repo-relative file path before it is interpolated into a shell git
|
|
46
|
+
* command (e.g. `diff … -- "${filePath}"`). Double-quoting does NOT neutralize
|
|
47
|
+
* `$()` / backticks / a closing `"`, so an unsanitized path from a WS frame is a
|
|
48
|
+
* shell-injection vector. Allow only ordinary path characters (no shell
|
|
49
|
+
* metacharacters), reject absolute paths, a leading `-` (flag injection), and
|
|
50
|
+
* `..` (traversal). Exported for reuse + tests.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} p
|
|
53
|
+
* @param {string} [label="file path"]
|
|
54
|
+
*/
|
|
55
|
+
export function assertSafeGitPath(p, label = "file path") {
|
|
56
|
+
if (typeof p !== "string" || p.length === 0) {
|
|
57
|
+
throw new Error(`Invalid ${label}: expected a non-empty string`);
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
!/^[A-Za-z0-9._/+@=, -]+$/.test(p) || // safe path chars only — no shell metachars
|
|
61
|
+
p.startsWith("-") || // no flag injection (e.g. `-- --output=…`)
|
|
62
|
+
p.startsWith("/") || // no absolute path (posix)
|
|
63
|
+
/^[A-Za-z]:/.test(p) || // no absolute path (windows drive)
|
|
64
|
+
p.includes("..") // no traversal / range tricks
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(p)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run git with an ARGV array (no shell) — for commands that carry free-text the
|
|
72
|
+
* caller can't allowlist (a commit / merge message). spawnSync passes each arg
|
|
73
|
+
* verbatim, so `$()`, backticks, quotes, and `;` in the message are inert
|
|
74
|
+
* instead of being interpreted by a shell as they would be through `gitExec`.
|
|
75
|
+
* Throws git's stderr on failure, mirroring `gitExec`'s contract.
|
|
76
|
+
*
|
|
77
|
+
* @param {string[]} args
|
|
78
|
+
* @param {string} cwd
|
|
79
|
+
* @param {object} [opts] { input }
|
|
80
|
+
* @returns {string} trimmed stdout
|
|
81
|
+
*/
|
|
82
|
+
export function gitExecArgs(args, cwd, opts = {}) {
|
|
83
|
+
const res = spawnSync("git", args, {
|
|
84
|
+
cwd,
|
|
85
|
+
encoding: "utf-8",
|
|
86
|
+
windowsHide: true,
|
|
87
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
88
|
+
input: opts.input,
|
|
89
|
+
});
|
|
90
|
+
if (res.error) throw res.error;
|
|
91
|
+
if (res.status !== 0) {
|
|
92
|
+
const stderr = (res.stderr || res.stdout || "").toString().trim();
|
|
93
|
+
throw new Error(
|
|
94
|
+
stderr || `git ${args.join(" ")} failed (exit ${res.status})`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return (res.stdout || "").toString().trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
44
100
|
/**
|
|
45
101
|
* Run a git command and return stdout.
|
|
46
102
|
*/
|
|
@@ -132,7 +188,11 @@ export function gitAutoCommit(dir, message) {
|
|
|
132
188
|
message ||
|
|
133
189
|
`auto: ${status.files.length} file(s) changed — ${new Date().toISOString().slice(0, 16)}`;
|
|
134
190
|
|
|
135
|
-
|
|
191
|
+
// Free-text message → argv (no shell) so `$()` / backticks / `;` in a
|
|
192
|
+
// caller-supplied message (e.g. `cc git auto-commit --message …`) can't
|
|
193
|
+
// inject a command. The prior `-m "${msg.replace(/"/g,'\\"')}"` only escaped
|
|
194
|
+
// double quotes — same gap fixed in worktree-isolator / mergeWorktree.
|
|
195
|
+
gitExecArgs(["commit", "-m", commitMsg], dir);
|
|
136
196
|
|
|
137
197
|
const hash = gitExec("rev-parse --short HEAD", dir);
|
|
138
198
|
|
|
@@ -153,12 +213,17 @@ export function gitLog(dir, limit = 10) {
|
|
|
153
213
|
}
|
|
154
214
|
|
|
155
215
|
try {
|
|
216
|
+
// Coerce `limit` to a bounded positive integer before it is interpolated
|
|
217
|
+
// into the shell string — a non-numeric value (`5; rm -rf ~`) would
|
|
218
|
+
// otherwise inject. The only in-tree caller already passes an int; this is
|
|
219
|
+
// defense-in-depth at the exported boundary.
|
|
220
|
+
const n = Math.min(10000, Math.max(1, Number.parseInt(limit, 10) || 10));
|
|
156
221
|
// Subject (%s) is free text that often contains "|"; keep it LAST so a
|
|
157
222
|
// pipe in the subject can't shift the date/author fields, then re-join the
|
|
158
223
|
// remainder. (Field order kept as %X|%X — Windows cmd.exe expands adjacent
|
|
159
224
|
// %VAR% pairs, so a control-char separator like %x1f is not portable here.)
|
|
160
225
|
const output = gitExec(
|
|
161
|
-
`log --oneline --no-decorate -${
|
|
226
|
+
`log --oneline --no-decorate -${n} --format="%H|%h|%ai|%an|%s"`,
|
|
162
227
|
dir,
|
|
163
228
|
);
|
|
164
229
|
return output
|
package/src/lib/goal-store.js
CHANGED
|
@@ -124,6 +124,13 @@ export function createGoal(input = {}, opts = {}) {
|
|
|
124
124
|
throw new Error("createGoal requires an objective");
|
|
125
125
|
}
|
|
126
126
|
const id = input.id || newId("goal");
|
|
127
|
+
// Generated ids are always safe, but `input.id` is caller-supplied — guard it
|
|
128
|
+
// like every other op (getGoal/saveGoal/deleteGoal) so a crafted id such as
|
|
129
|
+
// `../../etc/x` can't make atomicWriteFileSync write a .json OUTSIDE the
|
|
130
|
+
// goals dir. createGoal was the one mutation missing this check.
|
|
131
|
+
if (isUnsafeGoalId(id)) {
|
|
132
|
+
throw new Error(`非法 goal id: ${String(id).slice(0, 60)}`);
|
|
133
|
+
}
|
|
127
134
|
const keyResults = (input.keyResults || []).map((kr) => normalizeKr(kr));
|
|
128
135
|
const goal = {
|
|
129
136
|
id,
|
|
@@ -207,6 +214,10 @@ function mutate(id, fn, opts = {}) {
|
|
|
207
214
|
// sharing the goals dir) so concurrent goal edits don't lose an update.
|
|
208
215
|
// Best-effort lock (with-file-lock): on contention timeout it proceeds anyway,
|
|
209
216
|
// so the CLI never hangs.
|
|
217
|
+
// Guard before deriving the lock path: an unsafe id would otherwise place the
|
|
218
|
+
// lock file at a traversal path (`<root>/../../x.json.lock`) before getGoal
|
|
219
|
+
// even runs. getGoal/saveGoal also guard, but keep the lock inside the dir.
|
|
220
|
+
if (isUnsafeGoalId(id)) throw new Error(`no such goal: ${id}`);
|
|
210
221
|
const root = opts.root || defaultRoot();
|
|
211
222
|
return withFileLock(goalFile(root, id), () => {
|
|
212
223
|
const goal = getGoal(id, opts);
|
package/src/lib/hook-manager.js
CHANGED
|
@@ -285,6 +285,10 @@ export async function executeHook(hook, context = {}) {
|
|
|
285
285
|
const output = execSync(cmd, {
|
|
286
286
|
encoding: "utf-8",
|
|
287
287
|
timeout: hook.timeout || 5000,
|
|
288
|
+
// execSync defaults maxBuffer to 1 MB; a hook that prints more (a
|
|
289
|
+
// linter/build dumping output) would throw ENOBUFS and be reported as a
|
|
290
|
+
// FAILURE even though it succeeded. Give hook output generous headroom.
|
|
291
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
288
292
|
env,
|
|
289
293
|
});
|
|
290
294
|
const executionTime = Date.now() - start;
|
package/src/lib/hook-runner.cjs
CHANGED
|
@@ -107,7 +107,17 @@ function tryParseDecision(stdout) {
|
|
|
107
107
|
* nonBlockingError?:boolean, error?:string }}
|
|
108
108
|
*/
|
|
109
109
|
function runCommandHook(command, input = {}, opts = {}) {
|
|
110
|
-
const {
|
|
110
|
+
const { cwd, event } = opts;
|
|
111
|
+
// spawnSync is SYNCHRONOUS — it blocks the whole agent until the hook returns
|
|
112
|
+
// or its timeout fires. Node treats a 0/undefined timeout as UNLIMITED, so a
|
|
113
|
+
// missing, zero, NaN, or negative value (e.g. a malformed settings.json
|
|
114
|
+
// `timeout`) would let a hung hook freeze the agent forever. Guarantee a
|
|
115
|
+
// positive, bounded timeout regardless of the caller's value (already in ms).
|
|
116
|
+
const _rawTimeout = Number(opts.timeout);
|
|
117
|
+
const timeout =
|
|
118
|
+
Number.isFinite(_rawTimeout) && _rawTimeout > 0
|
|
119
|
+
? Math.min(_rawTimeout, 600000)
|
|
120
|
+
: 60000;
|
|
111
121
|
if (!command) {
|
|
112
122
|
return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
|
|
113
123
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { EventEmitter } from "events";
|
|
11
11
|
import { createHash } from "crypto";
|
|
12
|
+
import { firstBalancedJson } from "./json-schema-output.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Plan session statuses
|
|
@@ -345,12 +346,12 @@ Keep plans concise (3-8 steps). Use appropriate tools for each step.`;
|
|
|
345
346
|
]);
|
|
346
347
|
|
|
347
348
|
const content = response?.message?.content || response?.content || "";
|
|
348
|
-
const
|
|
349
|
-
if (!
|
|
349
|
+
const jsonText = firstBalancedJson(content, "{");
|
|
350
|
+
if (!jsonText) {
|
|
350
351
|
throw new Error("Failed to parse plan from LLM response");
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
return JSON.parse(
|
|
354
|
+
return JSON.parse(jsonText);
|
|
354
355
|
}
|
|
355
356
|
|
|
356
357
|
/**
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JetBrains / IntelliJ IDEA built-in MCP discovery (cc-side).
|
|
3
|
+
*
|
|
4
|
+
* IntelliJ IDEA 2025.2+ ships a built-in MCP server (Settings | Tools | MCP
|
|
5
|
+
* Server) exposing the IDE's own indexed operations (find usages, file-by-path,
|
|
6
|
+
* search, run configs, VCS, …). Letting the agent call those instead of reading
|
|
7
|
+
* and grepping files itself is cheaper (fewer tokens) and faster (rides the IDE
|
|
8
|
+
* index) — that is the whole point of this bridge.
|
|
9
|
+
*
|
|
10
|
+
* Unlike our own ide/pdh bridges (ide-bridge.js / pdh-bridge.js), IDEA writes NO
|
|
11
|
+
* discovery lockfile, so cc cannot scan for it. Instead the ChainlessChain
|
|
12
|
+
* JetBrains plugin — which runs INSIDE the IDE and therefore knows whether MCP
|
|
13
|
+
* is supported (IDE >= 2025.2) and enabled, and on which endpoint — injects the
|
|
14
|
+
* exact endpoint into the env of the `cc agent` it spawns:
|
|
15
|
+
*
|
|
16
|
+
* CHAINLESSCHAIN_JETBRAINS_MCP_URL http://127.0.0.1:<port>/<path>
|
|
17
|
+
* CHAINLESSCHAIN_JETBRAINS_TOKEN optional bearer token
|
|
18
|
+
* CHAINLESSCHAIN_JETBRAINS_TRANSPORT optional: http | https | sse (else inferred)
|
|
19
|
+
*
|
|
20
|
+
* When the IDE does NOT support MCP (older than 2025.2) or it is disabled, the
|
|
21
|
+
* plugin injects nothing -> cc connects nothing. This is the product decision:
|
|
22
|
+
* if the IDE's MCP is unsupported, do not try to connect (no port scan, no
|
|
23
|
+
* external proxy fallback). Reserved server name `idea` -> tools `mcp__idea__*`.
|
|
24
|
+
*
|
|
25
|
+
* Pure CLI, no network scan, no external dependency: a deterministic
|
|
26
|
+
* env -> MCP-config mapping, fully unit-testable. The connect path reuses the
|
|
27
|
+
* existing MCP client (Streamable-HTTP) via mcp-config.js `loadJetbrainsMcp`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Transports the CLI's MCP client can actually talk (see mcp-client.js). */
|
|
31
|
+
const SUPPORTED_TRANSPORTS = new Set(["http", "https", "sse"]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Only ever connect to a loopback endpoint. IDEA's built-in server binds
|
|
35
|
+
* localhost; refusing anything else keeps an injected env var from pointing cc
|
|
36
|
+
* at an arbitrary host. `new URL(...).hostname` strips the brackets from an
|
|
37
|
+
* IPv6 literal on most Node builds but keeps them on some — accept both forms.
|
|
38
|
+
*/
|
|
39
|
+
function isLoopbackUrl(url) {
|
|
40
|
+
try {
|
|
41
|
+
const h = new URL(url).hostname.toLowerCase();
|
|
42
|
+
return (
|
|
43
|
+
h === "127.0.0.1" || h === "::1" || h === "[::1]" || h === "localhost"
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function inferTransportFromUrl(url) {
|
|
51
|
+
if (/\/sse(\b|\/|$)/i.test(url)) return "sse";
|
|
52
|
+
if (/^https:/i.test(url)) return "https";
|
|
53
|
+
if (/^http:/i.test(url)) return "http";
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Are we running under the ChainlessChain JetBrains plugin with IDE MCP
|
|
59
|
+
* available? True iff the plugin injected an MCP URL — which it only does when
|
|
60
|
+
* the IDE is >= 2025.2 and the MCP server is enabled. No URL = treat as
|
|
61
|
+
* unsupported and stay out of the way.
|
|
62
|
+
*/
|
|
63
|
+
export function isInJetbrainsContext(env = process.env) {
|
|
64
|
+
return !!(env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the IDEA built-in MCP server to connect to, or null.
|
|
69
|
+
*
|
|
70
|
+
* Deterministic: the plugin-injected `CHAINLESSCHAIN_JETBRAINS_MCP_URL` (a
|
|
71
|
+
* loopback http/https/sse endpoint) is the single source of truth. A missing,
|
|
72
|
+
* non-loopback, or unsupported-transport URL yields null (don't connect).
|
|
73
|
+
*
|
|
74
|
+
* @param {object} opts { env? }
|
|
75
|
+
* @returns {{url:string, transport:string, token:(string|null)}|null}
|
|
76
|
+
*/
|
|
77
|
+
export function discoverJetbrainsServer({ env = process.env } = {}) {
|
|
78
|
+
const url = env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL;
|
|
79
|
+
if (!url || !isLoopbackUrl(url)) return null;
|
|
80
|
+
|
|
81
|
+
const transport = String(
|
|
82
|
+
env.CHAINLESSCHAIN_JETBRAINS_TRANSPORT || inferTransportFromUrl(url) || "",
|
|
83
|
+
).toLowerCase();
|
|
84
|
+
if (!SUPPORTED_TRANSPORTS.has(transport)) return null;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
url,
|
|
88
|
+
transport,
|
|
89
|
+
token:
|
|
90
|
+
typeof env.CHAINLESSCHAIN_JETBRAINS_TOKEN === "string"
|
|
91
|
+
? env.CHAINLESSCHAIN_JETBRAINS_TOKEN
|
|
92
|
+
: null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A discovered server -> an MCP server config row for `setupMcpFromConfig`.
|
|
98
|
+
* `longRunning` exempts it from the agent loop's per-call timeout (some IDE
|
|
99
|
+
* operations — e.g. an indexing-gated search — can take a while), matching the
|
|
100
|
+
* ide/pdh bridges.
|
|
101
|
+
*/
|
|
102
|
+
export function jetbrainsServerToMcpConfig(found) {
|
|
103
|
+
if (!found || !found.url) return null;
|
|
104
|
+
const headers = {};
|
|
105
|
+
if (found.token) headers.Authorization = `Bearer ${found.token}`;
|
|
106
|
+
return {
|
|
107
|
+
url: found.url,
|
|
108
|
+
transport: found.transport,
|
|
109
|
+
headers,
|
|
110
|
+
longRunning: true,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Human-readable explanation of why discovery did / didn't pick a server —
|
|
116
|
+
* backs `cc ide jetbrains`. Never surfaces the raw token.
|
|
117
|
+
*/
|
|
118
|
+
export function diagnoseJetbrains({ env = process.env } = {}) {
|
|
119
|
+
const rawUrl = (env && env.CHAINLESSCHAIN_JETBRAINS_MCP_URL) || null;
|
|
120
|
+
const chosen = discoverJetbrainsServer({ env });
|
|
121
|
+
|
|
122
|
+
let reason;
|
|
123
|
+
if (chosen) {
|
|
124
|
+
reason = "CHAINLESSCHAIN_JETBRAINS_MCP_URL injected by the IDE plugin";
|
|
125
|
+
} else if (!rawUrl) {
|
|
126
|
+
reason =
|
|
127
|
+
"no CHAINLESSCHAIN_JETBRAINS_MCP_URL — IDE MCP is unsupported (IDEA < " +
|
|
128
|
+
"2025.2) / disabled, or cc was not launched from the JetBrains plugin";
|
|
129
|
+
} else if (!isLoopbackUrl(rawUrl)) {
|
|
130
|
+
reason =
|
|
131
|
+
"injected URL is not a loopback (127.0.0.1/::1/localhost) endpoint";
|
|
132
|
+
} else {
|
|
133
|
+
reason = "injected URL is not a supported transport (http/https/sse)";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
supported: !!rawUrl,
|
|
138
|
+
chosen: chosen
|
|
139
|
+
? {
|
|
140
|
+
url: chosen.url,
|
|
141
|
+
transport: chosen.transport,
|
|
142
|
+
hasToken: !!chosen.token,
|
|
143
|
+
}
|
|
144
|
+
: null,
|
|
145
|
+
reason,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -80,6 +80,48 @@ export function validateAgainstSchema(value, schema, path = "$") {
|
|
|
80
80
|
return errors;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Return the first *balanced* JSON object/array substring in `text`, honoring
|
|
85
|
+
* string literals so braces inside strings don't throw off the depth count.
|
|
86
|
+
*
|
|
87
|
+
* Unlike a greedy `/\{[\s\S]*\}/` match (which spans to the LAST bracket and
|
|
88
|
+
* over-captures trailing prose or a second object — making JSON.parse throw),
|
|
89
|
+
* this stops at the first complete top-level value.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} text
|
|
92
|
+
* @param {"{"|"["} [only] restrict to object- or array-openers when set
|
|
93
|
+
* @returns {string|null} the balanced substring, or null if none is found
|
|
94
|
+
*/
|
|
95
|
+
export function firstBalancedJson(text, only) {
|
|
96
|
+
const s = String(text || "");
|
|
97
|
+
for (let i = 0; i < s.length; i++) {
|
|
98
|
+
const open = s[i];
|
|
99
|
+
if (open !== "{" && open !== "[") continue;
|
|
100
|
+
if (only && open !== only) continue;
|
|
101
|
+
const close = open === "{" ? "}" : "]";
|
|
102
|
+
let depth = 0;
|
|
103
|
+
let inStr = false;
|
|
104
|
+
let esc = false;
|
|
105
|
+
for (let j = i; j < s.length; j++) {
|
|
106
|
+
const c = s[j];
|
|
107
|
+
if (inStr) {
|
|
108
|
+
if (esc) esc = false;
|
|
109
|
+
else if (c === "\\") esc = true;
|
|
110
|
+
else if (c === '"') inStr = false;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (c === '"') inStr = true;
|
|
114
|
+
else if (c === open) depth++;
|
|
115
|
+
else if (c === close) {
|
|
116
|
+
depth--;
|
|
117
|
+
if (depth === 0) return s.slice(i, j + 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// opener at i never closed — try the next opener
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
83
125
|
/** Pull a JSON payload out of an LLM reply (bare, fenced, or embedded). */
|
|
84
126
|
export function extractJsonPayload(text) {
|
|
85
127
|
const raw = String(text || "").trim();
|
|
@@ -87,6 +129,11 @@ export function extractJsonPayload(text) {
|
|
|
87
129
|
tries.push(raw);
|
|
88
130
|
const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(raw);
|
|
89
131
|
if (fence) tries.push(fence[1].trim());
|
|
132
|
+
// Prefer a balanced run (stops at the first complete value) over the greedy
|
|
133
|
+
// first-bracket..last-bracket slice below, which over-captures on trailing
|
|
134
|
+
// prose or a second object.
|
|
135
|
+
const balanced = firstBalancedJson(raw);
|
|
136
|
+
if (balanced) tries.push(balanced);
|
|
90
137
|
const firstObj = raw.indexOf("{");
|
|
91
138
|
const lastObj = raw.lastIndexOf("}");
|
|
92
139
|
if (firstObj !== -1 && lastObj > firstObj)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { extractToolNames } from "./skill-synthesizer.js";
|
|
15
|
+
import { firstBalancedJson } from "../json-schema-output.js";
|
|
15
16
|
|
|
16
17
|
// ── _deps for test injection ────────────────────────
|
|
17
18
|
const _deps = {
|
|
@@ -268,9 +269,9 @@ export class ReflectionEngine {
|
|
|
268
269
|
try {
|
|
269
270
|
const messages = buildReflectionPrompt(reportData);
|
|
270
271
|
const response = await this.llmChat(messages);
|
|
271
|
-
const
|
|
272
|
-
if (!
|
|
273
|
-
return JSON.parse(
|
|
272
|
+
const jsonText = firstBalancedJson(response, "{");
|
|
273
|
+
if (!jsonText) return null;
|
|
274
|
+
return JSON.parse(jsonText);
|
|
274
275
|
} catch {
|
|
275
276
|
return null;
|
|
276
277
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import fs from "fs";
|
|
15
|
+
import { firstBalancedJson } from "../json-schema-output.js";
|
|
15
16
|
import path from "path";
|
|
16
17
|
|
|
17
18
|
// ── _deps for test injection ────────────────────────────
|
|
@@ -374,9 +375,9 @@ export class SkillImprover {
|
|
|
374
375
|
if (!this.llmChat) return null;
|
|
375
376
|
try {
|
|
376
377
|
const response = await this.llmChat(messages);
|
|
377
|
-
const
|
|
378
|
-
if (!
|
|
379
|
-
return JSON.parse(
|
|
378
|
+
const jsonText = firstBalancedJson(response, "{");
|
|
379
|
+
if (!jsonText) return null;
|
|
380
|
+
return JSON.parse(jsonText);
|
|
380
381
|
} catch {
|
|
381
382
|
return null;
|
|
382
383
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import fs from "fs";
|
|
20
|
+
import { firstBalancedJson } from "../json-schema-output.js";
|
|
20
21
|
import path from "path";
|
|
21
22
|
|
|
22
23
|
// ── _deps for test injection ────────────────────────────
|
|
@@ -258,9 +259,9 @@ export class SkillSynthesizer {
|
|
|
258
259
|
|
|
259
260
|
try {
|
|
260
261
|
// Try to parse JSON from response
|
|
261
|
-
const
|
|
262
|
-
if (!
|
|
263
|
-
return JSON.parse(
|
|
262
|
+
const jsonText = firstBalancedJson(response, "{");
|
|
263
|
+
if (!jsonText) return null;
|
|
264
|
+
return JSON.parse(jsonText);
|
|
264
265
|
} catch {
|
|
265
266
|
return null;
|
|
266
267
|
}
|