chainlesschain 0.162.124 → 0.162.126
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-BrTOcqIJ.js +1 -0
- package/src/assets/web-panel/assets/{ActionButton-COjZZKWG.js → ActionButton-na9cocmf.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-4ZDZE7lc.js → Analytics-BtDygkpt.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CYH5k2Rp.js → AppLayout-D_t5CS18.js} +5 -5
- package/src/assets/web-panel/assets/Audit-BYMZccCy.js +1 -0
- package/src/assets/web-panel/assets/{Backup-CiX61Qc2.js → Backup-D2qDfAdL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CDxIQokd.js → BaseInput-kj456Ju9.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CaKgdRab.js → Chat-BY0A0ZLE.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CjiWfu4s.js → Checkbox-DiBhtXsh.js} +1 -1
- package/src/assets/web-panel/assets/Codegen-B1ER0L0j.js +1 -0
- package/src/assets/web-panel/assets/{Col-ZKWREyNs.js → Col-CjLmza7D.js} +1 -1
- package/src/assets/web-panel/assets/Community-BNId05U7.js +1 -0
- package/src/assets/web-panel/assets/{Compact-DqknM98n.js → Compact-BuuMj7K1.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-DUuDycaJ.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-Dp29kHsC.js → Cowork-BKHiF6uL.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-zmEJAetz.js → Cron-C7p-lHnC.js} +2 -2
- package/src/assets/web-panel/assets/Crosschain-BvSOx8a6.js +1 -0
- package/src/assets/web-panel/assets/{DID-D_UFNzJ5.js → DID-D1k9jgHv.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-B9mCCnqY.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-CH7l3KCs.js → Dropdown-ge9UHSXF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bud2M3XX.js → EmailListRenderer-TZO7ppXi.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bd2_8uME.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
- package/src/assets/web-panel/assets/Federation-JLuA_8zp.js +1 -0
- package/src/assets/web-panel/assets/{FormItemContext-CpSH464Y.js → FormItemContext-C4w-rzNg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CANo8O-y.js → GenericCardRenderer-Btns4sV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-hvuZVoD3.js → Git-BheXMjzC.js} +2 -2
- package/src/assets/web-panel/assets/Governance-D9rLlatH.js +1 -0
- package/src/assets/web-panel/assets/Inference-DiklIMcd.js +1 -0
- package/src/assets/web-panel/assets/KnowledgeGraph-C70EWL03.js +1 -0
- package/src/assets/web-panel/assets/{Logs-DDkTnedu.js → Logs-Zt_MLI10.js} +2 -2
- package/src/assets/web-panel/assets/Marketplace-suQxES99.js +1 -0
- package/src/assets/web-panel/assets/{McpTools-Qx78XyOT.js → McpTools-CclpCDpY.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXYDO1oT.js → Memory-B2lHOUwF.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-BMXKoDAD.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-DEDbWNIk.js → MobileProjects-a6mWVdBV.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Dwa_vlR8.js → Mtc-BFpM4Fkj.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DAVkxhJv.js → MtcAudit-DDQkxkbS.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-C3upmgPN.js → Multisig-Crkd_zKQ.js} +3 -3
- package/src/assets/web-panel/assets/NLProgramming-Dpk5An7B.js +1 -0
- package/src/assets/web-panel/assets/{Notes-B1Oy3FbC.js → Notes-BrFnOMNz.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Bs4-Fc6b.js → NotificationSettings-CM0iApFM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CEy1pIeq.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-D4cJ1UNo.js → Organization-CHFW_38T.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-fdzvlF7x.js → Overflow-C-vl3EjV.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Y13PwXBA.js → P2P-BT7Slavq.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Ck1zCLe6.js → PdhVaultBrowser-DRUaULap.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CvEXP6LC.js → Permissions-DkhOAvMz.js} +2 -2
- package/src/assets/web-panel/assets/{PersonalDataHub-JeP7IWMW.js → PersonalDataHub-Dl7dSOvV.js} +3 -3
- package/src/assets/web-panel/assets/Pipeline-B3nVI4p6.js +1 -0
- package/src/assets/web-panel/assets/Privacy-Dv1SXx1Q.js +1 -0
- package/src/assets/web-panel/assets/{ProjectInit-DeWd9UcS.js → ProjectInit-DrAhVi5p.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DUpVuU9F.js → ProjectSettings-h-ggCYN_.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CM91hYdr.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DECW00ek.js → Providers-rfkZel3N.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Dl-pK9Io.js → QuickAsk-XepZB7h_.js} +1 -1
- package/src/assets/web-panel/assets/Recommend-DzQWsh1a.js +1 -0
- package/src/assets/web-panel/assets/Reputation-CDeaal0j.js +1 -0
- package/src/assets/web-panel/assets/Row-BN4aKhFH.js +1 -0
- package/src/assets/web-panel/assets/{RssFeed-iyQT5q9l.js → RssFeed-INP6VYAA.js} +3 -3
- package/src/assets/web-panel/assets/Search-ChPbwatu.js +1 -0
- package/src/assets/web-panel/assets/{Security-c4Jxf5Ih.js → Security-C97ZCY8N.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CRuisolj.js → Services-Cbtl2Ajs.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CCnzJkb-.js → Skeleton-B13sFxBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CZURCwZg.js → Skills-vI9zSIKX.js} +1 -1
- package/src/assets/web-panel/assets/Sla-Cr3oBH39.js +1 -0
- package/src/assets/web-panel/assets/SpeechSettings-BwemqPPV.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CJWVtd87.js → SyncSettings-CLA78P-Z.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-61lrQ_lS.js → Tasks-DBjQ_DOM.js} +1 -1
- package/src/assets/web-panel/assets/Templates-BkSLDkBs.js +1 -0
- package/src/assets/web-panel/assets/Tenant-Dn0nSlib.js +1 -0
- package/src/assets/web-panel/assets/{Terminal-CP15hcNS.js → Terminal-C8f4boru.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Sl5uXrkN.js → TimelineRenderer-DW_rMtJR.js} +1 -1
- package/src/assets/web-panel/assets/Tokens-BdffrKYV.js +1 -0
- package/src/assets/web-panel/assets/{Trigger-BLCQEOF2.js → Trigger-CX4CUg-Q.js} +1 -1
- package/src/assets/web-panel/assets/Trust-Cq9Bl7Od.js +1 -0
- package/src/assets/web-panel/assets/{UkeySign-Dwp0zXQ6.js → UkeySign-BQy18_VY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CIHA55Sx.js → VideoEditing-h5hzK7Vw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Hjajvnto.js → Wallet-BLotT3gw.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-DqSURUvI.js → WebAuthn-8cCANYLE.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B5bHRwUG.js → WorkflowEditor-Db6NwtAv.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DsheLKkG.js → chat-BPk1LbpL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CST2UkgL.js → colors-BnNal71p.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-yqEoy1L7.js → compact-item-W2VC0tcy.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Jwp78HFY.js → createContext-DceVsgnZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CVS5mFEP.js → hasIn-BEq6EDcp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLTUVd0V.js → index-5gIBUFBn.js} +2 -2
- package/src/assets/web-panel/assets/index-APCHxOkM.js +3 -0
- package/src/assets/web-panel/assets/index-B2dMhwZi.js +1 -0
- package/src/assets/web-panel/assets/{index-C0FZScMe.js → index-B8cPjrEH.js} +4 -4
- package/src/assets/web-panel/assets/{index-CjfN2CpJ.js → index-BDC5HsxZ.js} +1 -1
- package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
- package/src/assets/web-panel/assets/{index-SjxCCf5h.js → index-BUkDcIwJ.js} +2 -2
- package/src/assets/web-panel/assets/{index-Cjig5i3a.js → index-BWcCyH6-.js} +28 -26
- package/src/assets/web-panel/assets/{index-AnW25bEq.js → index-BgZikQoh.js} +1 -1
- package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
- package/src/assets/web-panel/assets/index-BzSzRWsw.js +55 -0
- package/src/assets/web-panel/assets/index-C1K9CsGr.js +1 -0
- package/src/assets/web-panel/assets/{index-DDAigsel.js → index-CBZVISK6.js} +2 -2
- package/src/assets/web-panel/assets/index-CEJN-R2L.js +1 -0
- package/src/assets/web-panel/assets/index-CG5dGC9E.js +1 -0
- package/src/assets/web-panel/assets/{index-DxaU_lza.js → index-CMGLpstm.js} +1 -1
- package/src/assets/web-panel/assets/index-CWrGCndd.js +1 -0
- package/src/assets/web-panel/assets/index-CbcvfpCV.js +1 -0
- package/src/assets/web-panel/assets/{index-De600Jjm.js → index-Ceqv4lI2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cy0dNzkp.js → index-CsT6frT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDAPnZUs.js → index-D8PQKsDT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crk4KWLW.js → index-DGncdA-j.js} +1 -1
- package/src/assets/web-panel/assets/{index-R-VGFpi6.js → index-DHanQHO0.js} +3 -3
- package/src/assets/web-panel/assets/index-DaUBk28E.js +12 -0
- package/src/assets/web-panel/assets/{index-BzetOasL.js → index-Di2J4b4V.js} +1 -1
- package/src/assets/web-panel/assets/index-DtuLvWZN.js +21 -0
- package/src/assets/web-panel/assets/{index-Dgyn8-wN.js → index-DvS1J8xc.js} +2 -2
- package/src/assets/web-panel/assets/{index-CtyEOGuj.js → index-DxePJdwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzsKK0tQ.js → index-Et4XvL49.js} +2 -2
- package/src/assets/web-panel/assets/{index-BuI27WSx.js → index-GlFxgcj4.js} +2 -2
- package/src/assets/web-panel/assets/index-HV119Oui.js +1 -0
- package/src/assets/web-panel/assets/index-InGW87pj.js +13 -0
- package/src/assets/web-panel/assets/{index-Dxljh9Fu.js → index-P1-s8JR7.js} +2 -2
- package/src/assets/web-panel/assets/{index-CSBPc-sI.js → index-SdABCNoi.js} +2 -2
- package/src/assets/web-panel/assets/{index-BCHAUdel.js → index-WTAZbN-c.js} +1 -1
- package/src/assets/web-panel/assets/index-_2I-KzOj.js +1 -0
- package/src/assets/web-panel/assets/index-m1sVaWVU.js +1 -0
- package/src/assets/web-panel/assets/index-s37wwyeN.js +1 -0
- package/src/assets/web-panel/assets/{index-CRBbVS43.js → index-tyWABqp9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ClAjrsQ-.js → initDefaultProps-DzrCDb3j.js} +1 -1
- package/src/assets/web-panel/assets/motion-BQERVnE3.js +11 -0
- package/src/assets/web-panel/assets/{move-DMelzIW-.js → move-I9ACA5XJ.js} +1 -1
- package/src/assets/web-panel/assets/mtc-parser-Dd2Pw-tr.js +1 -0
- package/src/assets/web-panel/assets/{omit-oxecfU3b.js → omit-DwzYRzWV.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DJ5F5M6x.js → pickAttrs-BSGULyIL.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DdbKsant.js → placementArrow-tSYYjAts.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BUuM5cmS.js → responsiveObserve-Dy2lqikT.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DSV0ccvR.js → slide-RaB4Uq0c.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B-6oLAXf.js → statusUtils-CBpC_wZg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DdCAHtsZ.js → styleChecker-BEKwPWt5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-ZORkcoZd.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BFp46LoP.js → useFs-n24jutw5.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-H7rxgbdW.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-eIq4Tk0J.js → vnode-BSp2KYcj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CTNrv8-H.js → zoom-7UfQtTPh.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +51 -2
- package/src/commands/compliance.js +3 -1
- package/src/commands/ide.js +60 -5
- package/src/commands/incentive.js +22 -22
- package/src/commands/mtc.js +5 -1
- package/src/commands/review.js +47 -17
- package/src/commands/session.js +11 -2
- package/src/gateways/ws/message-dispatcher.js +149 -166
- package/src/gateways/ws/ws-session-gateway.js +95 -9
- package/src/harness/background-task-manager.js +5 -1
- package/src/harness/jsonl-session-store.js +90 -12
- package/src/harness/mcp-client.js +62 -21
- package/src/harness/prompt-compressor.js +24 -5
- package/src/harness/worktree-isolator.js +10 -1
- package/src/lib/agent-core.js +2 -0
- package/src/lib/audit-logger.js +3 -1
- package/src/lib/autonomous-agent.js +29 -0
- package/src/lib/chat-core.js +5 -2
- package/src/lib/chat-intent-service.js +82 -23
- package/src/lib/checkpoint-store.js +31 -4
- package/src/lib/claude-code-bridge.js +48 -3
- package/src/lib/config-manager.js +25 -1
- package/src/lib/cost-budget.js +13 -2
- package/src/lib/credential-guard.js +81 -1
- package/src/lib/git-integration.js +16 -2
- package/src/lib/goal-store.js +11 -0
- package/src/lib/hook-manager.js +4 -0
- package/src/lib/interaction-adapter.js +32 -11
- package/src/lib/jetbrains-bridge.js +147 -0
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/llm-config-defaults.js +37 -0
- package/src/lib/llm-pricing.js +11 -4
- package/src/lib/llm-providers.js +11 -1
- package/src/lib/mcp-oauth.js +88 -10
- package/src/lib/mcp-serve.js +29 -0
- package/src/lib/orchestrator.js +38 -2
- package/src/lib/permission-engine.js +4 -1
- package/src/lib/pipeline-orchestrator.js +20 -2
- package/src/lib/project-instructions.js +13 -0
- package/src/lib/repl-bang-memorize.js +9 -1
- package/src/lib/repl-denials.js +137 -0
- package/src/lib/runnable-provider.js +7 -1
- package/src/lib/safe-json.js +22 -0
- package/src/lib/sandbox-v2.js +7 -6
- package/src/lib/search-command.js +65 -0
- package/src/lib/session-insights.js +13 -6
- package/src/lib/session-usage.js +21 -3
- package/src/lib/settings-hooks.cjs +133 -0
- package/src/lib/settings-loader.cjs +16 -7
- package/src/lib/social-graph.js +3 -1
- package/src/lib/status-line.cjs +4 -1
- package/src/lib/stream-retry.js +27 -0
- package/src/lib/sub-agent-context.js +9 -0
- package/src/lib/turn-context.js +15 -5
- package/src/repl/agent-repl.js +110 -8
- package/src/runtime/__tests__/message-roles.test.js +36 -3
- package/src/runtime/agent-core.js +179 -52
- package/src/runtime/coding-agent-contract-shared.cjs +9 -2
- package/src/runtime/coding-agent-shell-policy.cjs +23 -2
- package/src/runtime/file-ref-expander.js +51 -7
- package/src/runtime/headless-runner.js +96 -2
- package/src/runtime/headless-stream.js +78 -3
- package/src/runtime/mcp-config.js +114 -4
- package/src/runtime/message-roles.js +14 -1
- package/src/runtime/pipe-safety.js +51 -0
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/assets/web-panel/assets/AIOps-BsDJlD_l.js +0 -1
- package/src/assets/web-panel/assets/Audit-BKlFbLdl.js +0 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DgvpwU5o.js +0 -1
- package/src/assets/web-panel/assets/Codegen-tKWGzajw.js +0 -1
- package/src/assets/web-panel/assets/Community-CmLuPBUU.js +0 -1
- package/src/assets/web-panel/assets/Compliance-PZw0aBLI.js +0 -1
- package/src/assets/web-panel/assets/Crosschain-Sb-uM7Ty.js +0 -1
- package/src/assets/web-panel/assets/Dashboard-Btag698v.js +0 -3
- package/src/assets/web-panel/assets/Federation-1jhstF5P.js +0 -1
- package/src/assets/web-panel/assets/Governance-BQrWksKt.js +0 -1
- package/src/assets/web-panel/assets/Inference-jEBTp12v.js +0 -1
- package/src/assets/web-panel/assets/KnowledgeGraph-tDiV2taf.js +0 -1
- package/src/assets/web-panel/assets/Marketplace-9Yi32avt.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-MfUfkHA0.js +0 -1
- package/src/assets/web-panel/assets/NLProgramming-DO3CZ0_z.js +0 -1
- package/src/assets/web-panel/assets/Pipeline-BZ7wRzG1.js +0 -1
- package/src/assets/web-panel/assets/Privacy-BJ6qj9Hv.js +0 -1
- package/src/assets/web-panel/assets/Projects-y1WbI337.js +0 -1
- package/src/assets/web-panel/assets/Recommend-Bju04wTd.js +0 -1
- package/src/assets/web-panel/assets/Reputation-BUPa3nEk.js +0 -1
- package/src/assets/web-panel/assets/Row-WYqqaq_5.js +0 -1
- package/src/assets/web-panel/assets/Search-CrfwJF0R.js +0 -1
- package/src/assets/web-panel/assets/Sla-CrMzaEi2.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-jTDOM5eY.js +0 -1
- package/src/assets/web-panel/assets/Templates-Cg-iF2g1.js +0 -1
- package/src/assets/web-panel/assets/Tenant-DrDv1FFc.js +0 -1
- package/src/assets/web-panel/assets/Tokens-yRIZTVsR.js +0 -1
- package/src/assets/web-panel/assets/Trust-D5xq8z6s.js +0 -1
- package/src/assets/web-panel/assets/community-parser-Cuyslaku.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C8to6gQk.js +0 -1
- package/src/assets/web-panel/assets/index-BeblNJDH.js +0 -1
- package/src/assets/web-panel/assets/index-C130LLPq.js +0 -1
- package/src/assets/web-panel/assets/index-C8rq85gu.js +0 -1
- package/src/assets/web-panel/assets/index-CIE2n8k_.js +0 -1
- package/src/assets/web-panel/assets/index-CTd3lDxp.js +0 -13
- package/src/assets/web-panel/assets/index-CUWj5L2s.js +0 -1
- package/src/assets/web-panel/assets/index-ChRL6rgA.js +0 -3
- package/src/assets/web-panel/assets/index-CrlRa054.js +0 -1
- package/src/assets/web-panel/assets/index-Cu6Ql64n.js +0 -1
- package/src/assets/web-panel/assets/index-Cx_t5rWw.js +0 -12
- package/src/assets/web-panel/assets/index-DZfnYE6e.js +0 -1
- package/src/assets/web-panel/assets/index-D_1b7uh6.js +0 -55
- package/src/assets/web-panel/assets/index-D_e9gwfn.js +0 -1
- package/src/assets/web-panel/assets/index-DbxAlpPC.js +0 -21
- package/src/assets/web-panel/assets/index-DiK4vSZa.js +0 -1
- package/src/assets/web-panel/assets/index-DpYn5v2k.js +0 -1
- package/src/assets/web-panel/assets/index-diWkx2Wq.js +0 -1
- package/src/assets/web-panel/assets/motion-BalXv_60.js +0 -11
- package/src/assets/web-panel/assets/mtc-parser-CEQffxds.js +0 -1
|
@@ -21,7 +21,32 @@ function getSessionsDir() {
|
|
|
21
21
|
return dir;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A session id must be a single safe path segment. Ids are generated as
|
|
26
|
+
* `session-<ts>-<hex>`, but also arrive from CLI args (`cc agent --resume <id>`,
|
|
27
|
+
* `cc insights <id>`, `cc session show <id>`), so an id like `../../etc/x` would
|
|
28
|
+
* otherwise let sessionPath() read / append / delete a .jsonl OUTSIDE the
|
|
29
|
+
* sessions dir. Reject any separator or `..` (mirrors goal-store's
|
|
30
|
+
* isUnsafeGoalId / FileUploadService.isUnsafeSegment).
|
|
31
|
+
*/
|
|
32
|
+
export function isUnsafeSessionId(id) {
|
|
33
|
+
return (
|
|
34
|
+
id == null ||
|
|
35
|
+
id === "" ||
|
|
36
|
+
typeof id !== "string" ||
|
|
37
|
+
id.includes("/") ||
|
|
38
|
+
id.includes("\\") ||
|
|
39
|
+
id.includes("..")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
export function sessionPath(sessionId) {
|
|
44
|
+
// Fail closed for path building: every write/delete goes through here, so a
|
|
45
|
+
// traversal id can never escape the sessions dir. Reads guard separately and
|
|
46
|
+
// degrade to not-found instead of throwing.
|
|
47
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
48
|
+
throw new Error(`unsafe session id: ${String(sessionId).slice(0, 60)}`);
|
|
49
|
+
}
|
|
25
50
|
return join(getSessionsDir(), `${sessionId}.jsonl`);
|
|
26
51
|
}
|
|
27
52
|
|
|
@@ -69,6 +94,7 @@ export function appendCompactEvent(sessionId, stats) {
|
|
|
69
94
|
}
|
|
70
95
|
|
|
71
96
|
export function readEvents(sessionId) {
|
|
97
|
+
if (isUnsafeSessionId(sessionId)) return []; // traversal id → treat as empty
|
|
72
98
|
const filePath = sessionPath(sessionId);
|
|
73
99
|
if (!existsSync(filePath)) return [];
|
|
74
100
|
|
|
@@ -87,20 +113,36 @@ export function readEvents(sessionId) {
|
|
|
87
113
|
return events;
|
|
88
114
|
}
|
|
89
115
|
|
|
116
|
+
/**
|
|
117
|
+
* A replayable chat message must be a `{ role, content }` object — guard
|
|
118
|
+
* against a corrupt / partially-written / hand-edited event whose `data` is
|
|
119
|
+
* missing, null, or not a message (it would otherwise inject `undefined` into
|
|
120
|
+
* the resumed history and break the next LLM request).
|
|
121
|
+
*/
|
|
122
|
+
function isReplayableMessage(m) {
|
|
123
|
+
return Boolean(m) && typeof m === "object" && typeof m.role === "string";
|
|
124
|
+
}
|
|
125
|
+
|
|
90
126
|
export function rebuildMessages(sessionId) {
|
|
91
127
|
const events = readEvents(sessionId);
|
|
92
128
|
const messages = [];
|
|
93
129
|
let lastCompactIndex = -1;
|
|
94
130
|
|
|
131
|
+
// A `compact` event carries the pre-compaction snapshot in data.messages. A
|
|
132
|
+
// malformed compact line (type present but data null / messages not an array)
|
|
133
|
+
// must not crash the resume — skip it and look further back.
|
|
95
134
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
96
|
-
|
|
135
|
+
const e = events[i];
|
|
136
|
+
if (e && e.type === "compact" && Array.isArray(e.data?.messages)) {
|
|
97
137
|
lastCompactIndex = i;
|
|
98
138
|
break;
|
|
99
139
|
}
|
|
100
140
|
}
|
|
101
141
|
|
|
102
|
-
if (lastCompactIndex >= 0
|
|
103
|
-
|
|
142
|
+
if (lastCompactIndex >= 0) {
|
|
143
|
+
for (const m of events[lastCompactIndex].data.messages) {
|
|
144
|
+
if (isReplayableMessage(m)) messages.push(m);
|
|
145
|
+
}
|
|
104
146
|
}
|
|
105
147
|
|
|
106
148
|
const startIndex = lastCompactIndex >= 0 ? lastCompactIndex + 1 : 0;
|
|
@@ -108,9 +150,11 @@ export function rebuildMessages(sessionId) {
|
|
|
108
150
|
for (let i = startIndex; i < events.length; i++) {
|
|
109
151
|
const event = events[i];
|
|
110
152
|
if (
|
|
111
|
-
event
|
|
112
|
-
event.type === "
|
|
113
|
-
|
|
153
|
+
event &&
|
|
154
|
+
(event.type === "user_message" ||
|
|
155
|
+
event.type === "assistant_message" ||
|
|
156
|
+
event.type === "system") &&
|
|
157
|
+
isReplayableMessage(event.data)
|
|
114
158
|
) {
|
|
115
159
|
messages.push(event.data);
|
|
116
160
|
}
|
|
@@ -119,6 +163,19 @@ export function rebuildMessages(sessionId) {
|
|
|
119
163
|
return messages;
|
|
120
164
|
}
|
|
121
165
|
|
|
166
|
+
/** ISO string for a numeric ms timestamp, or "" when missing / non-finite /
|
|
167
|
+
* invalid — `new Date(undefined).toISOString()` (and `new Date("garbage")`)
|
|
168
|
+
* throw "Invalid time value", and one corrupt event must not crash a whole
|
|
169
|
+
* `cc session list` / `cc session search`. Exported so command-layer readers of
|
|
170
|
+
* the same (hand-editable) JSONL share the guard. */
|
|
171
|
+
export function toIsoSafe(ts) {
|
|
172
|
+
if (ts == null) return ""; // null/undefined → "" (Number(null) is 0 = epoch)
|
|
173
|
+
const n = Number(ts);
|
|
174
|
+
if (!Number.isFinite(n)) return "";
|
|
175
|
+
const d = new Date(n);
|
|
176
|
+
return Number.isNaN(d.getTime()) ? "" : d.toISOString();
|
|
177
|
+
}
|
|
178
|
+
|
|
122
179
|
export function listJsonlSessions(options = {}) {
|
|
123
180
|
const dir = getSessionsDir();
|
|
124
181
|
if (!existsSync(dir)) return [];
|
|
@@ -141,12 +198,8 @@ export function listJsonlSessions(options = {}) {
|
|
|
141
198
|
provider: startEvent?.data?.provider || "",
|
|
142
199
|
model: startEvent?.data?.model || "",
|
|
143
200
|
message_count: messageCount,
|
|
144
|
-
created_at: startEvent
|
|
145
|
-
|
|
146
|
-
: "",
|
|
147
|
-
updated_at: lastEvent
|
|
148
|
-
? new Date(lastEvent.timestamp).toISOString()
|
|
149
|
-
: "",
|
|
201
|
+
created_at: toIsoSafe(startEvent?.timestamp),
|
|
202
|
+
updated_at: toIsoSafe(lastEvent?.timestamp),
|
|
150
203
|
_lastTs: lastEvent?.timestamp || 0,
|
|
151
204
|
_eventCount: events.length,
|
|
152
205
|
};
|
|
@@ -184,6 +237,7 @@ export function forkSession(sourceId) {
|
|
|
184
237
|
}
|
|
185
238
|
|
|
186
239
|
export function sessionExists(sessionId) {
|
|
240
|
+
if (isUnsafeSessionId(sessionId)) return false; // never resolve a traversal id
|
|
187
241
|
return existsSync(sessionPath(sessionId));
|
|
188
242
|
}
|
|
189
243
|
|
|
@@ -266,6 +320,15 @@ export function migrateLegacySessionFile(filePath, options = {}) {
|
|
|
266
320
|
}
|
|
267
321
|
|
|
268
322
|
export function validateJsonlSession(sessionId) {
|
|
323
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
324
|
+
return {
|
|
325
|
+
sessionId,
|
|
326
|
+
valid: false,
|
|
327
|
+
reason: "invalid session id",
|
|
328
|
+
malformedLines: 0,
|
|
329
|
+
eventCount: 0,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
269
332
|
const filePath = sessionPath(sessionId);
|
|
270
333
|
if (!existsSync(filePath)) {
|
|
271
334
|
return {
|
|
@@ -335,6 +398,21 @@ function performLegacySessionMigration(sourcePath, options) {
|
|
|
335
398
|
const legacy = normalizeLegacySession(parsed, basename(sourcePath, ".json"));
|
|
336
399
|
const sessionId = legacy.id;
|
|
337
400
|
|
|
401
|
+
// A legacy file carries its OWN `id` (payload.id), so a crafted file could
|
|
402
|
+
// name a traversal target like "../../evil". sessionPath() throws on write
|
|
403
|
+
// (the backstop), but fail-fast HERE with a clear reason so the migration
|
|
404
|
+
// doesn't burn retry attempts on a deterministic error.
|
|
405
|
+
if (isUnsafeSessionId(sessionId)) {
|
|
406
|
+
return {
|
|
407
|
+
file: sourcePath,
|
|
408
|
+
sessionId,
|
|
409
|
+
migrated: false,
|
|
410
|
+
failed: true,
|
|
411
|
+
dryRun: Boolean(options.dryRun),
|
|
412
|
+
reason: "unsafe session id in legacy file",
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
338
416
|
if (!options.force && sessionExists(sessionId)) {
|
|
339
417
|
return {
|
|
340
418
|
file: sourcePath,
|
|
@@ -814,19 +814,18 @@ export class MCPClient extends EventEmitter {
|
|
|
814
814
|
const contentType = response.headers?.get
|
|
815
815
|
? String(response.headers.get("content-type") || "").toLowerCase()
|
|
816
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;
|
|
817
822
|
|
|
818
823
|
let envelope;
|
|
819
824
|
if (contentType.includes("text/event-stream")) {
|
|
820
|
-
envelope = await _extractSseResponse(response, id);
|
|
825
|
+
envelope = await _extractSseResponse(response, id, cap);
|
|
821
826
|
} else {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
? await response.json()
|
|
825
|
-
: JSON.parse(
|
|
826
|
-
typeof response.text === "function"
|
|
827
|
-
? await response.text()
|
|
828
|
-
: "",
|
|
829
|
-
);
|
|
827
|
+
const text = await _readBodyCapped(response, cap);
|
|
828
|
+
envelope = text ? JSON.parse(text) : null;
|
|
830
829
|
}
|
|
831
830
|
|
|
832
831
|
if (!envelope || typeof envelope !== "object") {
|
|
@@ -957,28 +956,70 @@ export class MCPClient extends EventEmitter {
|
|
|
957
956
|
}
|
|
958
957
|
|
|
959
958
|
/**
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
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.
|
|
963
967
|
*/
|
|
964
|
-
async function
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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") {
|
|
969
982
|
const reader = response.body.getReader();
|
|
970
983
|
const decoder = new TextDecoder("utf-8");
|
|
971
984
|
const chunks = [];
|
|
972
|
-
|
|
985
|
+
let total = 0;
|
|
986
|
+
for (;;) {
|
|
973
987
|
const { value, done } = await reader.read();
|
|
974
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
|
+
}
|
|
975
1000
|
chunks.push(decoder.decode(value, { stream: true }));
|
|
976
1001
|
}
|
|
977
1002
|
chunks.push(decoder.decode());
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
|
|
1003
|
+
return chunks.join("");
|
|
1004
|
+
}
|
|
1005
|
+
if (typeof response.text !== "function") {
|
|
1006
|
+
throw new Error("HTTP response is not readable");
|
|
981
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`);
|
|
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);
|
|
982
1023
|
|
|
983
1024
|
// Split into events on blank line, parse each event's concatenated `data:` lines.
|
|
984
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
|
|
|
@@ -462,7 +462,11 @@ export function applyWorktreeAutomationCandidate(
|
|
|
462
462
|
const commitMessage =
|
|
463
463
|
options.commitMessage ||
|
|
464
464
|
`Resolve ${filePath} via ${candidate.label || candidateId}`;
|
|
465
|
-
|
|
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);
|
|
466
470
|
|
|
467
471
|
const diff = diffWorktree(repoDir, branchName, { baseBranch });
|
|
468
472
|
const filesChanged = diff.summary?.filesChanged || 0;
|
|
@@ -546,6 +550,11 @@ function _hasUncommittedChanges(worktreePath) {
|
|
|
546
550
|
const output = execSync("git status --porcelain", {
|
|
547
551
|
cwd: worktreePath,
|
|
548
552
|
encoding: "utf-8",
|
|
553
|
+
// A worktree with many dirty files produces >1 MB of porcelain output and
|
|
554
|
+
// would ENOBUFS on the default cap — the catch below then reports "clean"
|
|
555
|
+
// (fail-OPEN), which could let a caller discard a worktree full of
|
|
556
|
+
// uncommitted work. Give it the same 64 MB headroom as gitExec.
|
|
557
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
549
558
|
});
|
|
550
559
|
return output.trim().length > 0;
|
|
551
560
|
} catch (_e) {
|
package/src/lib/agent-core.js
CHANGED
package/src/lib/audit-logger.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
|
+
import { safeJsonParse } from "./safe-json.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Event types for audit logging.
|
|
@@ -232,7 +233,8 @@ export function queryLogs(db, filters = {}) {
|
|
|
232
233
|
const rows = db.prepare(sql).all(...params);
|
|
233
234
|
return rows.map((r) => ({
|
|
234
235
|
...r,
|
|
235
|
-
|
|
236
|
+
// One corrupt details cell must not throw out of the whole query.
|
|
237
|
+
details: safeJsonParse(r.details, null),
|
|
236
238
|
success: r.success === 1,
|
|
237
239
|
}));
|
|
238
240
|
}
|
|
@@ -48,6 +48,12 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
48
48
|
this._initialized = false;
|
|
49
49
|
this._maxIterations = 20;
|
|
50
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;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -58,12 +64,14 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
58
64
|
toolExecutor,
|
|
59
65
|
hookManager,
|
|
60
66
|
maxIterations,
|
|
67
|
+
maxGoals,
|
|
61
68
|
iterationBudget,
|
|
62
69
|
} = {}) {
|
|
63
70
|
this._llmChat = llmChat || null;
|
|
64
71
|
this._toolExecutor = toolExecutor || null;
|
|
65
72
|
this._hookManager = hookManager || null;
|
|
66
73
|
if (maxIterations) this._maxIterations = maxIterations;
|
|
74
|
+
if (Number.isFinite(maxGoals) && maxGoals > 0) this._maxGoals = maxGoals;
|
|
67
75
|
this._iterationBudget = iterationBudget || null; // shared budget from caller
|
|
68
76
|
this._initialized = true;
|
|
69
77
|
}
|
|
@@ -96,6 +104,7 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
96
104
|
};
|
|
97
105
|
|
|
98
106
|
this._goals.set(goalId, goal);
|
|
107
|
+
this._pruneGoals();
|
|
99
108
|
this.emit("goal:submitted", { goalId, description });
|
|
100
109
|
|
|
101
110
|
// Start the ReAct loop asynchronously
|
|
@@ -108,6 +117,26 @@ export class CLIAutonomousAgent extends EventEmitter {
|
|
|
108
117
|
return { goalId };
|
|
109
118
|
}
|
|
110
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
|
+
|
|
111
140
|
/**
|
|
112
141
|
* Pause a running goal.
|
|
113
142
|
*/
|
package/src/lib/chat-core.js
CHANGED
|
@@ -13,8 +13,8 @@ import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
|
13
13
|
import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
14
14
|
import {
|
|
15
15
|
isRetryableStreamError,
|
|
16
|
-
STREAM_RETRY_MAX,
|
|
17
16
|
STREAM_RETRY_BASE_MS,
|
|
17
|
+
resolveStreamRetryMax,
|
|
18
18
|
} from "./stream-retry.js";
|
|
19
19
|
|
|
20
20
|
// A streaming chat call must not hang forever if the API accepts the connection
|
|
@@ -513,6 +513,9 @@ export async function* chatStream(messages, options) {
|
|
|
513
513
|
// any token still terminates the generator cleanly.
|
|
514
514
|
const streamPromise = (async () => {
|
|
515
515
|
try {
|
|
516
|
+
// Budget honors CC_MAX_RETRIES / CLAUDE_CODE_MAX_RETRIES (capped 15),
|
|
517
|
+
// same as the agent path.
|
|
518
|
+
const maxRetries = resolveStreamRetryMax();
|
|
516
519
|
let attempt = 0;
|
|
517
520
|
for (;;) {
|
|
518
521
|
try {
|
|
@@ -524,7 +527,7 @@ export async function* chatStream(messages, options) {
|
|
|
524
527
|
// zero-output retry safety (cc agent parity). Stall aborts (180s) and
|
|
525
528
|
// HTTP/auth errors are not retryable and surface immediately.
|
|
526
529
|
if (
|
|
527
|
-
attempt >=
|
|
530
|
+
attempt >= maxRetries ||
|
|
528
531
|
tokensEmitted > 0 ||
|
|
529
532
|
!isRetryableStreamError(err)
|
|
530
533
|
) {
|
|
@@ -22,6 +22,40 @@ import { firstBalancedJson } from "./json-schema-output.js";
|
|
|
22
22
|
|
|
23
23
|
const DEFAULT_INTENT_TIMEOUT_MS = 15000;
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the per-call intent-classification budget. `llmOptions.intentTimeoutMs`
|
|
27
|
+
* overrides the default; an explicit 0 disables the cap; NaN/negative/absent →
|
|
28
|
+
* default.
|
|
29
|
+
*/
|
|
30
|
+
function resolveIntentTimeout(llmOptions) {
|
|
31
|
+
const raw = llmOptions?.intentTimeoutMs;
|
|
32
|
+
if (raw === 0) return 0; // explicit disable
|
|
33
|
+
const n = Number(raw);
|
|
34
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_INTENT_TIMEOUT_MS;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Bound an LLM call to the intent budget. chat-core has its OWN stall guard, but
|
|
39
|
+
* it only fires on SILENCE (no bytes for 180s) — a slow trickling stream never
|
|
40
|
+
* trips it, so without this the declared 15s intent budget was never enforced
|
|
41
|
+
* and classification could block for minutes. Racing the deadline makes it fail
|
|
42
|
+
* fast into the caller's graceful rule/verbatim fallback. `ms <= 0` disables.
|
|
43
|
+
* The losing background request is harmless (chat-core releases its own socket);
|
|
44
|
+
* the timer is unref'd so it never holds the process open.
|
|
45
|
+
*/
|
|
46
|
+
function withIntentTimeout(promise, ms) {
|
|
47
|
+
if (!(Number(ms) > 0)) return promise;
|
|
48
|
+
let timer;
|
|
49
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
50
|
+
timer = setTimeout(
|
|
51
|
+
() => reject(new Error(`intent LLM call timed out after ${ms}ms`)),
|
|
52
|
+
Number(ms),
|
|
53
|
+
);
|
|
54
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
55
|
+
});
|
|
56
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
/**
|
|
26
60
|
* Build the V5 understandIntent system + user prompt pair.
|
|
27
61
|
*
|
|
@@ -123,17 +157,20 @@ export async function understandIntent({
|
|
|
123
157
|
);
|
|
124
158
|
|
|
125
159
|
try {
|
|
126
|
-
const fullContent = await
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
const fullContent = await withIntentTimeout(
|
|
161
|
+
chatWithStreaming(
|
|
162
|
+
[
|
|
163
|
+
{ role: "system", content: systemPrompt },
|
|
164
|
+
{ role: "user", content: userPrompt },
|
|
165
|
+
],
|
|
166
|
+
{
|
|
167
|
+
...llmOptions,
|
|
168
|
+
// Lower temperature → more deterministic JSON output.
|
|
169
|
+
temperature: 0.3,
|
|
170
|
+
maxTokens: 500,
|
|
171
|
+
},
|
|
172
|
+
),
|
|
173
|
+
resolveIntentTimeout(llmOptions),
|
|
137
174
|
);
|
|
138
175
|
|
|
139
176
|
const jsonText = extractJson(fullContent);
|
|
@@ -204,6 +241,11 @@ export async function* understandIntentStream({
|
|
|
204
241
|
);
|
|
205
242
|
let buffer = "";
|
|
206
243
|
try {
|
|
244
|
+
// Incremental token yielding makes a per-call deadline awkward to apply
|
|
245
|
+
// here; this streaming variant is instead bounded by chat-core's own
|
|
246
|
+
// silence-based stall guard (CC_CHAT_STALL_MS, default 180s). The awaited
|
|
247
|
+
// understandIntent / classifyFollowupIntent paths enforce the tighter
|
|
248
|
+
// intent budget (resolveIntentTimeout) directly.
|
|
207
249
|
for await (const event of chatStream(
|
|
208
250
|
[
|
|
209
251
|
{ role: "system", content: systemPrompt },
|
|
@@ -319,7 +361,12 @@ const FOLLOWUP_RULES = {
|
|
|
319
361
|
/(颜色|字体|大小|位置|标题).*(是|用|为)/,
|
|
320
362
|
/^.{1,20}(应该|具体)(是|为)/,
|
|
321
363
|
/^数据(来源|是)/,
|
|
322
|
-
|
|
364
|
+
// De-catastrophized: the old `/.*(用|采用).*(字体…)/` had a leading greedy
|
|
365
|
+
// `.*` overlapping `(用)`, causing exponential backtracking (a 2 KB input
|
|
366
|
+
// of "用" took ~3 s). `.test()` already searches anywhere, so the leading
|
|
367
|
+
// `.*` is redundant; a lazy middle drops it to quadratic, and the input
|
|
368
|
+
// cap in ruleBasedClassify bounds that.
|
|
369
|
+
/(用|采用)[\s\S]*?(字体|颜色|大小)/,
|
|
323
370
|
],
|
|
324
371
|
},
|
|
325
372
|
CANCEL_TASK: {
|
|
@@ -334,8 +381,15 @@ const FOLLOWUP_RULES = {
|
|
|
334
381
|
},
|
|
335
382
|
};
|
|
336
383
|
|
|
384
|
+
// A follow-up intent (continue / modify / clarify / cancel) is determinable
|
|
385
|
+
// from the start of the message; cap the text the heuristic regexes see so a
|
|
386
|
+
// huge paste can't drive their (quadratic) backtracking into a DoS. The full
|
|
387
|
+
// input is still forwarded to the LLM path / downstream — only this local
|
|
388
|
+
// classification is truncated.
|
|
389
|
+
const MAX_CLASSIFY_INPUT = 2000;
|
|
390
|
+
|
|
337
391
|
function ruleBasedClassify(userInput) {
|
|
338
|
-
const input = (userInput || "").trim();
|
|
392
|
+
const input = (userInput || "").trim().slice(0, MAX_CLASSIFY_INPUT);
|
|
339
393
|
|
|
340
394
|
if (input.length === 0) {
|
|
341
395
|
return {
|
|
@@ -440,16 +494,19 @@ ${
|
|
|
440
494
|
|
|
441
495
|
async function llmBasedClassify(userInput, context, llmOptions) {
|
|
442
496
|
const { systemPrompt, userPrompt } = buildFollowupPrompts(userInput, context);
|
|
443
|
-
const fullContent = await
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
497
|
+
const fullContent = await withIntentTimeout(
|
|
498
|
+
chatWithStreaming(
|
|
499
|
+
[
|
|
500
|
+
{ role: "system", content: systemPrompt },
|
|
501
|
+
{ role: "user", content: userPrompt },
|
|
502
|
+
],
|
|
503
|
+
{
|
|
504
|
+
...llmOptions,
|
|
505
|
+
temperature: 0.1,
|
|
506
|
+
maxTokens: 300,
|
|
507
|
+
},
|
|
508
|
+
),
|
|
509
|
+
resolveIntentTimeout(llmOptions),
|
|
453
510
|
);
|
|
454
511
|
const jsonText = extractJson(fullContent);
|
|
455
512
|
if (!jsonText) throw new Error("LLM response missing JSON");
|
|
@@ -528,5 +585,7 @@ export const _internal = {
|
|
|
528
585
|
buildFollowupPrompts,
|
|
529
586
|
extractJson,
|
|
530
587
|
ruleBasedClassify,
|
|
588
|
+
resolveIntentTimeout,
|
|
589
|
+
withIntentTimeout,
|
|
531
590
|
DEFAULT_INTENT_TIMEOUT_MS,
|
|
532
591
|
};
|