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
|
@@ -101,6 +101,17 @@ export function isLikelyConnectionError(err) {
|
|
|
101
101
|
const MCP_DISCOVERY_RETRIES = 2; // up to 3 attempts total
|
|
102
102
|
const MCP_DISCOVERY_BACKOFF_MS = 250; // 250ms, 500ms
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Hard cap on the unterminated stdout tail buffered per stdio server. JSON-RPC
|
|
106
|
+
* frames are newline-delimited; a runaway or non-MCP process that streams
|
|
107
|
+
* without ever emitting a newline would otherwise grow `entry._buffer` without
|
|
108
|
+
* bound and exhaust memory. 16M chars is far above any legitimate single MCP
|
|
109
|
+
* message (even a tool result with embedded base64), so crossing it signals a
|
|
110
|
+
* misbehaving server. Override per server with `config.maxBufferChars` (0
|
|
111
|
+
* disables the cap).
|
|
112
|
+
*/
|
|
113
|
+
const MCP_MAX_BUFFER_CHARS = 16 * 1024 * 1024;
|
|
114
|
+
|
|
104
115
|
/**
|
|
105
116
|
* Is this a TRANSIENT error worth a short retry during capability discovery?
|
|
106
117
|
* Narrower than isLikelyConnectionError: connection-level failures and 5xx
|
|
@@ -293,6 +304,20 @@ export class MCPClient extends EventEmitter {
|
|
|
293
304
|
failPending(`MCP server "${name}" process error: ${err.message}`);
|
|
294
305
|
this.emit("server-error", { name, error: err.message });
|
|
295
306
|
});
|
|
307
|
+
|
|
308
|
+
// Writing to a stdio server that closed its stdin read end (or died
|
|
309
|
+
// mid-write) makes the stdin pipe emit an asynchronous 'error' (EPIPE).
|
|
310
|
+
// An 'error' event with no listener is an uncaught exception in Node and
|
|
311
|
+
// would CRASH the whole CLI — and the try/catch around stdin.write only
|
|
312
|
+
// catches synchronous throws, not this async event. Handle it: drain
|
|
313
|
+
// in-flight requests and surface it, mirroring the process 'error' path.
|
|
314
|
+
if (proc.stdin && typeof proc.stdin.on === "function") {
|
|
315
|
+
proc.stdin.on("error", (err) => {
|
|
316
|
+
entry.state = ServerState.ERROR;
|
|
317
|
+
failPending(`MCP server "${name}" stdin error: ${err.message}`);
|
|
318
|
+
this.emit("server-error", { name, error: err.message });
|
|
319
|
+
});
|
|
320
|
+
}
|
|
296
321
|
} else {
|
|
297
322
|
throw new Error(
|
|
298
323
|
`transport "${transportKind}" is not supported by this client`,
|
|
@@ -384,6 +409,7 @@ export class MCPClient extends EventEmitter {
|
|
|
384
409
|
try {
|
|
385
410
|
entry.process.stdout?.removeAllListeners();
|
|
386
411
|
entry.process.stderr?.removeAllListeners();
|
|
412
|
+
entry.process.stdin?.removeAllListeners?.();
|
|
387
413
|
entry.process.removeAllListeners();
|
|
388
414
|
entry.process.kill();
|
|
389
415
|
} catch {
|
|
@@ -788,19 +814,18 @@ export class MCPClient extends EventEmitter {
|
|
|
788
814
|
const contentType = response.headers?.get
|
|
789
815
|
? String(response.headers.get("content-type") || "").toLowerCase()
|
|
790
816
|
: "";
|
|
817
|
+
// Bound the response body the same way the stdio path bounds its line
|
|
818
|
+
// buffer (per-server maxBufferChars override; 0 disables).
|
|
819
|
+
const cap = Number.isFinite(entry.config?.maxBufferChars)
|
|
820
|
+
? entry.config.maxBufferChars
|
|
821
|
+
: MCP_MAX_BUFFER_CHARS;
|
|
791
822
|
|
|
792
823
|
let envelope;
|
|
793
824
|
if (contentType.includes("text/event-stream")) {
|
|
794
|
-
envelope = await _extractSseResponse(response, id);
|
|
825
|
+
envelope = await _extractSseResponse(response, id, cap);
|
|
795
826
|
} else {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
? await response.json()
|
|
799
|
-
: JSON.parse(
|
|
800
|
-
typeof response.text === "function"
|
|
801
|
-
? await response.text()
|
|
802
|
-
: "",
|
|
803
|
-
);
|
|
827
|
+
const text = await _readBodyCapped(response, cap);
|
|
828
|
+
envelope = text ? JSON.parse(text) : null;
|
|
804
829
|
}
|
|
805
830
|
|
|
806
831
|
if (!envelope || typeof envelope !== "object") {
|
|
@@ -868,6 +893,37 @@ export class MCPClient extends EventEmitter {
|
|
|
868
893
|
// Skip malformed lines
|
|
869
894
|
}
|
|
870
895
|
}
|
|
896
|
+
|
|
897
|
+
// Guard against unbounded buffer growth: a runaway / non-MCP server that
|
|
898
|
+
// streams without ever sending a newline would grow the unterminated tail
|
|
899
|
+
// forever and exhaust memory. If the leftover partial line exceeds the cap,
|
|
900
|
+
// treat it as a fatal transport error (drop the buffer, drain in-flight
|
|
901
|
+
// requests, kill the process) rather than letting it grow without limit.
|
|
902
|
+
const cap = Number.isFinite(entry.config?.maxBufferChars)
|
|
903
|
+
? entry.config.maxBufferChars
|
|
904
|
+
: MCP_MAX_BUFFER_CHARS;
|
|
905
|
+
if (cap > 0 && entry._buffer.length > cap) {
|
|
906
|
+
entry._buffer = "";
|
|
907
|
+
entry.state = ServerState.ERROR;
|
|
908
|
+
const errMsg = `MCP server "${serverName}" exceeded the ${cap}-char line buffer with no newline (runaway or non-MCP output)`;
|
|
909
|
+
for (const [, pending] of entry._pending) {
|
|
910
|
+
if (pending.timeout) clearTimeout(pending.timeout);
|
|
911
|
+
try {
|
|
912
|
+
pending.reject(new Error(errMsg));
|
|
913
|
+
} catch {
|
|
914
|
+
// already settled — ignore
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
entry._pending.clear();
|
|
918
|
+
if (entry.process) {
|
|
919
|
+
try {
|
|
920
|
+
entry.process.kill();
|
|
921
|
+
} catch {
|
|
922
|
+
// best-effort — surfacing the error is what matters
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
this.emit("server-error", { name: serverName, error: errMsg });
|
|
926
|
+
}
|
|
871
927
|
}
|
|
872
928
|
|
|
873
929
|
_handleMessage(serverName, msg) {
|
|
@@ -900,28 +956,70 @@ export class MCPClient extends EventEmitter {
|
|
|
900
956
|
}
|
|
901
957
|
|
|
902
958
|
/**
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
959
|
+
* Read an HTTP response body to text with a hard size cap, mirroring the stdio
|
|
960
|
+
* transport's MCP_MAX_BUFFER_CHARS guard. The per-call timeout bounds TIME but
|
|
961
|
+
* not MEMORY: a malicious/buggy HTTP MCP server could stream a multi-GB body
|
|
962
|
+
* within the timeout and OOM the client. When the body is a readable stream
|
|
963
|
+
* (real fetch) we accumulate with a running cap and cancel on overflow; for a
|
|
964
|
+
* non-stream response (test mocks) we fall back to text() + a post-hoc check.
|
|
965
|
+
* A declared Content-Length over the cap is rejected before any read. cap<=0
|
|
966
|
+
* disables the limit.
|
|
906
967
|
*/
|
|
907
|
-
async function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
968
|
+
async function _readBodyCapped(response, cap) {
|
|
969
|
+
if (
|
|
970
|
+
cap > 0 &&
|
|
971
|
+
response.headers &&
|
|
972
|
+
typeof response.headers.get === "function"
|
|
973
|
+
) {
|
|
974
|
+
const declared = Number(response.headers.get("content-length"));
|
|
975
|
+
if (Number.isFinite(declared) && declared > cap) {
|
|
976
|
+
throw new Error(
|
|
977
|
+
`MCP HTTP response exceeds the ${cap}-byte cap (content-length ${declared})`,
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (response.body && typeof response.body.getReader === "function") {
|
|
912
982
|
const reader = response.body.getReader();
|
|
913
983
|
const decoder = new TextDecoder("utf-8");
|
|
914
984
|
const chunks = [];
|
|
915
|
-
|
|
985
|
+
let total = 0;
|
|
986
|
+
for (;;) {
|
|
916
987
|
const { value, done } = await reader.read();
|
|
917
988
|
if (done) break;
|
|
989
|
+
total += value && value.length ? value.length : 0;
|
|
990
|
+
if (cap > 0 && total > cap) {
|
|
991
|
+
try {
|
|
992
|
+
await reader.cancel();
|
|
993
|
+
} catch {
|
|
994
|
+
/* best-effort — the throw below is what matters */
|
|
995
|
+
}
|
|
996
|
+
throw new Error(
|
|
997
|
+
`MCP HTTP response exceeded the ${cap}-byte cap (runaway server)`,
|
|
998
|
+
);
|
|
999
|
+
}
|
|
918
1000
|
chunks.push(decoder.decode(value, { stream: true }));
|
|
919
1001
|
}
|
|
920
1002
|
chunks.push(decoder.decode());
|
|
921
|
-
|
|
922
|
-
}
|
|
923
|
-
|
|
1003
|
+
return chunks.join("");
|
|
1004
|
+
}
|
|
1005
|
+
if (typeof response.text !== "function") {
|
|
1006
|
+
throw new Error("HTTP response is not readable");
|
|
1007
|
+
}
|
|
1008
|
+
const text = await response.text();
|
|
1009
|
+
if (cap > 0 && text.length > cap) {
|
|
1010
|
+
throw new Error(`MCP HTTP response exceeded the ${cap}-char cap`);
|
|
924
1011
|
}
|
|
1012
|
+
return text;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Parse a `text/event-stream` HTTP response body and return the first
|
|
1017
|
+
* JSON-RPC envelope whose id matches `requestId`. Tolerates multiple
|
|
1018
|
+
* `data:` chunks, comments, and non-JSON-RPC events. `cap` bounds the body
|
|
1019
|
+
* size (see _readBodyCapped).
|
|
1020
|
+
*/
|
|
1021
|
+
async function _extractSseResponse(response, requestId, cap = 0) {
|
|
1022
|
+
const text = await _readBodyCapped(response, cap);
|
|
925
1023
|
|
|
926
1024
|
// Split into events on blank line, parse each event's concatenated `data:` lines.
|
|
927
1025
|
const events = text.split(/\r?\n\r?\n/);
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { feature, featureVariant } from "../lib/feature-flags.js";
|
|
14
14
|
|
|
15
|
+
// Bounds for the fuzzy near-dup pass in _deduplicate (see there). Generous
|
|
16
|
+
// enough that real near-dups are still caught; small enough that compaction of
|
|
17
|
+
// a long, tool-heavy history stays sub-second instead of O(n²·content).
|
|
18
|
+
const DEDUP_JACCARD_MAX_CHARS = 4000;
|
|
19
|
+
const DEDUP_FUZZY_WINDOW = 100;
|
|
20
|
+
|
|
15
21
|
export function estimateTokens(text) {
|
|
16
22
|
if (!text) return 0;
|
|
17
23
|
const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
|
|
@@ -320,8 +326,18 @@ export class PromptCompressor {
|
|
|
320
326
|
const last = [...messages].reverse().find((m) => m.role === "user");
|
|
321
327
|
const rest = messages.filter((m) => m.role !== "system" && m !== last);
|
|
322
328
|
|
|
329
|
+
// Bound the fuzzy pass so it stays usable on the very conversations
|
|
330
|
+
// compaction targets (long + tool-heavy). The naive version compared every
|
|
331
|
+
// message against EVERY prior one (O(n²)) and ran jaccard over full content
|
|
332
|
+
// (O(content) per compare) — 300 msgs × ~8 KB tool results blocked ~3.4 s.
|
|
333
|
+
// • Exact dedup (the md5 hash set) still covers ALL messages.
|
|
334
|
+
// • The fuzzy near-dup compare uses a capped prefix (char-set jaccard is
|
|
335
|
+
// alphabet-bounded, so a prefix barely changes the decision) and only the
|
|
336
|
+
// most-recent kept entries (near-dups cluster; this can only UNDER-dedup,
|
|
337
|
+
// never drop a genuinely-distinct message).
|
|
323
338
|
const seen = new Map();
|
|
324
339
|
const deduped = [];
|
|
340
|
+
const recent = []; // rolling window of recent kept {capped} contents
|
|
325
341
|
|
|
326
342
|
for (const msg of rest) {
|
|
327
343
|
const content = getContent(msg);
|
|
@@ -329,12 +345,13 @@ export class PromptCompressor {
|
|
|
329
345
|
|
|
330
346
|
if (seen.has(hash)) continue;
|
|
331
347
|
|
|
348
|
+
const capped =
|
|
349
|
+
content.length > DEDUP_JACCARD_MAX_CHARS
|
|
350
|
+
? content.slice(0, DEDUP_JACCARD_MAX_CHARS)
|
|
351
|
+
: content;
|
|
332
352
|
let isDup = false;
|
|
333
|
-
for (const
|
|
334
|
-
if (
|
|
335
|
-
jaccardSimilarity(content, getContent(existing)) >=
|
|
336
|
-
this.similarityThreshold
|
|
337
|
-
) {
|
|
353
|
+
for (const prev of recent) {
|
|
354
|
+
if (jaccardSimilarity(capped, prev) >= this.similarityThreshold) {
|
|
338
355
|
isDup = true;
|
|
339
356
|
break;
|
|
340
357
|
}
|
|
@@ -343,6 +360,8 @@ export class PromptCompressor {
|
|
|
343
360
|
if (!isDup) {
|
|
344
361
|
seen.set(hash, msg);
|
|
345
362
|
deduped.push(msg);
|
|
363
|
+
recent.push(capped);
|
|
364
|
+
if (recent.length > DEDUP_FUZZY_WINDOW) recent.shift();
|
|
346
365
|
}
|
|
347
366
|
}
|
|
348
367
|
|
|
@@ -13,7 +13,9 @@ import { resolve } from "node:path";
|
|
|
13
13
|
import {
|
|
14
14
|
isGitRepo,
|
|
15
15
|
gitExec,
|
|
16
|
+
gitExecArgs,
|
|
16
17
|
assertSafeGitRef,
|
|
18
|
+
assertSafeGitPath,
|
|
17
19
|
} from "../lib/git-integration.js";
|
|
18
20
|
|
|
19
21
|
const WORKTREE_DIR = ".worktrees";
|
|
@@ -167,6 +169,7 @@ export function diffWorktree(repoDir, branchName, options = {}) {
|
|
|
167
169
|
if (options.baseBranch) assertSafeGitRef(options.baseBranch, "base branch");
|
|
168
170
|
const base = options.baseBranch || "HEAD";
|
|
169
171
|
const filePath = options.filePath || null;
|
|
172
|
+
if (filePath) assertSafeGitPath(filePath, "file path");
|
|
170
173
|
const fileArg = filePath ? ` -- "${filePath}"` : "";
|
|
171
174
|
const statOutput = gitExec(
|
|
172
175
|
`diff ${base}...${branchName} --stat --numstat${fileArg}`,
|
|
@@ -223,14 +226,13 @@ export function mergeWorktree(repoDir, branchName, options = {}) {
|
|
|
223
226
|
const msg =
|
|
224
227
|
options.message ||
|
|
225
228
|
`feat: merge agent work from ${branchName} (squashed)`;
|
|
226
|
-
|
|
229
|
+
// Free-text message → argv (no shell) so `$()`/backticks/quotes in it
|
|
230
|
+
// can't inject a command (the old `-m "…"` only escaped `"`).
|
|
231
|
+
gitExecArgs(["commit", "-m", msg], repoDir);
|
|
227
232
|
} else {
|
|
228
233
|
const msg =
|
|
229
234
|
options.message || `Merge branch '${branchName}' (agent task)`;
|
|
230
|
-
|
|
231
|
-
`merge ${branchName} --no-edit -m "${msg.replace(/"/g, '\\"')}"`,
|
|
232
|
-
repoDir,
|
|
233
|
-
);
|
|
235
|
+
gitExecArgs(["merge", branchName, "--no-edit", "-m", msg], repoDir);
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
if (deleteBranch) {
|
|
@@ -393,6 +395,7 @@ export function applyWorktreeAutomationCandidate(
|
|
|
393
395
|
const conflictType = options.conflictType || null;
|
|
394
396
|
const baseBranch = _resolveBaseBranchForMerge(repoDir, options.baseBranch);
|
|
395
397
|
|
|
398
|
+
if (filePath) assertSafeGitPath(filePath, "file path");
|
|
396
399
|
if (!filePath) {
|
|
397
400
|
throw new Error("filePath is required");
|
|
398
401
|
}
|
|
@@ -459,7 +462,11 @@ export function applyWorktreeAutomationCandidate(
|
|
|
459
462
|
const commitMessage =
|
|
460
463
|
options.commitMessage ||
|
|
461
464
|
`Resolve ${filePath} via ${candidate.label || candidateId}`;
|
|
462
|
-
|
|
465
|
+
// Free-text message → argv (no shell) so `$()` / backticks / quotes in a
|
|
466
|
+
// caller-supplied commitMessage can't inject a command. The prior
|
|
467
|
+
// `-m "${msg.replace(/"/g,'\\"')}"` only neutralized double quotes — the
|
|
468
|
+
// same gap already fixed in mergeWorktree.
|
|
469
|
+
gitExecArgs(["commit", "-m", commitMessage], worktree.path);
|
|
463
470
|
|
|
464
471
|
const diff = diffWorktree(repoDir, branchName, { baseBranch });
|
|
465
472
|
const filesChanged = diff.summary?.filesChanged || 0;
|
|
@@ -514,8 +521,10 @@ export function worktreeLog(repoDir, branchName, baseBranch = "HEAD") {
|
|
|
514
521
|
|
|
515
522
|
function _fileStatus(repoDir, base, branch, filePath) {
|
|
516
523
|
try {
|
|
517
|
-
|
|
518
|
-
|
|
524
|
+
// filePath here comes from git's conflict output (a tracked file name);
|
|
525
|
+
// pass it via argv so an exotic / maliciously-named file can't inject.
|
|
526
|
+
const output = gitExecArgs(
|
|
527
|
+
["diff", `${base}...${branch}`, "--name-status", "--", filePath],
|
|
519
528
|
repoDir,
|
|
520
529
|
);
|
|
521
530
|
const status = output.trim().charAt(0);
|
|
@@ -603,7 +612,10 @@ function _buildConflictSummary(repoDir, filePath, branchName) {
|
|
|
603
612
|
|
|
604
613
|
function _conflictStatusCode(repoDir, filePath) {
|
|
605
614
|
try {
|
|
606
|
-
const output =
|
|
615
|
+
const output = gitExecArgs(
|
|
616
|
+
["status", "--porcelain", "--", filePath],
|
|
617
|
+
repoDir,
|
|
618
|
+
);
|
|
607
619
|
const firstLine = output.split("\n").find((line) => line.trim());
|
|
608
620
|
return firstLine ? firstLine.slice(0, 2) : "UU";
|
|
609
621
|
} catch (_e) {
|
|
@@ -779,7 +791,10 @@ function _buildDiffPreview(repoDir, filePath, branchName) {
|
|
|
779
791
|
};
|
|
780
792
|
|
|
781
793
|
try {
|
|
782
|
-
const diff =
|
|
794
|
+
const diff = gitExecArgs(
|
|
795
|
+
["diff", `HEAD...${branchName}`, "--", filePath],
|
|
796
|
+
repoDir,
|
|
797
|
+
);
|
|
783
798
|
preview.snippet =
|
|
784
799
|
diff.length > 2000
|
|
785
800
|
? `${diff.slice(0, 2000)}\n... [diff truncated]`
|
package/src/lib/agent-core.js
CHANGED
|
@@ -3,8 +3,11 @@ export {
|
|
|
3
3
|
AGENT_TOOLS,
|
|
4
4
|
AGENT_TOOL_REGISTRY,
|
|
5
5
|
MAX_SUB_AGENT_DEPTH,
|
|
6
|
+
MAX_SUB_AGENTS_PER_RUN,
|
|
6
7
|
MAX_TOOL_RESULT_CHARS,
|
|
7
8
|
capToolResultString,
|
|
9
|
+
safeStringifyToolResult,
|
|
10
|
+
tokenizeShellWords,
|
|
8
11
|
reloadSkills,
|
|
9
12
|
getAgentToolDefinitions,
|
|
10
13
|
getAgentToolDescriptors,
|
|
@@ -25,6 +28,7 @@ export {
|
|
|
25
28
|
listBackgroundShellTasks,
|
|
26
29
|
killBackgroundShellTask,
|
|
27
30
|
killAllBackgroundShellTasks,
|
|
31
|
+
reapIdleBackgroundShellTasks,
|
|
28
32
|
writeFileVerified,
|
|
29
33
|
formatProviderHttpError,
|
|
30
34
|
_accumulateOllamaStream,
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { EventEmitter } from "events";
|
|
11
|
+
import { firstBalancedJson } from "./json-schema-output.js";
|
|
11
12
|
|
|
12
13
|
// Exported for test injection
|
|
13
14
|
export const _deps = {
|
|
@@ -47,6 +48,12 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
47
48
|
this._initialized = false;
|
|
48
49
|
this._maxIterations = 20;
|
|
49
50
|
this._maxRetries = 3;
|
|
51
|
+
// Cap retained goal history. submitGoal stores every goal in _goals and
|
|
52
|
+
// nothing removes it; the REPL keeps one long-lived agent for the whole
|
|
53
|
+
// session, so goals (each holding steps/errors/result) accumulate without
|
|
54
|
+
// bound — and listGoals() returns the whole growing Map. Prune the oldest
|
|
55
|
+
// TERMINAL goals beyond this cap; running/pending/paused goals are kept.
|
|
56
|
+
this._maxGoals = 200;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
/**
|
|
@@ -57,12 +64,14 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
57
64
|
toolExecutor,
|
|
58
65
|
hookManager,
|
|
59
66
|
maxIterations,
|
|
67
|
+
maxGoals,
|
|
60
68
|
iterationBudget,
|
|
61
69
|
} = {}) {
|
|
62
70
|
this._llmChat = llmChat || null;
|
|
63
71
|
this._toolExecutor = toolExecutor || null;
|
|
64
72
|
this._hookManager = hookManager || null;
|
|
65
73
|
if (maxIterations) this._maxIterations = maxIterations;
|
|
74
|
+
if (Number.isFinite(maxGoals) && maxGoals > 0) this._maxGoals = maxGoals;
|
|
66
75
|
this._iterationBudget = iterationBudget || null; // shared budget from caller
|
|
67
76
|
this._initialized = true;
|
|
68
77
|
}
|
|
@@ -95,6 +104,7 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
95
104
|
};
|
|
96
105
|
|
|
97
106
|
this._goals.set(goalId, goal);
|
|
107
|
+
this._pruneGoals();
|
|
98
108
|
this.emit("goal:submitted", { goalId, description });
|
|
99
109
|
|
|
100
110
|
// Start the ReAct loop asynchronously
|
|
@@ -107,6 +117,26 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
107
117
|
return { goalId };
|
|
108
118
|
}
|
|
109
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Bound retained goal history to _maxGoals. Evicts the oldest TERMINAL goals
|
|
122
|
+
* (completed/failed/cancelled) FIFO; a still-active goal (pending/running/
|
|
123
|
+
* paused) is never dropped, so a burst of concurrent goals can briefly exceed
|
|
124
|
+
* the cap rather than losing live work.
|
|
125
|
+
*/
|
|
126
|
+
_pruneGoals() {
|
|
127
|
+
if (this._goals.size <= this._maxGoals) return;
|
|
128
|
+
for (const [id, g] of this._goals) {
|
|
129
|
+
if (this._goals.size <= this._maxGoals) break;
|
|
130
|
+
if (
|
|
131
|
+
g.status === GoalStatus.COMPLETED ||
|
|
132
|
+
g.status === GoalStatus.FAILED ||
|
|
133
|
+
g.status === GoalStatus.CANCELLED
|
|
134
|
+
) {
|
|
135
|
+
this._goals.delete(id);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
110
140
|
/**
|
|
111
141
|
* Pause a running goal.
|
|
112
142
|
*/
|
|
@@ -471,10 +501,10 @@ Reply with a JSON object: { "action": "retry|add_step|skip", "newParams": {...},
|
|
|
471
501
|
|
|
472
502
|
_parseSteps(text) {
|
|
473
503
|
try {
|
|
474
|
-
// Extract JSON array from response
|
|
475
|
-
const
|
|
476
|
-
if (
|
|
477
|
-
return JSON.parse(
|
|
504
|
+
// Extract the first balanced JSON array from the response
|
|
505
|
+
const jsonText = firstBalancedJson(text, "[");
|
|
506
|
+
if (jsonText) {
|
|
507
|
+
return JSON.parse(jsonText);
|
|
478
508
|
}
|
|
479
509
|
} catch (_err) {
|
|
480
510
|
// Parse failure
|
|
@@ -484,8 +514,8 @@ Reply with a JSON object: { "action": "retry|add_step|skip", "newParams": {...},
|
|
|
484
514
|
|
|
485
515
|
_parseJSON(text) {
|
|
486
516
|
try {
|
|
487
|
-
const
|
|
488
|
-
if (
|
|
517
|
+
const jsonText = firstBalancedJson(text, "{");
|
|
518
|
+
if (jsonText) return JSON.parse(jsonText);
|
|
489
519
|
} catch (_err) {
|
|
490
520
|
// Parse failure
|
|
491
521
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { chatWithStreaming, chatStream } from "./chat-core.js";
|
|
21
|
+
import { firstBalancedJson } from "./json-schema-output.js";
|
|
21
22
|
|
|
22
23
|
const DEFAULT_INTENT_TIMEOUT_MS = 15000;
|
|
23
24
|
|
|
@@ -76,8 +77,9 @@ function extractJson(content) {
|
|
|
76
77
|
if (fenced) return fenced[1].trim();
|
|
77
78
|
const generic = content.match(/```\s*([\s\S]*?)```/);
|
|
78
79
|
if (generic) return generic[1].trim();
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
// Balanced extraction stops at the first complete object instead of the old
|
|
81
|
+
// greedy /\{[\s\S]*\}/, which over-captured trailing prose with a stray }.
|
|
82
|
+
return firstBalancedJson(content, "{");
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/**
|
|
@@ -317,7 +319,12 @@ const FOLLOWUP_RULES = {
|
|
|
317
319
|
/(颜色|字体|大小|位置|标题).*(是|用|为)/,
|
|
318
320
|
/^.{1,20}(应该|具体)(是|为)/,
|
|
319
321
|
/^数据(来源|是)/,
|
|
320
|
-
|
|
322
|
+
// De-catastrophized: the old `/.*(用|采用).*(字体…)/` had a leading greedy
|
|
323
|
+
// `.*` overlapping `(用)`, causing exponential backtracking (a 2 KB input
|
|
324
|
+
// of "用" took ~3 s). `.test()` already searches anywhere, so the leading
|
|
325
|
+
// `.*` is redundant; a lazy middle drops it to quadratic, and the input
|
|
326
|
+
// cap in ruleBasedClassify bounds that.
|
|
327
|
+
/(用|采用)[\s\S]*?(字体|颜色|大小)/,
|
|
321
328
|
],
|
|
322
329
|
},
|
|
323
330
|
CANCEL_TASK: {
|
|
@@ -332,8 +339,15 @@ const FOLLOWUP_RULES = {
|
|
|
332
339
|
},
|
|
333
340
|
};
|
|
334
341
|
|
|
342
|
+
// A follow-up intent (continue / modify / clarify / cancel) is determinable
|
|
343
|
+
// from the start of the message; cap the text the heuristic regexes see so a
|
|
344
|
+
// huge paste can't drive their (quadratic) backtracking into a DoS. The full
|
|
345
|
+
// input is still forwarded to the LLM path / downstream — only this local
|
|
346
|
+
// classification is truncated.
|
|
347
|
+
const MAX_CLASSIFY_INPUT = 2000;
|
|
348
|
+
|
|
335
349
|
function ruleBasedClassify(userInput) {
|
|
336
|
-
const input = (userInput || "").trim();
|
|
350
|
+
const input = (userInput || "").trim().slice(0, MAX_CLASSIFY_INPUT);
|
|
337
351
|
|
|
338
352
|
if (input.length === 0) {
|
|
339
353
|
return {
|
|
@@ -76,9 +76,19 @@ function gitDir(root) {
|
|
|
76
76
|
return path.resolve(root, git(["rev-parse", "--git-dir"], { cwd: root }));
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Ref-safe session segment. Beyond the charset filter, enforce git's per-
|
|
81
|
+
* component ref rules so a legit-looking name never makes every checkpoint op
|
|
82
|
+
* throw: a component may not begin with `.`, contain `..`, or end with `.lock`
|
|
83
|
+
* (`/` is already collapsed to `-`, so the session is always a single segment
|
|
84
|
+
* and cannot traverse the ref namespace).
|
|
85
|
+
*/
|
|
80
86
|
function sanitizeSession(session) {
|
|
81
|
-
|
|
87
|
+
let s = String(session || "default").replace(/[^A-Za-z0-9._-]/g, "-");
|
|
88
|
+
s = s.replace(/\.{2,}/g, "."); // git forbids ".." in a refname
|
|
89
|
+
s = s.replace(/^\.+/, ""); // a component may not begin with "."
|
|
90
|
+
s = s.replace(/\.lock$/i, "-lock"); // nor end with ".lock"
|
|
91
|
+
s = s.replace(/\.+$/, ""); // nor end with a dot
|
|
82
92
|
return s || "default";
|
|
83
93
|
}
|
|
84
94
|
|
|
@@ -86,9 +96,20 @@ function sessionPrefix(session) {
|
|
|
86
96
|
return `${REF_NS}/${sanitizeSession(session)}`;
|
|
87
97
|
}
|
|
88
98
|
|
|
99
|
+
// Monotonic per-process counter so two temp-index paths minted in the same
|
|
100
|
+
// millisecond never collide. pid disambiguates across processes; Date.now() +
|
|
101
|
+
// this counter disambiguate within one. A collision would mean two operations
|
|
102
|
+
// sharing one GIT_INDEX_FILE (corrupt snapshot) or one's cleanup deleting the
|
|
103
|
+
// other's live index.
|
|
104
|
+
let _indexSeq = 0;
|
|
105
|
+
|
|
89
106
|
/** Unique temp index path inside .git (never the real index). */
|
|
90
107
|
function tempIndexPath(dir) {
|
|
91
|
-
|
|
108
|
+
_indexSeq = (_indexSeq + 1) >>> 0;
|
|
109
|
+
return path.join(
|
|
110
|
+
dir,
|
|
111
|
+
`cc-checkpoint-index-${process.pid}-${Date.now()}-${_indexSeq}`,
|
|
112
|
+
);
|
|
92
113
|
}
|
|
93
114
|
|
|
94
115
|
/**
|
|
@@ -256,6 +277,29 @@ export function createCheckpoint(cwd = process.cwd(), opts = {}) {
|
|
|
256
277
|
/* non-critical */
|
|
257
278
|
}
|
|
258
279
|
|
|
280
|
+
// Bound the session's checkpoint history when asked. Auto-checkpointing
|
|
281
|
+
// (one ref per mutating tool) passes a cap so a long agentic run can't
|
|
282
|
+
// accumulate unbounded refs — which also keeps nextId's per-create
|
|
283
|
+
// for-each-ref scan bounded. Best-effort: pruning never fails the
|
|
284
|
+
// checkpoint, and the dropped commit objects stay reachable via newer
|
|
285
|
+
// checkpoints' parent chain (only addressability by id is lost for the
|
|
286
|
+
// oldest entries). Manual `cc checkpoint create` omits the cap → unbounded.
|
|
287
|
+
if (Number.isFinite(opts.maxPerSession) && opts.maxPerSession > 0) {
|
|
288
|
+
try {
|
|
289
|
+
const rows = listRefs(root, session); // oldest-first (creatordate)
|
|
290
|
+
const excess = rows.length - opts.maxPerSession;
|
|
291
|
+
for (let i = 0; i < excess; i++) {
|
|
292
|
+
try {
|
|
293
|
+
git(["update-ref", "-d", rows[i].ref], { cwd: root });
|
|
294
|
+
} catch {
|
|
295
|
+
/* best-effort — a failed prune never affects the new checkpoint */
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
/* pruning is entirely best-effort */
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
259
303
|
return { id, ref, commit, tree, parent, label, session, createdAt, files };
|
|
260
304
|
} finally {
|
|
261
305
|
rmQuiet(tmpIndex);
|
|
@@ -557,4 +601,10 @@ export function clearCheckpoints(cwd = process.cwd(), opts = {}) {
|
|
|
557
601
|
}
|
|
558
602
|
|
|
559
603
|
// Exposed for unit tests / advanced callers.
|
|
560
|
-
export const _internals = {
|
|
604
|
+
export const _internals = {
|
|
605
|
+
git,
|
|
606
|
+
repoRoot,
|
|
607
|
+
sanitizeSession,
|
|
608
|
+
snapshotTree,
|
|
609
|
+
tempIndexPath,
|
|
610
|
+
};
|