chainlesschain 0.162.125 → 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/assets/{AIOps-DcqNhZhA.js → AIOps-BrTOcqIJ.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BigtmPoq.js → ActionButton-na9cocmf.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-1J_X_HF4.js → Analytics-BtDygkpt.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-DlcDG_a_.js → AppLayout-D_t5CS18.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-Do-y8_3w.js → Audit-BYMZccCy.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-S2Df-50Y.js → Backup-D2qDfAdL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D5kgWJfk.js → BaseInput-kj456Ju9.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-aUcp3kN1.js → Chat-BY0A0ZLE.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-D1VvrOPQ.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DTg1U7Xs.js → Checkbox-DiBhtXsh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-YO9ZZ4Ky.js → Codegen-B1ER0L0j.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BHonE9fU.js → Col-CjLmza7D.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DA2AlEFh.js → Community-BNId05U7.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DULxLRNg.js → Compact-BuuMj7K1.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BZqfuuZL.js → Compliance-DUuDycaJ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CJe3vyMS.js → Cowork-BKHiF6uL.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CeV8AtRu.js → Cron-C7p-lHnC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CAg66zS5.js → Crosschain-BvSOx8a6.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Dtup5JZm.js → DID-D1k9jgHv.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DAR_8PNy.js → Dashboard-B9mCCnqY.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-K5bTaCYN.js → Dropdown-ge9UHSXF.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C890uErx.js → EmailListRenderer-TZO7ppXi.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B-Tj_8yM.js → FamilyGuardDashboard-DkhLJmzx.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C6WZ98ni.js → Federation-JLuA_8zp.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-D-LTfGWd.js → FormItemContext-C4w-rzNg.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C80DK4W7.js → GenericCardRenderer-Btns4sV9.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Cjvvv3zW.js → Git-BheXMjzC.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C0BND77H.js → Governance-D9rLlatH.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BL9kTtSu.js → Inference-DiklIMcd.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CnVTBmJf.js → KnowledgeGraph-C70EWL03.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CbCbg7K6.js → Logs-Zt_MLI10.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CG5On-fH.js → Marketplace-suQxES99.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-AYYDZZK7.js → McpTools-CclpCDpY.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tMWbMDQq.js → Memory-B2lHOUwF.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BAOsf4wB.js → MobileBridge-BMXKoDAD.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BT-2komQ.js → MobileProjects-a6mWVdBV.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BFwfC63n.js → Mtc-BFpM4Fkj.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DYG3CWdH.js → MtcAudit-DDQkxkbS.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-KJwd0yWT.js → Multisig-Crkd_zKQ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BmL5dNWm.js → NLProgramming-Dpk5An7B.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DmkaVaMc.js → Notes-BrFnOMNz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-tmVQszDZ.js → NotificationSettings-CM0iApFM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BtrR7anf.js → OrderTableRenderer-XXjeqkdz.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-pppktQyW.js → Organization-CHFW_38T.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-D3lBoQDA.js → Overflow-C-vl3EjV.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BgE2qGlZ.js → P2P-BT7Slavq.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DRUaULap.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BR8no2Xe.js → Permissions-DkhOAvMz.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-DabbyQEF.js → PersonalDataHub-Dl7dSOvV.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-n4sNpEz8.js → Pipeline-B3nVI4p6.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CGNp4n8M.js → Privacy-Dv1SXx1Q.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CAVA5VsX.js → ProjectInit-DrAhVi5p.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-GCgkfM7A.js → ProjectSettings-h-ggCYN_.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BYK17p52.js → Projects-CM91hYdr.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DpUT5W-d.js → Providers-rfkZel3N.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ZJxTb7vi.js → QuickAsk-XepZB7h_.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIzFRDk1.js → Recommend-DzQWsh1a.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-lQ_WDNZn.js → Reputation-CDeaal0j.js} +1 -1
- package/src/assets/web-panel/assets/{Row-1MEtrgtF.js → Row-BN4aKhFH.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-SSgcPPl4.js → RssFeed-INP6VYAA.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CcTMOGNY.js → Search-ChPbwatu.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BA1KMaDx.js → Security-C97ZCY8N.js} +3 -3
- package/src/assets/web-panel/assets/{Services-_aj7czZn.js → Services-Cbtl2Ajs.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BWgg_0Zr.js → Skeleton-B13sFxBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C2ZIziYM.js → Skills-vI9zSIKX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CYxp4axY.js → Sla-Cr3oBH39.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C7Csp1jV.js → SpeechSettings-BwemqPPV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-svGeswiw.js → SyncSettings-CLA78P-Z.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-ClISj6oK.js → Tasks-DBjQ_DOM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CmO-Fp7r.js → Templates-BkSLDkBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-a3Ac3lxz.js → Tenant-Dn0nSlib.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DyJIRh81.js → Terminal-C8f4boru.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-acXS5N5h.js → TimelineRenderer-DW_rMtJR.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DJW0KqV4.js → Tokens-BdffrKYV.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-0HeTi4r6.js → Trigger-CX4CUg-Q.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-aFym34eo.js → Trust-Cq9Bl7Od.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CYhszHJv.js → UkeySign-BQy18_VY.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BbJxPF9X.js → VideoEditing-h5hzK7Vw.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DDthEwiu.js → Wallet-BLotT3gw.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-DQ6McYPg.js → WebAuthn-8cCANYLE.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CnF8zRsi.js → WorkflowEditor-Db6NwtAv.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Dzkx2gTP.js → chat-BPk1LbpL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-35T51Oo5.js → colors-BnNal71p.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-TtzNrlu1.js → compact-item-W2VC0tcy.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Y1LkWrYb.js → createContext-DceVsgnZ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning--rwFDBhx.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BOYw1BgS.js → hasIn-BEq6EDcp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtlJWaSP.js → index-5gIBUFBn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLqC2sRT.js → index-APCHxOkM.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Wt5feN.js → index-B2dMhwZi.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqhwG1ox.js → index-B8cPjrEH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxWUEFKy.js → index-BDC5HsxZ.js} +1 -1
- package/src/assets/web-panel/assets/index-BPFuPGKi.js +1 -0
- package/src/assets/web-panel/assets/{index-NM9Oke2k.js → index-BUkDcIwJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BWgNn9As.js → index-BWcCyH6-.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVrUs6rf.js → index-BgZikQoh.js} +1 -1
- package/src/assets/web-panel/assets/index-Bwjus13Y.js +1 -0
- package/src/assets/web-panel/assets/{index-D6tG4B25.js → index-BzSzRWsw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjeUZxE5.js → index-C1K9CsGr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmkG479D.js → index-CBZVISK6.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw1iOGhM.js → index-CEJN-R2L.js} +1 -1
- package/src/assets/web-panel/assets/{index-G4ClSmJc.js → index-CG5dGC9E.js} +1 -1
- package/src/assets/web-panel/assets/{index-D47robkX.js → index-CMGLpstm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKRlWHUp.js → index-CWrGCndd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhMSOd_2.js → index-CbcvfpCV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dx0hIfC-.js → index-Ceqv4lI2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdyFg4FI.js → index-CsT6frT8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7aKAZzS.js → index-D8PQKsDT.js} +1 -1
- package/src/assets/web-panel/assets/{index-8jffdb6b.js → index-DGncdA-j.js} +1 -1
- package/src/assets/web-panel/assets/{index-XIuZV9by.js → index-DHanQHO0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dhfw-BXs.js → index-DaUBk28E.js} +1 -1
- package/src/assets/web-panel/assets/{index-hyqEKkBT.js → index-Di2J4b4V.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAizH273.js → index-DtuLvWZN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPGNc2tF.js → index-DvS1J8xc.js} +1 -1
- package/src/assets/web-panel/assets/{index-KgXrlfNF.js → index-DxePJdwP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFhlelzN.js → index-Et4XvL49.js} +1 -1
- package/src/assets/web-panel/assets/{index-zF77jnI2.js → index-GlFxgcj4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs82BHAj.js → index-HV119Oui.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dof7lWA_.js → index-InGW87pj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bs69SI96.js → index-P1-s8JR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4BgCBa3.js → index-SdABCNoi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSpFe6j5.js → index-WTAZbN-c.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7dCBw_M.js → index-_2I-KzOj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGPEaeB1.js → index-m1sVaWVU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ16GlOs.js → index-s37wwyeN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2od7Bgx.js → index-tyWABqp9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DD5y5msp.js → initDefaultProps-DzrCDb3j.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BuoutMia.js → motion-BQERVnE3.js} +1 -1
- package/src/assets/web-panel/assets/{move-DkpjqOXg.js → move-I9ACA5XJ.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BmGJD-v-.js → mtc-parser-Dd2Pw-tr.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DftUE0Sz.js → omit-DwzYRzWV.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDRb9FIu.js → pickAttrs-BSGULyIL.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DPgtxOOD.js → placementArrow-tSYYjAts.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BQFRzFeZ.js → responsiveObserve-Dy2lqikT.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DQ4lmUSz.js → slide-RaB4Uq0c.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C1TR4ZiQ.js → statusUtils-CBpC_wZg.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B-Suj978.js → styleChecker-BEKwPWt5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Q7bDZ3EI.js → useFlexGapSupport-C4CnYJH1.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DHjRLyQH.js → useFs-n24jutw5.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-lQOvCvCr.js → usePersonalDataHub-DMgS8F6G.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Ie0g4-p3.js → vnode-BSp2KYcj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CHP0YEoH.js → zoom-7UfQtTPh.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +41 -2
- package/src/commands/compliance.js +3 -1
- package/src/commands/review.js +47 -17
- package/src/commands/session.js +11 -2
- package/src/gateways/ws/message-dispatcher.js +145 -165
- package/src/gateways/ws/ws-session-gateway.js +39 -5
- package/src/harness/jsonl-session-store.js +39 -12
- package/src/harness/worktree-isolator.js +5 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/audit-logger.js +3 -1
- package/src/lib/chat-core.js +5 -2
- package/src/lib/chat-intent-service.js +68 -21
- package/src/lib/config-manager.js +25 -1
- package/src/lib/credential-guard.js +43 -1
- package/src/lib/git-integration.js +5 -0
- package/src/lib/interaction-adapter.js +32 -11
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/llm-config-defaults.js +37 -0
- package/src/lib/mcp-oauth.js +76 -10
- 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-denials.js +137 -0
- 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/settings-hooks.cjs +133 -0
- package/src/lib/settings-loader.cjs +16 -7
- package/src/lib/social-graph.js +3 -1
- package/src/lib/stream-retry.js +27 -0
- package/src/lib/turn-context.js +15 -5
- package/src/repl/agent-repl.js +81 -8
- package/src/runtime/__tests__/message-roles.test.js +36 -3
- package/src/runtime/agent-core.js +175 -50
- 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 +83 -2
- package/src/runtime/headless-stream.js +69 -3
- package/src/runtime/mcp-config.js +42 -1
- package/src/runtime/message-roles.js +14 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Pg7dIsiE.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-D26Bz5gS.js +0 -7
- package/src/assets/web-panel/assets/devWarning-D_lwLZ6O.js +0 -1
- package/src/assets/web-panel/assets/index-CEwuCM44.js +0 -1
- package/src/assets/web-panel/assets/index-DzzgU4IU.js +0 -1
|
@@ -123,6 +123,33 @@ export class WSSessionManager {
|
|
|
123
123
|
this.maxPendingPatches = Number.isFinite(options.maxPendingPatches)
|
|
124
124
|
? options.maxPendingPatches
|
|
125
125
|
: 50;
|
|
126
|
+
// The entry-count caps above bound how MANY patches are kept, but a SINGLE
|
|
127
|
+
// proposed patch (a client-reachable `patch-propose`) could still carry an
|
|
128
|
+
// unbounded number of files, each with unbounded before/after/diff content
|
|
129
|
+
// — all held in memory AND serialized to disk by _persistSessionState. One
|
|
130
|
+
// file with a multi-GB diff string is enough to OOM. Bound a single patch's
|
|
131
|
+
// payload: cap the file count and truncate each content field.
|
|
132
|
+
this.maxPatchFiles = Number.isFinite(options.maxPatchFiles)
|
|
133
|
+
? options.maxPatchFiles
|
|
134
|
+
: 200;
|
|
135
|
+
this.maxPatchContentChars = Number.isFinite(options.maxPatchContentChars)
|
|
136
|
+
? options.maxPatchContentChars
|
|
137
|
+
: 512 * 1024; // 512 KB per before/after/diff field
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Truncate a patch content field (before/after/diff) to maxPatchContentChars,
|
|
142
|
+
* appending a visible marker. Preserves the null-vs-string distinction (a null
|
|
143
|
+
* field stays null — it signals create/delete ops). Pure.
|
|
144
|
+
*/
|
|
145
|
+
_capPatchContent(value) {
|
|
146
|
+
if (value == null) return null;
|
|
147
|
+
const s = String(value);
|
|
148
|
+
if (s.length <= this.maxPatchContentChars) return s;
|
|
149
|
+
return (
|
|
150
|
+
s.slice(0, this.maxPatchContentChars) +
|
|
151
|
+
`\n…[truncated for storage: field was ${s.length} chars]`
|
|
152
|
+
);
|
|
126
153
|
}
|
|
127
154
|
|
|
128
155
|
_normalizeEnabledToolNames(enabledToolNames) {
|
|
@@ -854,8 +881,13 @@ export class WSSessionManager {
|
|
|
854
881
|
const session = this.sessions.get(sessionId);
|
|
855
882
|
if (!session) return null;
|
|
856
883
|
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
884
|
+
const rawFiles = Array.isArray(payload.files) ? payload.files : [];
|
|
885
|
+
if (rawFiles.length === 0) return null;
|
|
886
|
+
// Cap the per-patch file count; a proposal touching more than this is
|
|
887
|
+
// pathological for review. Excess files are dropped (count recorded below)
|
|
888
|
+
// so one message can't pin unbounded memory / disk.
|
|
889
|
+
const files = rawFiles.slice(0, this.maxPatchFiles);
|
|
890
|
+
const droppedFiles = rawFiles.length - files.length;
|
|
859
891
|
|
|
860
892
|
const patchId = `patch-${this._generateId()}`;
|
|
861
893
|
const now = new Date().toISOString();
|
|
@@ -866,9 +898,9 @@ export class WSSessionManager {
|
|
|
866
898
|
index,
|
|
867
899
|
path: file.path || `unknown-${index}`,
|
|
868
900
|
op,
|
|
869
|
-
before:
|
|
870
|
-
after:
|
|
871
|
-
diff:
|
|
901
|
+
before: this._capPatchContent(file.before),
|
|
902
|
+
after: this._capPatchContent(file.after),
|
|
903
|
+
diff: this._capPatchContent(file.diff),
|
|
872
904
|
stats,
|
|
873
905
|
};
|
|
874
906
|
});
|
|
@@ -895,6 +927,8 @@ export class WSSessionManager {
|
|
|
895
927
|
fileCount: normalizedFiles.length,
|
|
896
928
|
added: totalStats.added,
|
|
897
929
|
removed: totalStats.removed,
|
|
930
|
+
// Surfaced so the reviewer/UI knows the proposal was capped for storage.
|
|
931
|
+
...(droppedFiles > 0 ? { droppedFiles } : {}),
|
|
898
932
|
},
|
|
899
933
|
};
|
|
900
934
|
|
|
@@ -113,20 +113,36 @@ export function readEvents(sessionId) {
|
|
|
113
113
|
return events;
|
|
114
114
|
}
|
|
115
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
|
+
|
|
116
126
|
export function rebuildMessages(sessionId) {
|
|
117
127
|
const events = readEvents(sessionId);
|
|
118
128
|
const messages = [];
|
|
119
129
|
let lastCompactIndex = -1;
|
|
120
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.
|
|
121
134
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
122
|
-
|
|
135
|
+
const e = events[i];
|
|
136
|
+
if (e && e.type === "compact" && Array.isArray(e.data?.messages)) {
|
|
123
137
|
lastCompactIndex = i;
|
|
124
138
|
break;
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
|
|
128
|
-
if (lastCompactIndex >= 0
|
|
129
|
-
|
|
142
|
+
if (lastCompactIndex >= 0) {
|
|
143
|
+
for (const m of events[lastCompactIndex].data.messages) {
|
|
144
|
+
if (isReplayableMessage(m)) messages.push(m);
|
|
145
|
+
}
|
|
130
146
|
}
|
|
131
147
|
|
|
132
148
|
const startIndex = lastCompactIndex >= 0 ? lastCompactIndex + 1 : 0;
|
|
@@ -134,9 +150,11 @@ export function rebuildMessages(sessionId) {
|
|
|
134
150
|
for (let i = startIndex; i < events.length; i++) {
|
|
135
151
|
const event = events[i];
|
|
136
152
|
if (
|
|
137
|
-
event
|
|
138
|
-
event.type === "
|
|
139
|
-
|
|
153
|
+
event &&
|
|
154
|
+
(event.type === "user_message" ||
|
|
155
|
+
event.type === "assistant_message" ||
|
|
156
|
+
event.type === "system") &&
|
|
157
|
+
isReplayableMessage(event.data)
|
|
140
158
|
) {
|
|
141
159
|
messages.push(event.data);
|
|
142
160
|
}
|
|
@@ -145,6 +163,19 @@ export function rebuildMessages(sessionId) {
|
|
|
145
163
|
return messages;
|
|
146
164
|
}
|
|
147
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
|
+
|
|
148
179
|
export function listJsonlSessions(options = {}) {
|
|
149
180
|
const dir = getSessionsDir();
|
|
150
181
|
if (!existsSync(dir)) return [];
|
|
@@ -167,12 +198,8 @@ export function listJsonlSessions(options = {}) {
|
|
|
167
198
|
provider: startEvent?.data?.provider || "",
|
|
168
199
|
model: startEvent?.data?.model || "",
|
|
169
200
|
message_count: messageCount,
|
|
170
|
-
created_at: startEvent
|
|
171
|
-
|
|
172
|
-
: "",
|
|
173
|
-
updated_at: lastEvent
|
|
174
|
-
? new Date(lastEvent.timestamp).toISOString()
|
|
175
|
-
: "",
|
|
201
|
+
created_at: toIsoSafe(startEvent?.timestamp),
|
|
202
|
+
updated_at: toIsoSafe(lastEvent?.timestamp),
|
|
176
203
|
_lastTs: lastEvent?.timestamp || 0,
|
|
177
204
|
_eventCount: events.length,
|
|
178
205
|
};
|
|
@@ -550,6 +550,11 @@ function _hasUncommittedChanges(worktreePath) {
|
|
|
550
550
|
const output = execSync("git status --porcelain", {
|
|
551
551
|
cwd: worktreePath,
|
|
552
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,
|
|
553
558
|
});
|
|
554
559
|
return output.trim().length > 0;
|
|
555
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
|
}
|
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 },
|
|
@@ -452,16 +494,19 @@ ${
|
|
|
452
494
|
|
|
453
495
|
async function llmBasedClassify(userInput, context, llmOptions) {
|
|
454
496
|
const { systemPrompt, userPrompt } = buildFollowupPrompts(userInput, context);
|
|
455
|
-
const fullContent = await
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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),
|
|
465
510
|
);
|
|
466
511
|
const jsonText = extractJson(fullContent);
|
|
467
512
|
if (!jsonText) throw new Error("LLM response missing JSON");
|
|
@@ -540,5 +585,7 @@ export const _internal = {
|
|
|
540
585
|
buildFollowupPrompts,
|
|
541
586
|
extractJson,
|
|
542
587
|
ruleBasedClassify,
|
|
588
|
+
resolveIntentTimeout,
|
|
589
|
+
withIntentTimeout,
|
|
543
590
|
DEFAULT_INTENT_TIMEOUT_MS,
|
|
544
591
|
};
|
|
@@ -11,6 +11,16 @@ import { getConfigPath } from "./paths.js";
|
|
|
11
11
|
import { DEFAULT_CONFIG } from "../constants.js";
|
|
12
12
|
import { withFileLock } from "./with-file-lock.js";
|
|
13
13
|
|
|
14
|
+
// Warn at most once per config path per process. Injectable seam: the default
|
|
15
|
+
// stays silent under vitest so test output isn't polluted; tests override
|
|
16
|
+
// `_deps.warn` to assert it fired.
|
|
17
|
+
const _warnedConfigPaths = new Set();
|
|
18
|
+
export const _deps = {
|
|
19
|
+
warn: (msg) => {
|
|
20
|
+
if (!process.env.VITEST) process.stderr.write(msg);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
14
24
|
export function loadConfig() {
|
|
15
25
|
const configPath = getConfigPath();
|
|
16
26
|
if (!existsSync(configPath)) {
|
|
@@ -20,7 +30,21 @@ export function loadConfig() {
|
|
|
20
30
|
const raw = readFileSync(configPath, "utf-8");
|
|
21
31
|
const parsed = JSON.parse(raw);
|
|
22
32
|
return deepMerge(structuredClone(DEFAULT_CONFIG), parsed);
|
|
23
|
-
} catch {
|
|
33
|
+
} catch (err) {
|
|
34
|
+
// The file EXISTS but couldn't be read/parsed (a typo, trailing comma, or
|
|
35
|
+
// truncated write). Silently returning defaults drops the user's entire
|
|
36
|
+
// config — provider, model, baseUrl, API key — and falls back to the
|
|
37
|
+
// default provider, producing a baffling "configured X but it runs Y / says
|
|
38
|
+
// no API key". Warn once so the failure is visible; still fall back so cc
|
|
39
|
+
// keeps working.
|
|
40
|
+
if (!_warnedConfigPaths.has(configPath)) {
|
|
41
|
+
_warnedConfigPaths.add(configPath);
|
|
42
|
+
_deps.warn(
|
|
43
|
+
`⚠️ Could not read config at ${configPath} (${err.message}). ` +
|
|
44
|
+
`Using defaults — your saved settings (provider / model / API key) are being IGNORED. ` +
|
|
45
|
+
`Fix the JSON to restore them.\n`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
24
48
|
return { ...structuredClone(DEFAULT_CONFIG) };
|
|
25
49
|
}
|
|
26
50
|
}
|
|
@@ -149,7 +149,10 @@ export function credentialFileReasonResolved(targetPath, opts = {}) {
|
|
|
149
149
|
// ── secret ENV-VAR + command detection ──────────────────────────────────────
|
|
150
150
|
|
|
151
151
|
// Content-reader commands: when one of these is aimed at a credential file, the
|
|
152
|
-
// file's bytes land in the agent's tool output.
|
|
152
|
+
// file's bytes land in the agent's tool output. Includes text processors
|
|
153
|
+
// (awk/sed), encoders (base64), and field/byte slicers — all of which dump a
|
|
154
|
+
// file's content just like `cat`, so an agent told to "read the .env" can't
|
|
155
|
+
// route around the guard with `base64 .env` / `awk '{print}' .env`.
|
|
153
156
|
const READ_COMMANDS = new Set([
|
|
154
157
|
"cat",
|
|
155
158
|
"tac",
|
|
@@ -165,8 +168,31 @@ const READ_COMMANDS = new Set([
|
|
|
165
168
|
"xxd",
|
|
166
169
|
"od",
|
|
167
170
|
"strings",
|
|
171
|
+
"awk",
|
|
172
|
+
"gawk",
|
|
173
|
+
"mawk",
|
|
174
|
+
"sed",
|
|
175
|
+
"base64",
|
|
176
|
+
"base32",
|
|
177
|
+
"cut",
|
|
178
|
+
"rev",
|
|
179
|
+
"sort",
|
|
180
|
+
"uniq",
|
|
181
|
+
"tr",
|
|
182
|
+
"fold",
|
|
183
|
+
"expand",
|
|
184
|
+
"unexpand",
|
|
185
|
+
"column",
|
|
186
|
+
"paste",
|
|
187
|
+
"pr",
|
|
168
188
|
]);
|
|
169
189
|
|
|
190
|
+
// grep-family is a content-reader too, but its FIRST non-flag arg is the search
|
|
191
|
+
// PATTERN (which may itself look credential-ish, e.g. `grep "id_rsa" notes.txt`)
|
|
192
|
+
// — so it is handled separately, checking only the FILE args after the pattern,
|
|
193
|
+
// to keep the guard precise (no false positive on the pattern).
|
|
194
|
+
const GREP_COMMANDS = new Set(["grep", "egrep", "fgrep", "rg", "ag"]);
|
|
195
|
+
|
|
170
196
|
// Commands whose job is to print a value into output — the exfil surface for a
|
|
171
197
|
// secret env var (a tool that merely CONSUMES `$API_KEY` does not echo it).
|
|
172
198
|
const PRINT_COMMANDS = new Set([
|
|
@@ -281,6 +307,22 @@ export function commandReadsCredentials(command) {
|
|
|
281
307
|
}
|
|
282
308
|
}
|
|
283
309
|
|
|
310
|
+
// (b2) grep-family: skip the search pattern (first non-flag arg) and check
|
|
311
|
+
// only the file args, so `grep KEY .env` is caught but `grep "id_rsa"
|
|
312
|
+
// notes.txt` (searching FOR that text) is not a false positive.
|
|
313
|
+
if (GREP_COMMANDS.has(first)) {
|
|
314
|
+
for (const tok of nonFlagArgs.slice(1)) {
|
|
315
|
+
const r = credentialFileReason(tok);
|
|
316
|
+
if (r)
|
|
317
|
+
return {
|
|
318
|
+
reason: `reads ${r}`,
|
|
319
|
+
kind: "file",
|
|
320
|
+
target: tok,
|
|
321
|
+
segment: seg,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
284
326
|
// (c) printing / reading a secret-looking env var (via $VAR / %VAR% / env:).
|
|
285
327
|
if (PRINT_COMMANDS.has(first) || READ_COMMANDS.has(first)) {
|
|
286
328
|
const vars = referencedSecretVars(seg);
|
|
@@ -105,6 +105,11 @@ export function gitExec(args, cwd) {
|
|
|
105
105
|
return execSync(`git ${args}`, {
|
|
106
106
|
cwd,
|
|
107
107
|
encoding: "utf-8",
|
|
108
|
+
// git diff/log/worktree-list output on a large repo easily exceeds
|
|
109
|
+
// execSync's 1 MB default → ENOBUFS. Match gitExecArgs' 64 MB ceiling so
|
|
110
|
+
// a big diff/log doesn't spuriously fail (e.g. worktree-diff's --numstat
|
|
111
|
+
// pass, which isn't wrapped in a degrade-gracefully try/catch).
|
|
112
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
108
113
|
stdio: ["pipe", "pipe", "pipe"],
|
|
109
114
|
}).trim();
|
|
110
115
|
} catch (err) {
|
|
@@ -159,7 +159,15 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
159
159
|
},
|
|
160
160
|
options.timeoutMs || 5 * 60 * 1000,
|
|
161
161
|
);
|
|
162
|
-
|
|
162
|
+
// `kind` tags what KIND of response settles this request ("question" vs
|
|
163
|
+
// "host-tool"). Checked in _resolvePending so a peer's session-answer can
|
|
164
|
+
// never resolve a host-tool call with a plain string (or vice versa).
|
|
165
|
+
this._pending.set(requestId, {
|
|
166
|
+
resolve,
|
|
167
|
+
reject,
|
|
168
|
+
timeoutId,
|
|
169
|
+
kind: options.kind || null,
|
|
170
|
+
});
|
|
163
171
|
|
|
164
172
|
this._sendWs({
|
|
165
173
|
...message,
|
|
@@ -170,12 +178,15 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
170
178
|
}
|
|
171
179
|
|
|
172
180
|
_ask(questionType, question, extra = {}) {
|
|
173
|
-
return this._request(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
return this._request(
|
|
182
|
+
{
|
|
183
|
+
type: "question",
|
|
184
|
+
questionType,
|
|
185
|
+
question,
|
|
186
|
+
...extra,
|
|
187
|
+
},
|
|
188
|
+
{ kind: "question" },
|
|
189
|
+
);
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
async askInput(question, options = {}) {
|
|
@@ -200,7 +211,7 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
200
211
|
* Resolves the corresponding pending promise.
|
|
201
212
|
*/
|
|
202
213
|
resolveAnswer(requestId, answer) {
|
|
203
|
-
this._resolvePending(requestId, answer);
|
|
214
|
+
this._resolvePending(requestId, answer, "question");
|
|
204
215
|
}
|
|
205
216
|
|
|
206
217
|
async requestHostTool(toolName, args = {}, extra = {}) {
|
|
@@ -211,12 +222,12 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
211
222
|
args,
|
|
212
223
|
...extra,
|
|
213
224
|
},
|
|
214
|
-
{ timeoutMs: extra.timeoutMs || 60 * 1000 },
|
|
225
|
+
{ timeoutMs: extra.timeoutMs || 60 * 1000, kind: "host-tool" },
|
|
215
226
|
);
|
|
216
227
|
}
|
|
217
228
|
|
|
218
229
|
resolveHostTool(requestId, payload) {
|
|
219
|
-
this._resolvePending(requestId, payload);
|
|
230
|
+
this._resolvePending(requestId, payload, "host-tool");
|
|
220
231
|
}
|
|
221
232
|
|
|
222
233
|
rejectAllPending(reason = createAbortError("Interaction interrupted")) {
|
|
@@ -310,11 +321,21 @@ export class WebSocketInteractionAdapter extends InteractionAdapter {
|
|
|
310
321
|
}
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
_resolvePending(requestId, payload) {
|
|
324
|
+
_resolvePending(requestId, payload, expectedKind = null) {
|
|
314
325
|
const pending = this._pending.get(requestId);
|
|
315
326
|
if (!pending) {
|
|
316
327
|
return;
|
|
317
328
|
}
|
|
329
|
+
// Kind guard: a question-answer must not settle a host-tool request (or
|
|
330
|
+
// vice versa). On a mismatch, IGNORE the message — leave the request pending
|
|
331
|
+
// so its correct resolver (or its timeout) can still settle it. Prevents a
|
|
332
|
+
// peer from resolving a host-tool call with a plain question string, or a
|
|
333
|
+
// question with a structured tool payload (type confusion of the awaited
|
|
334
|
+
// value). Backward-compatible: callers passing no expectedKind, and
|
|
335
|
+
// pre-kind pending entries, behave exactly as before.
|
|
336
|
+
if (expectedKind && pending.kind && pending.kind !== expectedKind) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
318
339
|
|
|
319
340
|
this._pending.delete(requestId);
|
|
320
341
|
if (pending.timeoutId) {
|
|
@@ -17,6 +17,43 @@
|
|
|
17
17
|
* lesson as the --settings "opus"→404 vision trap.
|
|
18
18
|
* - No configured provider → unchanged (runner defaults apply: ollama).
|
|
19
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* Reconcile a MISLABELED llm config/options object. The `baseUrl` is the
|
|
22
|
+
* authoritative signal of which provider you actually reach, so when `provider`
|
|
23
|
+
* disagrees with the provider its `baseUrl` belongs to, the config is simply
|
|
24
|
+
* mislabeled — the recurring "provider:anthropic + volces baseUrl + model:haiku"
|
|
25
|
+
* corruption that makes runnable-provider relabel (and emit "provider 配置与
|
|
26
|
+
* baseUrl 不一致") on EVERY run because the on-disk config never gets fixed.
|
|
27
|
+
*
|
|
28
|
+
* Correct `provider` to match the endpoint, and if the model is foreign to the
|
|
29
|
+
* corrected provider, replace it with that provider's default. Pure: the input
|
|
30
|
+
* is never mutated — returns `{ llm, changed }` where `llm` is the original
|
|
31
|
+
* object (changed:false) or a corrected shallow copy (changed:true). Works for
|
|
32
|
+
* both a `config.llm` block and a resolved request-`options` (same shape).
|
|
33
|
+
*
|
|
34
|
+
* `inferProviderFromBaseUrl` returns null for unknown/custom hosts (proxies),
|
|
35
|
+
* so an intentionally-custom endpoint is left untouched.
|
|
36
|
+
*/
|
|
37
|
+
export async function reconcileConfigLlmProvider(cfgLlm = {}) {
|
|
38
|
+
if (!cfgLlm || !cfgLlm.provider || !cfgLlm.baseUrl) {
|
|
39
|
+
return { llm: cfgLlm, changed: false };
|
|
40
|
+
}
|
|
41
|
+
const { inferProviderFromBaseUrl, modelForeignToProvider } =
|
|
42
|
+
await import("./runnable-provider.js");
|
|
43
|
+
const inferred = inferProviderFromBaseUrl(cfgLlm.baseUrl);
|
|
44
|
+
if (!inferred || inferred === cfgLlm.provider) {
|
|
45
|
+
return { llm: cfgLlm, changed: false };
|
|
46
|
+
}
|
|
47
|
+
const corrected = { ...cfgLlm, provider: inferred };
|
|
48
|
+
if (modelForeignToProvider(inferred, cfgLlm.model)) {
|
|
49
|
+
const { selectModelForTask, TaskType } =
|
|
50
|
+
await import("./task-model-selector.js");
|
|
51
|
+
const def = selectModelForTask(inferred, TaskType.CHAT);
|
|
52
|
+
if (def) corrected.model = def;
|
|
53
|
+
}
|
|
54
|
+
return { llm: corrected, changed: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
20
57
|
export function applyConfigLlmDefaults(options = {}, cfgLlm = {}, opts = {}) {
|
|
21
58
|
// The IDE chat panel pins --provider/--model (read from config) but DROPS
|
|
22
59
|
// --base-url/--api-key. With an explicit --provider the block below bails, so
|