chainlesschain 0.162.78 → 0.162.80
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/README.md +37 -1
- package/bin/chainlesschain.js +20 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BhKMd38k.js → AIOps-CwebRiRI.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BzhKY5C_.js → ActionButton-C7h2xsW3.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CATIz2Jc.js → Analytics-BRdOQzzK.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-eCx64YWg.js → AppLayout-D_i-Jbsu.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CGOHfCHj.js → Audit-AgCF_nLK.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Dyr6R0Ra.js → Backup-Be9up1Uo.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CVhBu7NZ.js → BaseInput-2j-7gyTU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DOCCKp2k.js → Chat-ZyYadHdk.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DcXCCnjN.js → ChatBubbleRenderer-CwnbmcAg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3yjnENud.js → Checkbox-Di0bejSO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-D06sn8kB.js → Codegen-BgEwqrVh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bjn5vFES.js → Col-FIduoerd.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BYHpQHmf.js → Community-Bf7olKXg.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-MKRmnUDQ.js → Compact-CsjIF6B3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CVtPe8dh.js → Compliance-CQchRaQh.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BRu5M3dv.js → Cowork-gJzDgX9E.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-D_7eU5Ut.js → Cron-CDjeagXb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BFyVp_e9.js → Crosschain-D4UE5bt0.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DRoG7638.js → DID-OwvsxTzD.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NfM_v-Oe.js → Dashboard-CbmOlZ1l.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CGN1Ksu0.js → Dropdown-B6tBUMev.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DhsY_z_a.js → EmailListRenderer-DnM2-O3n.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-vW_VsKUG.js → FamilyGuardDashboard-DPD9znJH.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D2ob_c7h.js → Federation-DjUj87Vr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cpem15Pr.js → FormItemContext-DyPgrKLf.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-P6XfmXwT.js → GenericCardRenderer-D3fmbO1W.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CfSeec1R.js → Git-IvwC_R2h.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-4ipIpqNl.js → Governance-CTVEvxpW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CiQY_P9Y.js → Inference-DQlp7Rf1.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B3Qem78R.js → KnowledgeGraph-Cg8pVh5j.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-De2zWVy6.js → Logs-D7kLXyaK.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CTNu4c1A.js → Marketplace-QOj6g-oT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Xx25EA2f.js → McpTools-PO9yrTD6.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-YzzCCrch.js → Memory-B1FwSFmt.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CJZY98f0.js → MobileBridge-CYiQSoKu.js} +3 -3
- package/src/assets/web-panel/assets/{MobileProjects-Dw7yl4KN.js → MobileProjects-BThUga4r.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D7TzGJhH.js → Mtc-B4pMzmr7.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-THyGhf0h.js → MtcAudit-D6-KGsvR.js} +6 -6
- package/src/assets/web-panel/assets/{Multisig-RVxuGPUR.js → Multisig-BlZigJMj.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DK7TCzKF.js → NLProgramming-Dhy92OLw.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BaOU0psj.js → Notes-DOSv2Lb0.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BMivJy85.js → NotificationSettings-FRWsHQfi.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BZOiY8Yw.js → OrderTableRenderer-xRSRWRZg.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-zVRtW1n_.js → Organization-DApwYY-B.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C0YLldFH.js → Overflow-DyJQHxIY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Xxgzghqp.js → P2P-NKTPd_Bn.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DH_LO13b.js → PdhVaultBrowser-DCgGt1BE.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CvAd1VBw.js → Permissions-BX9MCb2z.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CWQGgCAK.js → PersonalDataHub-Bcg4az84.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-BNAoh-Lb.js → Pipeline-DjsbfwbG.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CNO5pFq-.js → Privacy-kDWl06vo.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-WaVVDsm3.js → ProjectInit-yCe-Imkv.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-D1WfkuJ3.js → ProjectSettings-tVcxlGJ5.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BzjvJYMW.js → Projects-DGL4Za4o.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-IOOJ4_wy.js → Providers-CciGxskW.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ChHZqVZy.js → QuickAsk-It17rT4F.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CSiW6Qv9.js → Recommend-CixKdpBl.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-BQe0rkfF.js → Reputation-LB0_D2lx.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Dem0Wxxb.js → Row-BoiDd-zb.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-pBY5G41C.js → RssFeed-yA5FdTgh.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CtRepO6B.js → Search-xYldUFeJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-nrSlKpWq.js → Security-BtRMnkFm.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DeaDBASi.js → Services-CNKTgE2v.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Cz9R-Wjb.js → Skeleton-B9goiUo_.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B3U-XLH3.js → Skills-CUf2Z5Ge.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Bu46dIA_.js → Sla-TPga8c2I.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C9Z0V0pk.js → SpeechSettings-B8zrmLP6.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Ctj9KHHr.js → SyncSettings-DGyAbZ84.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-D4upQgR_.js → Tasks-DPeVrQNM.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-JHsPGU_c.js → Templates-CNLco6pc.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-uoaQL3fB.js → Tenant-CL9Eczo8.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-DGLvbp97.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BTicmSAV.js → TimelineRenderer-Cv0LxMwd.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bp3BUe2K.js → Tokens-DLhHgtcS.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CgoISw5d.js → Trigger-DzDaE-An.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC29awNT.js → Trust-CRh-fhYe.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CB1SB6Nc.js → UkeySign-_xBJ16UC.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D7vptDUg.js → VideoEditing-Z5m_edIa.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BWfjzF7p.js → Wallet-DgmchNit.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Dzz5OnPc.js → WebAuthn-BxuKxjuf.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CiDeVmsG.js → WorkflowEditor-luJ180aM.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DQbciNb5.js → chat-DzglnTps.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DcLbPJzb.js → colors-DelLNoxZ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CvYrR3rc.js → compact-item-BZaabUge.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BR4P7Rgm.js → createContext-DqTSTjmk.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CJLMPKYL.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-IQ88RNRJ.js → hasIn-CAeHUQj2.js} +1 -1
- package/src/assets/web-panel/assets/index-7CJalvEf.js +1 -0
- package/src/assets/web-panel/assets/{index-Bw0Dm_P6.js → index-7FxBHcH8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db5LFFCN.js → index-BAcpfWwI.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5W1vQHV.js → index-BAfdWN9t.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUTN1VlO.js → index-BR-DF81e.js} +3 -3
- package/src/assets/web-panel/assets/{index-BmPuR0aA.js → index-BTbN0V4A.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8OJdOc_.js → index-Bv2Tp7kz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhkZZXtI.js → index-BzLgm3Jm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzERBV9P.js → index-CBZPDGTg.js} +1 -1
- package/src/assets/web-panel/assets/{index-UbB2IcFR.js → index-CBtnHlYF.js} +1 -1
- package/src/assets/web-panel/assets/{index-eKd1n8pw.js → index-CDw1am9U.js} +1 -1
- package/src/assets/web-panel/assets/{index-JqOP7puJ.js → index-CI5cynRw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiMlLIZ-.js → index-CKZQVcH1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgP5aQmA.js → index-CV4FisuU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BH2RT15D.js → index-CaSLz8-6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgaCUxpi.js → index-Cj7oeTxA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdQBxvpt.js → index-Clq1OP4B.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl1TSbTE.js → index-CppTZ4SW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQXs-5db.js → index-D-QuIaEh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGwa8mnJ.js → index-D-lVDXUg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHIp5msb.js → index-DE4-6oHW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCO8yc1h.js → index-DM7xncnU.js} +1 -1
- package/src/assets/web-panel/assets/{index-b6FjzfoJ.js → index-DRt2lx0X.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bo7HAK6G.js → index-DSATjRyg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dpmnk2qv.js → index-DU9QWJO5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Mn8_ryOe.js → index-DXvcxNo5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dox9vEhP.js → index-DhMSUhbW.js} +1 -1
- package/src/assets/web-panel/assets/{index-bRT7u-51.js → index-Dk1R9vFq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHxJnExB.js → index-DrSuq6t6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPOupQSX.js → index-DtfTElxo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxY0ozve.js → index-KeadEGaZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvQpTO67.js → index-NZBXGj64.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiOZ_Whh.js → index-OGKhEFZZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeCWyiFl.js → index-RZ23Wlp8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVb6RI7f.js → index-WzAdJ0PX.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm74AosZ.js → index-Xo2WWPZ4.js} +1 -1
- package/src/assets/web-panel/assets/index-_hLbeSOT.js +1 -0
- package/src/assets/web-panel/assets/{index-CE2mqX8w.js → index-kJ30C4m8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu8931Yi.js → index-vLR-ssxc.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C0arzCLE.js → initDefaultProps-BiHvIjo1.js} +1 -1
- package/src/assets/web-panel/assets/{motion-C1K6JxwD.js → motion-COD0OBOe.js} +1 -1
- package/src/assets/web-panel/assets/{move-DREsRLHj.js → move-DJNLMhIj.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BtPS3EDq.js → omit-4qrDRhlN.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BPz6tHoT.js → pickAttrs-3jv8tAgW.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B0CR_CSI.js → placementArrow-N1UVUOH_.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ch2ojiNn.js → responsiveObserve-D67_gjCH.js} +1 -1
- package/src/assets/web-panel/assets/{slide-9qU9vOhj.js → slide-DiDh7_u4.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Cr4fICjV.js → statusUtils-Dzz3tSiz.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cor2-FwV.js → styleChecker-L-tgt7xx.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BINo_rNH.js → useFlexGapSupport-vAgElNal.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Dm1tDNYC.js → useFs-af0c_HYI.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-__JgBEkX.js → usePersonalDataHub-V9U2Mbny.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1hQKpRgP.js → vnode-C7zS_LLr.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C1EY9X2J.js → zoom-DdXBDemd.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/audit.js +4 -3
- package/src/commands/automation.js +6 -14
- package/src/commands/bi.js +10 -9
- package/src/commands/codegen.js +5 -13
- package/src/commands/dao.js +8 -6
- package/src/commands/dbevo.js +13 -14
- package/src/commands/economy.js +3 -2
- package/src/commands/evolution.js +3 -2
- package/src/commands/federation.js +4 -3
- package/src/commands/governance.js +9 -4
- package/src/commands/hardening.js +5 -4
- package/src/commands/incentive.js +6 -5
- package/src/commands/kg.js +17 -10
- package/src/commands/lowcode.js +23 -11
- package/src/commands/marketplace.js +4 -3
- package/src/commands/mcp.js +17 -5
- package/src/commands/ops.js +9 -4
- package/src/commands/recommend.js +7 -5
- package/src/commands/scim.js +3 -2
- package/src/commands/session.js +9 -6
- package/src/commands/social.js +4 -3
- package/src/commands/sync.js +3 -2
- package/src/commands/tenant.js +11 -6
- package/src/commands/zkp.js +8 -9
- package/src/gateways/ws/ws-agent-handler.js +12 -3
- package/src/gateways/ws/ws-server.js +6 -0
- package/src/harness/background-task-manager.js +44 -18
- package/src/harness/mcp-client.js +125 -46
- package/src/lib/chat-core.js +209 -107
- package/src/lib/claude-code-bridge.js +13 -1
- package/src/lib/dao-governance.js +3 -3
- package/src/lib/downloader.js +82 -25
- package/src/lib/headless-config-command.js +62 -0
- package/src/lib/json-schema-output.js +55 -11
- package/src/lib/mcp-oauth.js +110 -21
- package/src/lib/mcp-serve.js +70 -11
- package/src/lib/multisig-runtime.js +22 -3
- package/src/lib/parse-json-option.js +35 -0
- package/src/lib/parse-number-option.js +27 -0
- package/src/lib/runnable-provider.js +216 -0
- package/src/repl/agent-repl.js +76 -17
- package/src/repl/config-summary.js +66 -0
- package/src/runtime/agent-core.js +210 -37
- package/src/runtime/headless-runner.js +49 -1
- package/src/runtime/headless-stream.js +34 -0
- package/src/assets/web-panel/assets/Terminal-CWRWr8bq.js +0 -3
- package/src/assets/web-panel/assets/devWarning-CnV02N63.js +0 -1
- package/src/assets/web-panel/assets/index-DJ2gkaIH.js +0 -1
- package/src/assets/web-panel/assets/index-Dvm_-AOi.js +0 -1
|
@@ -33,6 +33,14 @@ export const ServerState = {
|
|
|
33
33
|
/** Transport kinds that carry a URL (no stdio process). */
|
|
34
34
|
const URL_TRANSPORTS = new Set(["http", "https", "sse", "ws", "wss"]);
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Default per-call timeout for HTTP MCP requests, mirroring the 30s stdio
|
|
38
|
+
* timeout so a hung/dead HTTP server can't block a request forever. Servers
|
|
39
|
+
* flagged `longRunning` (e.g. the IDE bridge, whose openDiff blocks on human
|
|
40
|
+
* review) are exempt; override per server with `config.requestTimeoutMs`.
|
|
41
|
+
*/
|
|
42
|
+
const HTTP_REQUEST_TIMEOUT_MS = 30000;
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Infer the transport kind for a server config. Falls back to "stdio".
|
|
38
46
|
* Prefers an explicit `transport` field; otherwise derives from URL scheme
|
|
@@ -218,13 +226,32 @@ export class MCPClient extends EventEmitter {
|
|
|
218
226
|
this.emit("server-error", { name, error: data.toString("utf8") });
|
|
219
227
|
});
|
|
220
228
|
|
|
229
|
+
// If the server process dies with requests in flight, reject them
|
|
230
|
+
// immediately with a clear error instead of letting each hang until its
|
|
231
|
+
// 30s timeout (fail-fast on a crashed/exited MCP server).
|
|
232
|
+
const failPending = (errMsg) => {
|
|
233
|
+
for (const [, pending] of entry._pending) {
|
|
234
|
+
if (pending.timeout) clearTimeout(pending.timeout);
|
|
235
|
+
try {
|
|
236
|
+
pending.reject(new Error(errMsg));
|
|
237
|
+
} catch {
|
|
238
|
+
// already settled — ignore
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
entry._pending.clear();
|
|
242
|
+
};
|
|
243
|
+
|
|
221
244
|
proc.on("close", (code) => {
|
|
222
245
|
entry.state = ServerState.DISCONNECTED;
|
|
246
|
+
failPending(
|
|
247
|
+
`MCP server "${name}" process exited (code ${code}) before responding`,
|
|
248
|
+
);
|
|
223
249
|
this.emit("server-disconnected", { name, code });
|
|
224
250
|
});
|
|
225
251
|
|
|
226
252
|
proc.on("error", (err) => {
|
|
227
253
|
entry.state = ServerState.ERROR;
|
|
254
|
+
failPending(`MCP server "${name}" process error: ${err.message}`);
|
|
228
255
|
this.emit("server-error", { name, error: err.message });
|
|
229
256
|
});
|
|
230
257
|
} else {
|
|
@@ -247,12 +274,24 @@ export class MCPClient extends EventEmitter {
|
|
|
247
274
|
entry.serverInfo = initResult?.serverInfo || {};
|
|
248
275
|
entry.capabilities = initResult?.capabilities || {};
|
|
249
276
|
|
|
250
|
-
// Fetch available tools
|
|
277
|
+
// Fetch available tools. Per MCP a server advertises a `tools` capability
|
|
278
|
+
// in its initialize response; if it does and tools/list then fails, that
|
|
279
|
+
// is a genuine fetch failure we must surface (Claude-Code 2.1.181 — show
|
|
280
|
+
// "Connected · tools fetch failed" rather than a misleading "Tools: 0").
|
|
281
|
+
// A server that did not advertise tools simply has none, so a failure
|
|
282
|
+
// there is expected and stays quiet.
|
|
283
|
+
entry.tools = [];
|
|
284
|
+
entry.toolsError = null;
|
|
285
|
+
const advertisesTools =
|
|
286
|
+
entry.capabilities && entry.capabilities.tools !== undefined;
|
|
251
287
|
try {
|
|
252
288
|
const toolsResult = await this._sendRequest(name, "tools/list", {});
|
|
253
289
|
entry.tools = toolsResult?.tools || [];
|
|
254
|
-
} catch {
|
|
255
|
-
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (advertisesTools) {
|
|
292
|
+
entry.toolsError = err?.message || String(err);
|
|
293
|
+
}
|
|
294
|
+
// else: server did not advertise tools — legitimately none.
|
|
256
295
|
}
|
|
257
296
|
|
|
258
297
|
// Fetch available resources
|
|
@@ -275,11 +314,16 @@ export class MCPClient extends EventEmitter {
|
|
|
275
314
|
// Server may not support prompts
|
|
276
315
|
}
|
|
277
316
|
|
|
278
|
-
this.emit("server-connected", {
|
|
317
|
+
this.emit("server-connected", {
|
|
318
|
+
name,
|
|
319
|
+
tools: entry.tools.length,
|
|
320
|
+
toolsError: entry.toolsError,
|
|
321
|
+
});
|
|
279
322
|
return {
|
|
280
323
|
name,
|
|
281
324
|
state: entry.state,
|
|
282
325
|
tools: entry.tools,
|
|
326
|
+
toolsError: entry.toolsError,
|
|
283
327
|
resources: entry.resources,
|
|
284
328
|
prompts: entry.prompts,
|
|
285
329
|
serverInfo: entry.serverInfo,
|
|
@@ -338,6 +382,7 @@ export class MCPClient extends EventEmitter {
|
|
|
338
382
|
name,
|
|
339
383
|
state: entry.state,
|
|
340
384
|
tools: entry.tools.length,
|
|
385
|
+
toolsError: entry.toolsError || null,
|
|
341
386
|
resources: entry.resources.length,
|
|
342
387
|
prompts: (entry.prompts || []).length,
|
|
343
388
|
serverInfo: entry.serverInfo || {},
|
|
@@ -599,53 +644,87 @@ export class MCPClient extends EventEmitter {
|
|
|
599
644
|
headers["Mcp-Session-Id"] = entry.httpSessionId;
|
|
600
645
|
}
|
|
601
646
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
typeof response.text === "function" ? await response.text() : "";
|
|
621
|
-
throw new Error(
|
|
622
|
-
`HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
|
|
623
|
-
);
|
|
647
|
+
// Per-call timeout (parity with the 30s stdio timeout) so a hung or dead
|
|
648
|
+
// HTTP MCP server can't block the request forever. Servers flagged
|
|
649
|
+
// longRunning — e.g. the IDE bridge, whose openDiff blocks on human review
|
|
650
|
+
// (see ideServerToMcpConfig) — are exempt. Override per server with
|
|
651
|
+
// config.requestTimeoutMs (0 disables).
|
|
652
|
+
const longRunning = Boolean(entry.config && entry.config.longRunning);
|
|
653
|
+
const timeoutMs = Number.isFinite(entry.config?.requestTimeoutMs)
|
|
654
|
+
? entry.config.requestTimeoutMs
|
|
655
|
+
: HTTP_REQUEST_TIMEOUT_MS;
|
|
656
|
+
let controller = null;
|
|
657
|
+
let timer = null;
|
|
658
|
+
if (
|
|
659
|
+
!longRunning &&
|
|
660
|
+
timeoutMs > 0 &&
|
|
661
|
+
typeof AbortController === "function"
|
|
662
|
+
) {
|
|
663
|
+
controller = new AbortController();
|
|
664
|
+
timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
624
665
|
}
|
|
625
666
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
667
|
+
try {
|
|
668
|
+
const response = await _deps.fetch(entry.httpUrl, {
|
|
669
|
+
method: "POST",
|
|
670
|
+
headers,
|
|
671
|
+
body,
|
|
672
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
673
|
+
});
|
|
629
674
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
);
|
|
640
|
-
}
|
|
675
|
+
// Capture session id (server may emit on initialize response only)
|
|
676
|
+
const sessionId =
|
|
677
|
+
(response.headers && typeof response.headers.get === "function"
|
|
678
|
+
? response.headers.get("mcp-session-id") ||
|
|
679
|
+
response.headers.get("Mcp-Session-Id")
|
|
680
|
+
: null) || null;
|
|
681
|
+
if (sessionId && !entry.httpSessionId) {
|
|
682
|
+
entry.httpSessionId = sessionId;
|
|
683
|
+
}
|
|
641
684
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
685
|
+
if (!response.ok) {
|
|
686
|
+
const text =
|
|
687
|
+
typeof response.text === "function" ? await response.text() : "";
|
|
688
|
+
throw new Error(
|
|
689
|
+
`HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const contentType = response.headers?.get
|
|
694
|
+
? String(response.headers.get("content-type") || "").toLowerCase()
|
|
695
|
+
: "";
|
|
696
|
+
|
|
697
|
+
let envelope;
|
|
698
|
+
if (contentType.includes("text/event-stream")) {
|
|
699
|
+
envelope = await _extractSseResponse(response, id);
|
|
700
|
+
} else {
|
|
701
|
+
envelope =
|
|
702
|
+
typeof response.json === "function"
|
|
703
|
+
? await response.json()
|
|
704
|
+
: JSON.parse(
|
|
705
|
+
typeof response.text === "function"
|
|
706
|
+
? await response.text()
|
|
707
|
+
: "",
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (!envelope || typeof envelope !== "object") {
|
|
712
|
+
throw new Error("Empty or invalid JSON-RPC response");
|
|
713
|
+
}
|
|
714
|
+
if (envelope.error) {
|
|
715
|
+
throw new Error(envelope.error.message || "Unknown error");
|
|
716
|
+
}
|
|
717
|
+
return envelope.result;
|
|
718
|
+
} catch (err) {
|
|
719
|
+
if (controller && controller.signal.aborted) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`,
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
throw err;
|
|
725
|
+
} finally {
|
|
726
|
+
if (timer) clearTimeout(timer);
|
|
647
727
|
}
|
|
648
|
-
return envelope.result;
|
|
649
728
|
}
|
|
650
729
|
|
|
651
730
|
/** Fire-and-forget JSON-RPC notification over HTTP. Errors swallowed. */
|
package/src/lib/chat-core.js
CHANGED
|
@@ -12,23 +12,65 @@
|
|
|
12
12
|
import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
|
|
13
13
|
import { appendTokenUsage } from "../harness/jsonl-session-store.js";
|
|
14
14
|
|
|
15
|
+
// A streaming chat call must not hang forever if the API accepts the connection
|
|
16
|
+
// but then goes silent (TCP up, no bytes). Abort the request if no data arrives
|
|
17
|
+
// for STREAM_STALL_MS — reset on every chunk, so it's a stall detector, not a
|
|
18
|
+
// total-time cap (long but healthy responses are unaffected). The default is
|
|
19
|
+
// generous (and env-overridable) so a slow local model's first token isn't cut
|
|
20
|
+
// off; raise CC_CHAT_STALL_MS if you run very large local models.
|
|
21
|
+
export const STREAM_STALL_MS = Number(process.env.CC_CHAT_STALL_MS) || 180000;
|
|
22
|
+
|
|
23
|
+
function makeStallGuard(stallMs = STREAM_STALL_MS) {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
let timer = null;
|
|
26
|
+
const bump = () => {
|
|
27
|
+
if (timer) clearTimeout(timer);
|
|
28
|
+
timer = setTimeout(() => controller.abort(), stallMs);
|
|
29
|
+
if (timer && typeof timer.unref === "function") timer.unref();
|
|
30
|
+
};
|
|
31
|
+
const stop = () => {
|
|
32
|
+
if (timer) clearTimeout(timer);
|
|
33
|
+
timer = null;
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
signal: controller.signal,
|
|
37
|
+
bump,
|
|
38
|
+
stop,
|
|
39
|
+
stalled: () => controller.signal.aborted,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
/**
|
|
16
44
|
* Stream a response from Ollama.
|
|
17
45
|
* If `onUsage` is provided, it's called with `{inputTokens, outputTokens}`
|
|
18
46
|
* derived from Ollama's terminal `prompt_eval_count` / `eval_count` fields.
|
|
19
47
|
*/
|
|
20
48
|
export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
const guard = makeStallGuard();
|
|
50
|
+
guard.bump();
|
|
51
|
+
let response;
|
|
52
|
+
try {
|
|
53
|
+
response = await fetch(`${baseUrl}/api/chat`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
model,
|
|
58
|
+
messages,
|
|
59
|
+
stream: true,
|
|
60
|
+
}),
|
|
61
|
+
signal: guard.signal,
|
|
62
|
+
});
|
|
63
|
+
} catch (e) {
|
|
64
|
+
guard.stop();
|
|
65
|
+
throw guard.stalled()
|
|
66
|
+
? new Error(
|
|
67
|
+
`Ollama request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
68
|
+
)
|
|
69
|
+
: e;
|
|
70
|
+
}
|
|
30
71
|
|
|
31
72
|
if (!response.ok) {
|
|
73
|
+
guard.stop();
|
|
32
74
|
throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
|
|
33
75
|
}
|
|
34
76
|
|
|
@@ -36,31 +78,42 @@ export async function streamOllama(messages, model, baseUrl, onToken, onUsage) {
|
|
|
36
78
|
const decoder = new TextDecoder();
|
|
37
79
|
let fullResponse = "";
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
81
|
+
try {
|
|
82
|
+
while (true) {
|
|
83
|
+
const { done, value } = await reader.read();
|
|
84
|
+
if (done) break;
|
|
85
|
+
guard.bump();
|
|
42
86
|
|
|
43
|
-
|
|
44
|
-
|
|
87
|
+
const text = decoder.decode(value, { stream: true });
|
|
88
|
+
const lines = text.split("\n").filter(Boolean);
|
|
45
89
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
if (json.done && onUsage) {
|
|
54
|
-
const inputTokens = Number(json.prompt_eval_count) || 0;
|
|
55
|
-
const outputTokens = Number(json.eval_count) || 0;
|
|
56
|
-
if (inputTokens || outputTokens) {
|
|
57
|
-
onUsage({ inputTokens, outputTokens });
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
try {
|
|
92
|
+
const json = JSON.parse(line);
|
|
93
|
+
if (json.message?.content) {
|
|
94
|
+
fullResponse += json.message.content;
|
|
95
|
+
onToken(json.message.content);
|
|
58
96
|
}
|
|
97
|
+
if (json.done && onUsage) {
|
|
98
|
+
const inputTokens = Number(json.prompt_eval_count) || 0;
|
|
99
|
+
const outputTokens = Number(json.eval_count) || 0;
|
|
100
|
+
if (inputTokens || outputTokens) {
|
|
101
|
+
onUsage({ inputTokens, outputTokens });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
// Partial JSON, skip
|
|
59
106
|
}
|
|
60
|
-
} catch {
|
|
61
|
-
// Partial JSON, skip
|
|
62
107
|
}
|
|
63
108
|
}
|
|
109
|
+
} catch (e) {
|
|
110
|
+
throw guard.stalled()
|
|
111
|
+
? new Error(
|
|
112
|
+
`Ollama stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
|
|
113
|
+
)
|
|
114
|
+
: e;
|
|
115
|
+
} finally {
|
|
116
|
+
guard.stop();
|
|
64
117
|
}
|
|
65
118
|
|
|
66
119
|
return fullResponse;
|
|
@@ -77,23 +130,37 @@ export async function streamOpenAI(
|
|
|
77
130
|
onToken,
|
|
78
131
|
onUsage,
|
|
79
132
|
) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
const guard = makeStallGuard();
|
|
134
|
+
guard.bump();
|
|
135
|
+
let response;
|
|
136
|
+
try {
|
|
137
|
+
response = await fetch(`${baseUrl}/chat/completions`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
Authorization: `Bearer ${apiKey}`,
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
model,
|
|
145
|
+
messages,
|
|
146
|
+
stream: true,
|
|
147
|
+
// Opt-in token usage in the terminal chunk (OpenAI-compatible).
|
|
148
|
+
// Servers that don't understand it simply ignore it.
|
|
149
|
+
stream_options: { include_usage: true },
|
|
150
|
+
}),
|
|
151
|
+
signal: guard.signal,
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
guard.stop();
|
|
155
|
+
throw guard.stalled()
|
|
156
|
+
? new Error(
|
|
157
|
+
`API request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
158
|
+
)
|
|
159
|
+
: e;
|
|
160
|
+
}
|
|
95
161
|
|
|
96
162
|
if (!response.ok) {
|
|
163
|
+
guard.stop();
|
|
97
164
|
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
98
165
|
}
|
|
99
166
|
|
|
@@ -101,36 +168,45 @@ export async function streamOpenAI(
|
|
|
101
168
|
const decoder = new TextDecoder();
|
|
102
169
|
let fullResponse = "";
|
|
103
170
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
171
|
+
try {
|
|
172
|
+
while (true) {
|
|
173
|
+
const { done, value } = await reader.read();
|
|
174
|
+
if (done) break;
|
|
175
|
+
guard.bump();
|
|
107
176
|
|
|
108
|
-
|
|
109
|
-
|
|
177
|
+
const text = decoder.decode(value, { stream: true });
|
|
178
|
+
const lines = text.split("\n").filter(Boolean);
|
|
110
179
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (line.startsWith("data: ")) {
|
|
182
|
+
const data = line.slice(6);
|
|
183
|
+
if (data === "[DONE]") continue;
|
|
184
|
+
try {
|
|
185
|
+
const json = JSON.parse(data);
|
|
186
|
+
const content = json.choices?.[0]?.delta?.content;
|
|
187
|
+
if (content) {
|
|
188
|
+
fullResponse += content;
|
|
189
|
+
onToken(content);
|
|
190
|
+
}
|
|
191
|
+
if (json.usage && onUsage) {
|
|
192
|
+
const inputTokens = Number(json.usage.prompt_tokens) || 0;
|
|
193
|
+
const outputTokens = Number(json.usage.completion_tokens) || 0;
|
|
194
|
+
if (inputTokens || outputTokens) {
|
|
195
|
+
onUsage({ inputTokens, outputTokens });
|
|
196
|
+
}
|
|
127
197
|
}
|
|
198
|
+
} catch {
|
|
199
|
+
// Partial data
|
|
128
200
|
}
|
|
129
|
-
} catch {
|
|
130
|
-
// Partial data
|
|
131
201
|
}
|
|
132
202
|
}
|
|
133
203
|
}
|
|
204
|
+
} catch (e) {
|
|
205
|
+
throw guard.stalled()
|
|
206
|
+
? new Error(`API stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`)
|
|
207
|
+
: e;
|
|
208
|
+
} finally {
|
|
209
|
+
guard.stop();
|
|
134
210
|
}
|
|
135
211
|
|
|
136
212
|
return fullResponse;
|
|
@@ -161,23 +237,37 @@ export async function streamAnthropic(
|
|
|
161
237
|
}
|
|
162
238
|
}
|
|
163
239
|
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
240
|
+
const guard = makeStallGuard();
|
|
241
|
+
guard.bump();
|
|
242
|
+
let response;
|
|
243
|
+
try {
|
|
244
|
+
response = await fetch(`${baseUrl}/messages`, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
"x-api-key": apiKey,
|
|
249
|
+
"anthropic-version": "2023-06-01",
|
|
250
|
+
},
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
model,
|
|
253
|
+
max_tokens: 4096,
|
|
254
|
+
stream: true,
|
|
255
|
+
...(system ? { system } : {}),
|
|
256
|
+
messages: convo,
|
|
257
|
+
}),
|
|
258
|
+
signal: guard.signal,
|
|
259
|
+
});
|
|
260
|
+
} catch (e) {
|
|
261
|
+
guard.stop();
|
|
262
|
+
throw guard.stalled()
|
|
263
|
+
? new Error(
|
|
264
|
+
`Anthropic request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
|
|
265
|
+
)
|
|
266
|
+
: e;
|
|
267
|
+
}
|
|
179
268
|
|
|
180
269
|
if (!response.ok) {
|
|
270
|
+
guard.stop();
|
|
181
271
|
throw new Error(
|
|
182
272
|
`Anthropic error: ${response.status} ${response.statusText}`,
|
|
183
273
|
);
|
|
@@ -190,36 +280,48 @@ export async function streamAnthropic(
|
|
|
190
280
|
let inputTokens = 0;
|
|
191
281
|
let outputTokens = 0;
|
|
192
282
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
283
|
+
try {
|
|
284
|
+
while (true) {
|
|
285
|
+
const { done, value } = await reader.read();
|
|
286
|
+
if (done) break;
|
|
287
|
+
guard.bump();
|
|
288
|
+
buf += decoder.decode(value, { stream: true });
|
|
289
|
+
const lines = buf.split("\n");
|
|
290
|
+
buf = lines.pop() || "";
|
|
291
|
+
for (const raw of lines) {
|
|
292
|
+
const line = raw.trim();
|
|
293
|
+
if (!line || !line.startsWith("data:")) continue;
|
|
294
|
+
const payload = line.slice(5).trim();
|
|
295
|
+
if (!payload) continue;
|
|
296
|
+
try {
|
|
297
|
+
const obj = JSON.parse(payload);
|
|
298
|
+
if (obj.type === "content_block_delta") {
|
|
299
|
+
const delta = obj.delta?.text;
|
|
300
|
+
if (delta) {
|
|
301
|
+
fullResponse += delta;
|
|
302
|
+
onToken(delta);
|
|
303
|
+
}
|
|
304
|
+
} else if (obj.type === "message_start") {
|
|
305
|
+
inputTokens =
|
|
306
|
+
Number(obj.message?.usage?.input_tokens) || inputTokens;
|
|
307
|
+
outputTokens =
|
|
308
|
+
Number(obj.message?.usage?.output_tokens) || outputTokens;
|
|
309
|
+
} else if (obj.type === "message_delta") {
|
|
310
|
+
outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
|
|
211
311
|
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
outputTokens =
|
|
215
|
-
Number(obj.message?.usage?.output_tokens) || outputTokens;
|
|
216
|
-
} else if (obj.type === "message_delta") {
|
|
217
|
-
outputTokens = Number(obj.usage?.output_tokens) || outputTokens;
|
|
312
|
+
} catch {
|
|
313
|
+
/* skip malformed */
|
|
218
314
|
}
|
|
219
|
-
} catch {
|
|
220
|
-
/* skip malformed */
|
|
221
315
|
}
|
|
222
316
|
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
throw guard.stalled()
|
|
319
|
+
? new Error(
|
|
320
|
+
`Anthropic stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
|
|
321
|
+
)
|
|
322
|
+
: e;
|
|
323
|
+
} finally {
|
|
324
|
+
guard.stop();
|
|
223
325
|
}
|
|
224
326
|
|
|
225
327
|
if (onUsage && (inputTokens || outputTokens)) {
|
|
@@ -97,6 +97,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
97
97
|
timeout = 300_000,
|
|
98
98
|
context = "",
|
|
99
99
|
allowedTools = null,
|
|
100
|
+
killGraceMs = 3000,
|
|
100
101
|
} = options;
|
|
101
102
|
|
|
102
103
|
const fullPrompt = context
|
|
@@ -128,10 +129,19 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
128
129
|
});
|
|
129
130
|
this._proc = proc;
|
|
130
131
|
|
|
132
|
+
// SIGKILL-escalation timer is hoisted so the close/error handlers can
|
|
133
|
+
// clear it — otherwise, when the process dies promptly from SIGTERM, this
|
|
134
|
+
// inner timer still fires a redundant SIGKILL on a dead pid AND holds the
|
|
135
|
+
// event loop open for the full grace period. unref() is a second guard so
|
|
136
|
+
// it never keeps the process alive on its own.
|
|
137
|
+
let killTimer = null;
|
|
131
138
|
const timer = setTimeout(() => {
|
|
132
139
|
timedOut = true;
|
|
133
140
|
proc.kill("SIGTERM");
|
|
134
|
-
setTimeout(() => proc.kill("SIGKILL"),
|
|
141
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), killGraceMs);
|
|
142
|
+
if (killTimer && typeof killTimer.unref === "function") {
|
|
143
|
+
killTimer.unref();
|
|
144
|
+
}
|
|
135
145
|
}, timeout);
|
|
136
146
|
|
|
137
147
|
proc.stdout.on("data", (data) => {
|
|
@@ -146,6 +156,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
146
156
|
|
|
147
157
|
proc.on("close", (code) => {
|
|
148
158
|
clearTimeout(timer);
|
|
159
|
+
if (killTimer) clearTimeout(killTimer);
|
|
149
160
|
this._proc = null;
|
|
150
161
|
const duration = Date.now() - startTime;
|
|
151
162
|
const rawOutput = outputChunks.join("");
|
|
@@ -178,6 +189,7 @@ export class ClaudeCodeAgent extends EventEmitter {
|
|
|
178
189
|
|
|
179
190
|
proc.on("error", (err) => {
|
|
180
191
|
clearTimeout(timer);
|
|
192
|
+
if (killTimer) clearTimeout(killTimer);
|
|
181
193
|
this._proc = null;
|
|
182
194
|
this.status = AGENT_STATUS.FAILED;
|
|
183
195
|
this.currentTask = null;
|