chainlesschain 0.166.4 → 0.166.5
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 +32 -0
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-HcOh0ZbH.js → AIOps-DhCWWRmS.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DWjDqBhd.js → ActionButton-BGbZcxSE.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-C6FS-y3W.js → Analytics-CUJNYrmU.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DCN5cldF.js → AppLayout-ParuJRmc.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Crd-KE8P.js → Artifacts-BMESCkte.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BexuTQZc.js → Audit-B614pUXR.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dydkh-b5.js → BackgroundAgents-BKkkvkQl.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BYq21wMP.js → Backup-CoFiC_kv.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CmNZP6yM.js → BaseInput-Cu4OHz1W.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-J6bD0zCl.js → Chat-mxb_SR22.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-MEoaygnL.js → ChatBubbleRenderer-CsqO-fiV.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Ce-9vNmK.js → Checkbox-BJpZz-5L.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-zf7-Y-Eh.js → Codegen-ng7fYMi8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D-TkY0FD.js → Col-D751NOsU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-l300NA-1.js → Community-BPVTHvTC.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-D-9Q_hxc.js → Compact-CY42ARxJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DAJ8lLiQ.js → Compliance-Ec3zabvu.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-xmzZ967S.js → Cowork-Do0oOlj8.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CeGqO5W4.js → Cron-BXu6LGra.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DXN0AE-4.js → Crosschain-Dg1CsWLA.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CtIKASPr.js → DID-D1B8O6C9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DqRwT8NX.js → Dashboard-DJFnfIVk.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-ddIVljO1.js → Dropdown-Be6KCHb7.js} +1 -1
- package/src/assets/web-panel/assets/EmailListRenderer-9L4EdmcR.js +1 -0
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-PnlAUGcG.js → FamilyGuardDashboard-CWt6WUoP.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BlmLuQrL.js → Federation-Ba7bRkZh.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-dDovBj1Y.js → FormItemContext-CNa8GTAM.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DTvGshFr.js → GenericCardRenderer-DGHg2fio.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DuFR2xgA.js → Git-DJAgN16e.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-9EaNcJuq.js → Governance-DqIapin6.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B-iYWH_r.js → Inference-B00_KwDW.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BGBaKiLX.js → KnowledgeGraph-D1_ZCehX.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-G3wmXzfa.js → Logs-22jGgUmw.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-hk0YwjTR.js → MarkdownRenderer-DOIQQUTp.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BNARBeAv.js → Marketplace-ClIyNRL6.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DST2cSSK.js → McpTools-BhPsaaJr.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-piwDLsVH.js → Memory-BXNDHgEH.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D-W8zHdm.js → MobileBridge-BeSEujLm.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CwaYHlVP.js → MobileProjects-DKJjWZ6t.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-C2dxnN7Q.js → Mtc-CsX4lo0h.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BwPZsV3N.js → MtcAudit-CdGiWPEN.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CUt8w2al.js → Multisig-C1aJmYGd.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DwxErx27.js → NLProgramming-ClOwZKGW.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B-mXAZ84.js → Notes-xqZJkp5X.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Bd1fBAzd.js → NotificationSettings-D_8hg6E5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CCInnavw.js → OrderTableRenderer-BNMgCFHY.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DTTONYZT.js → Organization-DQjjPQ9u.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-H6lCZ1rE.js → Overflow-D0g51CZb.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DyBZzeYW.js → P2P-BqDCDnon.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-BDMyV-DM.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-B5nSQwsr.js → Permissions-D9Mk4hzw.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CACaQtlY.js → PersonalDataHub-Cksd0Ft-.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-5y-DR_Bw.js → Pipeline-ALaa1U0w.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BMSA3Ehd.js → Privacy-M-mmVDtr.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-VCOj1dTa.js → ProjectInit-Bn39Z5S7.js} +2 -2
- package/src/assets/web-panel/assets/ProjectSettings-6Xmsc-zo.js +2 -0
- package/src/assets/web-panel/assets/Projects-C2KI2sH1.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DZ1FNKUR.js → Providers-FLNmdOWT.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-6DKc9UBX.js → QrScannerModal-C5PNS5N7.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Dyd0Ckrk.js → QuickAsk-CHSVdvQA.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CYW4-GJH.js → Recommend-BRwvF4m9.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DLrfPy4F.js → RemoteSession-DeAfmrU2.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DfqPo1FI.js → Reputation-Be_YQzm9.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CR89H2x5.js → Row-Bv3cbL2A.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B9QY7R-F.js → RssFeed-DsTYAdrh.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CUDnzu4w.js → Search-2gch6_kT.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CJ0CtM_Z.js → Security-pkS2eKdn.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CyrQ6F5U.js → Services-D1ns1NlD.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DcwnKatQ.js → Skeleton-B-9nVmze.js} +1 -1
- package/src/assets/web-panel/assets/Skills-V7TqlXWR.js +1 -0
- package/src/assets/web-panel/assets/{Sla-CWh-awh7.js → Sla-CI9DZg-s.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BaUMNzx7.js → SpeechSettings-CH0mQsl3.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D1Ijwwxl.js → SyncSettings-BHeEVZyN.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DFqjHi3T.js → Tasks-DOld05sg.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Ct-9_ki3.js → Templates--GvWvJEB.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-zBkgyilh.js → Tenant-L-FgZD-r.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-VVO2Hjfi.js → Terminal-DMNfs5hK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CX9pIkx3.js → TimelineRenderer-DFzInCAj.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DHaD6e_m.js → Tokens-Bnh73Gam.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CFq9j3WV.js → Trigger-BO-fMwq8.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DuF7gIYX.js → Trust-CX-HXcRl.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DbuidXJX.js → UkeySign-C8AOFGVL.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B8IA9_qE.js → VideoEditing-B_bJ6BO2.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DdCglSIK.js → Wallet-Ccd3u1t4.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CeteRsA8.js → WebAuthn-DVSL5e6o.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DJhIbG__.js → WorkflowEditor-DqFq6xGO.js} +1 -1
- package/src/assets/web-panel/assets/{chat-srOi_Wef.js → chat-s5SEbhS8.js} +1 -1
- package/src/assets/web-panel/assets/{colors-HpVHsNGK.js → colors-CRFmX1eW.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DsLhrqWe.js → compact-item-DCofr-Ii.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-aOasIUuE.js → createContext-duu0_qGr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-0bFBr86K.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5BVMimS.js → hasIn-B0KKSHGi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV4y5rUj.js → index-4aqbfxrp.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChGUPEwS.js → index-B24nGxlB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCp70IlO.js → index-BCnLafAV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQXp_uNd.js → index-BDGNTPr6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiXsis7m.js → index-BERyKHMq.js} +1 -1
- package/src/assets/web-panel/assets/index-BJCitHZ6.js +1 -0
- package/src/assets/web-panel/assets/{index-V0gpVTbS.js → index-BM48QU3P.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4tPtHU0.js → index-BMaM-Yex.js} +1 -1
- package/src/assets/web-panel/assets/{index-vQ6jy8Ew.js → index-BRpzktuV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu6TVdgQ.js → index-BWWfogVi.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2XRXkZU.js → index-B_8atrbf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvhVCHk-.js → index-BugberH-.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUUs12L9.js → index-C3PtUFgB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGVHW0nF.js → index-C5chs_dS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcLBzIiI.js → index-CDCDbWnv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQEoGqMG.js → index-CIiqGHl0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dum_r_oY.js → index-CQWeRqsN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BD64r0Tx.js → index-CRjnopxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1xZMQrN.js → index-CUFMz21M.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTwDhhvl.js → index-CaDLq49Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-lFOfvZDn.js → index-Ch8O1mrO.js} +3 -3
- package/src/assets/web-panel/assets/{index-BNbsjeEc.js → index-CpnGJDxL.js} +1 -1
- package/src/assets/web-panel/assets/{index-LSz0ZOB8.js → index-CxvnkXVj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ow3SRXRX.js → index-D6sAHm_C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSxJ4oRd.js → index-D8sxQTSI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRCIPsdm.js → index-DDLVcra4.js} +1 -1
- package/src/assets/web-panel/assets/{index-q--LtRh9.js → index-DFzJlpwu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dpj-rEoV.js → index-DHHrnUkx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGBvkNN3.js → index-DJrRZ5um.js} +1 -1
- package/src/assets/web-panel/assets/{index-P6IMLmLT.js → index-DUT3dpdB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVp_dHCs.js → index-DY6ZHvJw.js} +1 -1
- package/src/assets/web-panel/assets/{index-fZnlPk7H.js → index-DeJMQynL.js} +1 -1
- package/src/assets/web-panel/assets/{index-dPFzlII1.js → index-DjZvE3_P.js} +1 -1
- package/src/assets/web-panel/assets/{index-B615KD2o.js → index-DtPSAUza.js} +1 -1
- package/src/assets/web-panel/assets/{index-b0Y0hDhX.js → index-NiXCoVfh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BigtYchO.js → index-PR4YRMxw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQ5j1VKg.js → index-SbxDTh6S.js} +1 -1
- package/src/assets/web-panel/assets/index-WmqZ5PwS.js +1 -0
- package/src/assets/web-panel/assets/{index-kYqi6zYp.js → index-oxcFCNJ4.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-1NU9yNpr.js → initDefaultProps-Ncgc0S0H.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BZk2hQ6c.js → motion-BBobPHz-.js} +1 -1
- package/src/assets/web-panel/assets/{move-BNvublME.js → move-Dgeb9hea.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DWk5BUoL.js → mtc-parser-onxX64FZ.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DztYxX3x.js → omit-D2OZXzri.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DURL8mtg.js → pickAttrs-J5iBCavx.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B820R56z.js → placementArrow-CE4U8sO6.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DUAkGJL1.js → responsiveObserve-Dl64-wdo.js} +1 -1
- package/src/assets/web-panel/assets/{slide-uZQeNSo6.js → slide-DRFC8TgC.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CGR1wFVq.js → statusUtils-Bh1xb87a.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BIIPOMsQ.js → styleChecker-DlG_dRlk.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-rIB9c0g2.js → useFlexGapSupport-5qmSh3pc.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CrJe1BJi.js → useFs-C_fMqkQA.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DyEoD3Qm.js → usePersonalDataHub-D0Aih7lk.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CjohPxI0.js → vnode-Cv17kLab.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C5POL1_6.js → zoom-B8Mzvavx.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/serve.js +62 -9
- package/src/data/changelog.json +13 -0
- package/src/gateways/ws/ws-server.js +392 -33
- package/src/generated/cc-agent-protocol.schema.json +1442 -1
- package/src/lib/app-server/server.js +3 -1
- package/src/lib/app-server/websocket-transport.js +546 -0
- package/src/lib/public-ide-capability-manifest.js +4 -0
- package/src/runtime/__tests__/coding-agent-events.test.js +36 -0
- package/src/runtime/coding-agent-events.cjs +82 -0
- package/src/assets/web-panel/assets/EmailListRenderer-CcsFzV4y.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-jj7LhT04.js +0 -7
- package/src/assets/web-panel/assets/ProjectSettings-CJaXO_iE.js +0 -2
- package/src/assets/web-panel/assets/Projects-C21-MQ3H.js +0 -1
- package/src/assets/web-panel/assets/Skills-BhKkYF3_.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B_acr36n.js +0 -1
- package/src/assets/web-panel/assets/index-BnLYpdox.js +0 -1
- package/src/assets/web-panel/assets/index-Dzmwsl_v.js +0 -1
|
@@ -174,6 +174,36 @@ const HEARTBEAT_INTERVAL = 30_000;
|
|
|
174
174
|
*/
|
|
175
175
|
const AUTH_TIMEOUT_MS = 15_000;
|
|
176
176
|
|
|
177
|
+
const LEGACY_WS_FLOW_DEFAULTS = Object.freeze({
|
|
178
|
+
maxPendingMessages: 128,
|
|
179
|
+
maxPendingMessageBytes: 8 * 1024 * 1024,
|
|
180
|
+
maxQueuedOutputMessages: 512,
|
|
181
|
+
maxQueuedOutputBytes: 8 * 1024 * 1024,
|
|
182
|
+
maxSocketBufferedBytes: 4 * 1024 * 1024,
|
|
183
|
+
slowConsumerTimeoutMs: 5_000,
|
|
184
|
+
maxCommandOutputBytes: 8 * 1024 * 1024,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function boundedInteger(value, fallback, maximum = Number.MAX_SAFE_INTEGER) {
|
|
188
|
+
const parsed = Number(value);
|
|
189
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) return fallback;
|
|
190
|
+
return Math.min(parsed, maximum);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function frameByteLength(data) {
|
|
194
|
+
if (typeof data === "string") return Buffer.byteLength(data, "utf8");
|
|
195
|
+
if (Buffer.isBuffer(data)) return data.byteLength;
|
|
196
|
+
if (Array.isArray(data)) {
|
|
197
|
+
return data.reduce((total, part) => total + frameByteLength(part), 0);
|
|
198
|
+
}
|
|
199
|
+
if (data?.byteLength != null) return Number(data.byteLength) || 0;
|
|
200
|
+
return Buffer.byteLength(String(data ?? ""), "utf8");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function wait(milliseconds) {
|
|
204
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
205
|
+
}
|
|
206
|
+
|
|
177
207
|
/**
|
|
178
208
|
* Tokenize a command string into an array of arguments.
|
|
179
209
|
* Handles double-quoted and single-quoted strings. Does NOT invoke a shell.
|
|
@@ -226,6 +256,13 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
226
256
|
* @param {string} [options.token] - If set, clients must authenticate first
|
|
227
257
|
* @param {number} [options.maxConnections=10]
|
|
228
258
|
* @param {number} [options.timeout=30000] - Command execution timeout (ms)
|
|
259
|
+
* @param {number} [options.maxPendingMessages=128]
|
|
260
|
+
* @param {number} [options.maxPendingMessageBytes=8388608]
|
|
261
|
+
* @param {number} [options.maxQueuedOutputMessages=512]
|
|
262
|
+
* @param {number} [options.maxQueuedOutputBytes=8388608]
|
|
263
|
+
* @param {number} [options.maxSocketBufferedBytes=4194304]
|
|
264
|
+
* @param {number} [options.slowConsumerTimeoutMs=5000]
|
|
265
|
+
* @param {number} [options.maxCommandOutputBytes=8388608]
|
|
229
266
|
* @param {Function} [options.spawn] - Process seam for tests
|
|
230
267
|
*/
|
|
231
268
|
constructor(options = {}) {
|
|
@@ -251,6 +288,37 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
251
288
|
// grace window so it can't hold a maxConnections slot forever (see
|
|
252
289
|
// AUTH_TIMEOUT_MS). `??` so an explicit 0 (disable) is honoured.
|
|
253
290
|
this.authTimeoutMs = options.authTimeoutMs ?? AUTH_TIMEOUT_MS;
|
|
291
|
+
this.maxPendingMessages = boundedInteger(
|
|
292
|
+
options.maxPendingMessages,
|
|
293
|
+
LEGACY_WS_FLOW_DEFAULTS.maxPendingMessages,
|
|
294
|
+
10_000,
|
|
295
|
+
);
|
|
296
|
+
this.maxPendingMessageBytes = boundedInteger(
|
|
297
|
+
options.maxPendingMessageBytes,
|
|
298
|
+
LEGACY_WS_FLOW_DEFAULTS.maxPendingMessageBytes,
|
|
299
|
+
);
|
|
300
|
+
this.maxQueuedOutputMessages = boundedInteger(
|
|
301
|
+
options.maxQueuedOutputMessages,
|
|
302
|
+
LEGACY_WS_FLOW_DEFAULTS.maxQueuedOutputMessages,
|
|
303
|
+
100_000,
|
|
304
|
+
);
|
|
305
|
+
this.maxQueuedOutputBytes = boundedInteger(
|
|
306
|
+
options.maxQueuedOutputBytes,
|
|
307
|
+
LEGACY_WS_FLOW_DEFAULTS.maxQueuedOutputBytes,
|
|
308
|
+
);
|
|
309
|
+
this.maxSocketBufferedBytes = boundedInteger(
|
|
310
|
+
options.maxSocketBufferedBytes,
|
|
311
|
+
LEGACY_WS_FLOW_DEFAULTS.maxSocketBufferedBytes,
|
|
312
|
+
);
|
|
313
|
+
this.slowConsumerTimeoutMs = boundedInteger(
|
|
314
|
+
options.slowConsumerTimeoutMs,
|
|
315
|
+
LEGACY_WS_FLOW_DEFAULTS.slowConsumerTimeoutMs,
|
|
316
|
+
120_000,
|
|
317
|
+
);
|
|
318
|
+
this.maxCommandOutputBytes = boundedInteger(
|
|
319
|
+
options.maxCommandOutputBytes,
|
|
320
|
+
LEGACY_WS_FLOW_DEFAULTS.maxCommandOutputBytes,
|
|
321
|
+
);
|
|
254
322
|
this._spawnProcess = options.spawn || _deps.spawn;
|
|
255
323
|
|
|
256
324
|
/** Optional Phase-5 envelope bus for fan-out to hosted HTTP SSE. */
|
|
@@ -261,6 +329,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
261
329
|
|
|
262
330
|
/** Connected clients: clientId → { ws, authenticated, connectedAt } */
|
|
263
331
|
this.clients = new Map();
|
|
332
|
+
this._clientsBySocket = new WeakMap();
|
|
264
333
|
|
|
265
334
|
/** Running child processes: requestId → ChildProcess */
|
|
266
335
|
this.processes = new Map();
|
|
@@ -455,6 +524,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
455
524
|
|
|
456
525
|
// Close all client connections
|
|
457
526
|
for (const [, client] of this.clients) {
|
|
527
|
+
this._clearClientOutput(client);
|
|
458
528
|
try {
|
|
459
529
|
client.ws.close(1001, "Server shutting down");
|
|
460
530
|
} catch (_err) {
|
|
@@ -509,8 +579,16 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
509
579
|
alive: true,
|
|
510
580
|
authTimer: null,
|
|
511
581
|
membershipConnectionNonce: randomBytes(32).toString("base64url"),
|
|
582
|
+
pendingMessages: 0,
|
|
583
|
+
pendingMessageBytes: 0,
|
|
584
|
+
outputQueue: [],
|
|
585
|
+
outputQueuedBytes: 0,
|
|
586
|
+
outputPumping: false,
|
|
587
|
+
outputFailure: null,
|
|
588
|
+
lastOverloadAt: 0,
|
|
512
589
|
};
|
|
513
590
|
this.clients.set(clientId, client);
|
|
591
|
+
this._clientsBySocket.set(ws, client);
|
|
514
592
|
|
|
515
593
|
// Auth grace timer: a token-protected connection that never sends a valid
|
|
516
594
|
// auth message would otherwise hold a slot forever (the heartbeat only reaps
|
|
@@ -537,16 +615,49 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
537
615
|
this.emit("connection", { clientId, ip: clientIp });
|
|
538
616
|
|
|
539
617
|
ws.on("message", (data) => {
|
|
618
|
+
const bytes = frameByteLength(data);
|
|
619
|
+
let message;
|
|
540
620
|
try {
|
|
541
|
-
|
|
542
|
-
this._handleMessage(clientId, ws, message);
|
|
621
|
+
message = JSON.parse(data.toString("utf8"));
|
|
543
622
|
} catch (_err) {
|
|
544
623
|
this._send(ws, {
|
|
545
624
|
type: "error",
|
|
546
625
|
code: "INVALID_JSON",
|
|
547
626
|
message: "Failed to parse message as JSON",
|
|
548
627
|
});
|
|
628
|
+
return;
|
|
549
629
|
}
|
|
630
|
+
|
|
631
|
+
if (
|
|
632
|
+
client.pendingMessages >= this.maxPendingMessages ||
|
|
633
|
+
client.pendingMessageBytes + bytes > this.maxPendingMessageBytes
|
|
634
|
+
) {
|
|
635
|
+
this._sendInputOverloaded(client, message?.id);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
client.pendingMessages += 1;
|
|
639
|
+
client.pendingMessageBytes += bytes;
|
|
640
|
+
void Promise.resolve()
|
|
641
|
+
.then(() => this._handleMessage(clientId, ws, message))
|
|
642
|
+
.catch((error) => {
|
|
643
|
+
this.emit("client-error", {
|
|
644
|
+
clientId,
|
|
645
|
+
error: error?.message || String(error),
|
|
646
|
+
});
|
|
647
|
+
this._send(ws, {
|
|
648
|
+
id: message?.id,
|
|
649
|
+
type: "error",
|
|
650
|
+
code: "INTERNAL_ERROR",
|
|
651
|
+
message: "WebSocket request failed",
|
|
652
|
+
});
|
|
653
|
+
})
|
|
654
|
+
.finally(() => {
|
|
655
|
+
client.pendingMessages = Math.max(0, client.pendingMessages - 1);
|
|
656
|
+
client.pendingMessageBytes = Math.max(
|
|
657
|
+
0,
|
|
658
|
+
client.pendingMessageBytes - bytes,
|
|
659
|
+
);
|
|
660
|
+
});
|
|
550
661
|
});
|
|
551
662
|
|
|
552
663
|
ws.on("close", () => {
|
|
@@ -558,7 +669,9 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
558
669
|
// mirrors the CLI attach detach semantics (an idle worker with no
|
|
559
670
|
// remaining clients finalizes itself).
|
|
560
671
|
cleanupBgAttachments(client);
|
|
672
|
+
this._clearClientOutput(client);
|
|
561
673
|
this.clients.delete(clientId);
|
|
674
|
+
this._clientsBySocket.delete(ws);
|
|
562
675
|
try {
|
|
563
676
|
for (const affected of this.remoteSessions.removeClient(clientId)) {
|
|
564
677
|
if (affected.closed)
|
|
@@ -616,11 +729,26 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
616
729
|
return this._dispatcher.dispatch(clientId, ws, message);
|
|
617
730
|
}
|
|
618
731
|
|
|
732
|
+
_sendInputOverloaded(client, id) {
|
|
733
|
+
const now = Date.now();
|
|
734
|
+
if (now - client.lastOverloadAt < 100 && id == null) return;
|
|
735
|
+
client.lastOverloadAt = now;
|
|
736
|
+
this._send(client.ws, {
|
|
737
|
+
id,
|
|
738
|
+
type: "error",
|
|
739
|
+
code: "OVERLOADED",
|
|
740
|
+
message: "WebSocket request backlog is full",
|
|
741
|
+
retryAfterMs: 100,
|
|
742
|
+
retry_after_ms: 100,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
619
746
|
/** Read the same static + scoped authority used by the Agent Core. */
|
|
620
747
|
async _handlePermissionRulesGet(id, ws) {
|
|
621
748
|
try {
|
|
622
|
-
const { loadPermissionAuthority } =
|
|
623
|
-
|
|
749
|
+
const { loadPermissionAuthority } = await import(
|
|
750
|
+
"../../lib/permission-authority.js"
|
|
751
|
+
);
|
|
624
752
|
const loaded = loadPermissionAuthority({ cwd: this.projectRoot });
|
|
625
753
|
this._send(ws, {
|
|
626
754
|
id,
|
|
@@ -1035,22 +1163,42 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1035
1163
|
this.processes.set(id, child);
|
|
1036
1164
|
this.emit("command:start", { id, command, stream });
|
|
1037
1165
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1166
|
+
let settled = false;
|
|
1167
|
+
let timer = null;
|
|
1168
|
+
const settleWithError = (
|
|
1169
|
+
code,
|
|
1170
|
+
message,
|
|
1171
|
+
{ kill = true, send = true } = {},
|
|
1172
|
+
) => {
|
|
1173
|
+
if (settled) return false;
|
|
1174
|
+
settled = true;
|
|
1175
|
+
if (timer) clearTimeout(timer);
|
|
1176
|
+
if (kill) {
|
|
1041
1177
|
try {
|
|
1042
1178
|
child.kill("SIGTERM");
|
|
1043
1179
|
} catch (_err) {
|
|
1044
1180
|
// Process may have already exited
|
|
1045
1181
|
}
|
|
1046
|
-
|
|
1182
|
+
}
|
|
1183
|
+
this.processes.delete(id);
|
|
1184
|
+
if (send) {
|
|
1047
1185
|
this._send(ws, {
|
|
1048
1186
|
id,
|
|
1049
1187
|
type: "error",
|
|
1050
|
-
code
|
|
1051
|
-
message
|
|
1188
|
+
code,
|
|
1189
|
+
message,
|
|
1052
1190
|
});
|
|
1053
1191
|
}
|
|
1192
|
+
this.emit("command:end", { id, exitCode: null, error: code });
|
|
1193
|
+
return true;
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
// Timeout handling
|
|
1197
|
+
timer = setTimeout(() => {
|
|
1198
|
+
settleWithError(
|
|
1199
|
+
"COMMAND_TIMEOUT",
|
|
1200
|
+
`Command timed out after ${this.timeout}ms`,
|
|
1201
|
+
);
|
|
1054
1202
|
}, this.timeout);
|
|
1055
1203
|
|
|
1056
1204
|
if (stream) {
|
|
@@ -1062,32 +1210,74 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1062
1210
|
// been announced; if there is no stdout, flush it before stream-end.
|
|
1063
1211
|
let stdoutSeen = false;
|
|
1064
1212
|
const pendingStderr = [];
|
|
1213
|
+
let pendingStderrBytes = 0;
|
|
1065
1214
|
|
|
1066
1215
|
child.stdout.on("data", (data) => {
|
|
1216
|
+
if (settled) return;
|
|
1067
1217
|
stdoutSeen = true;
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1218
|
+
if (
|
|
1219
|
+
!this._send(ws, {
|
|
1220
|
+
id,
|
|
1221
|
+
type: "stream-data",
|
|
1222
|
+
channel: "stdout",
|
|
1223
|
+
data: data.toString("utf8"),
|
|
1224
|
+
})
|
|
1225
|
+
) {
|
|
1226
|
+
settleWithError(
|
|
1227
|
+
"COMMAND_OUTPUT_UNAVAILABLE",
|
|
1228
|
+
"Output consumer closed",
|
|
1229
|
+
{
|
|
1230
|
+
send: false,
|
|
1231
|
+
},
|
|
1232
|
+
);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1074
1235
|
while (pendingStderr.length > 0) {
|
|
1075
|
-
this._send(ws, pendingStderr.shift())
|
|
1236
|
+
if (!this._send(ws, pendingStderr.shift())) {
|
|
1237
|
+
settleWithError(
|
|
1238
|
+
"COMMAND_OUTPUT_UNAVAILABLE",
|
|
1239
|
+
"Output consumer closed",
|
|
1240
|
+
{ send: false },
|
|
1241
|
+
);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1076
1244
|
}
|
|
1245
|
+
pendingStderrBytes = 0;
|
|
1077
1246
|
});
|
|
1078
1247
|
|
|
1079
1248
|
child.stderr.on("data", (data) => {
|
|
1249
|
+
if (settled) return;
|
|
1080
1250
|
const message = {
|
|
1081
1251
|
id,
|
|
1082
1252
|
type: "stream-data",
|
|
1083
1253
|
channel: "stderr",
|
|
1084
1254
|
data: data.toString("utf8"),
|
|
1085
1255
|
};
|
|
1086
|
-
if (stdoutSeen)
|
|
1087
|
-
|
|
1256
|
+
if (stdoutSeen) {
|
|
1257
|
+
if (!this._send(ws, message)) {
|
|
1258
|
+
settleWithError(
|
|
1259
|
+
"COMMAND_OUTPUT_UNAVAILABLE",
|
|
1260
|
+
"Output consumer closed",
|
|
1261
|
+
{ send: false },
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
pendingStderrBytes += frameByteLength(data);
|
|
1267
|
+
if (pendingStderrBytes > this.maxCommandOutputBytes) {
|
|
1268
|
+
pendingStderr.length = 0;
|
|
1269
|
+
settleWithError(
|
|
1270
|
+
"COMMAND_OUTPUT_TOO_LARGE",
|
|
1271
|
+
`Command output exceeded ${this.maxCommandOutputBytes} bytes`,
|
|
1272
|
+
);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
pendingStderr.push(message);
|
|
1088
1276
|
});
|
|
1089
1277
|
|
|
1090
1278
|
child.on("close", (exitCode) => {
|
|
1279
|
+
if (settled) return;
|
|
1280
|
+
settled = true;
|
|
1091
1281
|
clearTimeout(timer);
|
|
1092
1282
|
this.processes.delete(id);
|
|
1093
1283
|
for (const message of pendingStderr) this._send(ws, message);
|
|
@@ -1102,11 +1292,30 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1102
1292
|
// Buffered mode: collect all output then send result
|
|
1103
1293
|
const stdoutChunks = [];
|
|
1104
1294
|
const stderrChunks = [];
|
|
1295
|
+
let outputBytes = 0;
|
|
1296
|
+
|
|
1297
|
+
const collectOutput = (data, chunks) => {
|
|
1298
|
+
if (settled) return;
|
|
1299
|
+
const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
1300
|
+
outputBytes += chunk.byteLength;
|
|
1301
|
+
if (outputBytes > this.maxCommandOutputBytes) {
|
|
1302
|
+
stdoutChunks.length = 0;
|
|
1303
|
+
stderrChunks.length = 0;
|
|
1304
|
+
settleWithError(
|
|
1305
|
+
"COMMAND_OUTPUT_TOO_LARGE",
|
|
1306
|
+
`Command output exceeded ${this.maxCommandOutputBytes} bytes`,
|
|
1307
|
+
);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
chunks.push(chunk);
|
|
1311
|
+
};
|
|
1105
1312
|
|
|
1106
|
-
child.stdout.on("data", (data) =>
|
|
1107
|
-
child.stderr.on("data", (data) =>
|
|
1313
|
+
child.stdout.on("data", (data) => collectOutput(data, stdoutChunks));
|
|
1314
|
+
child.stderr.on("data", (data) => collectOutput(data, stderrChunks));
|
|
1108
1315
|
|
|
1109
1316
|
child.on("close", (exitCode) => {
|
|
1317
|
+
if (settled) return;
|
|
1318
|
+
settled = true;
|
|
1110
1319
|
clearTimeout(timer);
|
|
1111
1320
|
this.processes.delete(id);
|
|
1112
1321
|
|
|
@@ -1126,13 +1335,8 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1126
1335
|
}
|
|
1127
1336
|
|
|
1128
1337
|
child.on("error", (err) => {
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
this._send(ws, {
|
|
1132
|
-
id,
|
|
1133
|
-
type: "error",
|
|
1134
|
-
code: "SPAWN_ERROR",
|
|
1135
|
-
message: err.message,
|
|
1338
|
+
settleWithError("SPAWN_ERROR", err.message, {
|
|
1339
|
+
kill: false,
|
|
1136
1340
|
});
|
|
1137
1341
|
});
|
|
1138
1342
|
}
|
|
@@ -1294,8 +1498,9 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1294
1498
|
/** @private — ping/pong heartbeat to detect dead connections */
|
|
1295
1499
|
async _ensureTaskManager() {
|
|
1296
1500
|
if (this._taskManager) return this._taskManager;
|
|
1297
|
-
const { BackgroundTaskManager } =
|
|
1298
|
-
|
|
1501
|
+
const { BackgroundTaskManager } = await import(
|
|
1502
|
+
"../../harness/background-task-manager.js"
|
|
1503
|
+
);
|
|
1299
1504
|
this._taskManager = new BackgroundTaskManager({
|
|
1300
1505
|
recoverOnStart: true,
|
|
1301
1506
|
policyCwd: this.projectRoot,
|
|
@@ -1340,16 +1545,170 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
1340
1545
|
|
|
1341
1546
|
/** @private — safe JSON send */
|
|
1342
1547
|
_send(ws, data) {
|
|
1343
|
-
if (ws.readyState
|
|
1548
|
+
if (ws.readyState !== ws.OPEN) return false;
|
|
1549
|
+
let frame;
|
|
1550
|
+
try {
|
|
1551
|
+
frame = JSON.stringify(data);
|
|
1552
|
+
} catch (_err) {
|
|
1553
|
+
return false;
|
|
1554
|
+
}
|
|
1555
|
+
const client = this._clientsBySocket.get(ws);
|
|
1556
|
+
if (!client) {
|
|
1344
1557
|
try {
|
|
1345
|
-
ws.send(
|
|
1558
|
+
ws.send(frame);
|
|
1346
1559
|
this._mirrorRemoteSessionEvent(ws, data);
|
|
1560
|
+
return true;
|
|
1347
1561
|
} catch (_err) {
|
|
1348
|
-
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return this._enqueueClientOutput(client, { frame, data });
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
_enqueueClientOutput(client, entry) {
|
|
1569
|
+
if (client.outputFailure || client.ws.readyState !== client.ws.OPEN) {
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
entry.bytes = Buffer.byteLength(entry.frame, "utf8");
|
|
1573
|
+
if (
|
|
1574
|
+
entry.bytes > this.maxSocketBufferedBytes ||
|
|
1575
|
+
client.outputQueue.length >= this.maxQueuedOutputMessages ||
|
|
1576
|
+
client.outputQueuedBytes + entry.bytes > this.maxQueuedOutputBytes
|
|
1577
|
+
) {
|
|
1578
|
+
this._tripClientOutput(client, "WebSocket output backlog exceeded");
|
|
1579
|
+
return false;
|
|
1580
|
+
}
|
|
1581
|
+
client.outputQueue.push(entry);
|
|
1582
|
+
client.outputQueuedBytes += entry.bytes;
|
|
1583
|
+
void this._pumpClientOutput(client);
|
|
1584
|
+
return true;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
async _pumpClientOutput(client) {
|
|
1588
|
+
if (client.outputPumping || client.outputFailure) return;
|
|
1589
|
+
client.outputPumping = true;
|
|
1590
|
+
try {
|
|
1591
|
+
while (client.outputQueue.length > 0 && !client.outputFailure) {
|
|
1592
|
+
const entry = client.outputQueue[0];
|
|
1593
|
+
await this._waitForClientOutput(client, entry.bytes);
|
|
1594
|
+
await this._writeClientFrame(client, entry.frame);
|
|
1595
|
+
client.outputQueue.shift();
|
|
1596
|
+
client.outputQueuedBytes = Math.max(
|
|
1597
|
+
0,
|
|
1598
|
+
client.outputQueuedBytes - entry.bytes,
|
|
1599
|
+
);
|
|
1600
|
+
this._mirrorRemoteSessionEvent(client.ws, entry.data);
|
|
1349
1601
|
}
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
this._tripClientOutput(
|
|
1604
|
+
client,
|
|
1605
|
+
error?.message || "WebSocket slow consumer",
|
|
1606
|
+
);
|
|
1607
|
+
} finally {
|
|
1608
|
+
client.outputPumping = false;
|
|
1350
1609
|
}
|
|
1351
1610
|
}
|
|
1352
1611
|
|
|
1612
|
+
async _waitForClientOutput(client, frameBytes) {
|
|
1613
|
+
const deadline = Date.now() + this.slowConsumerTimeoutMs;
|
|
1614
|
+
while (
|
|
1615
|
+
Number(client.ws.bufferedAmount || 0) + frameBytes >
|
|
1616
|
+
this.maxSocketBufferedBytes
|
|
1617
|
+
) {
|
|
1618
|
+
if (client.ws.readyState !== client.ws.OPEN) {
|
|
1619
|
+
throw new Error("WebSocket closed before output drained");
|
|
1620
|
+
}
|
|
1621
|
+
if (Date.now() >= deadline) {
|
|
1622
|
+
throw new Error("WebSocket slow consumer deadline exceeded");
|
|
1623
|
+
}
|
|
1624
|
+
await wait(10);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async _writeClientFrame(client, frame) {
|
|
1629
|
+
if (client.ws.readyState !== client.ws.OPEN) {
|
|
1630
|
+
throw new Error("WebSocket closed before output send");
|
|
1631
|
+
}
|
|
1632
|
+
let timer;
|
|
1633
|
+
const write = new Promise((resolve, reject) => {
|
|
1634
|
+
client.ws.send(frame, { binary: false }, (error) =>
|
|
1635
|
+
error ? reject(error) : resolve("sent"),
|
|
1636
|
+
);
|
|
1637
|
+
});
|
|
1638
|
+
const deadline = new Promise((resolve) => {
|
|
1639
|
+
timer = setTimeout(resolve, this.slowConsumerTimeoutMs, "timeout");
|
|
1640
|
+
timer.unref?.();
|
|
1641
|
+
});
|
|
1642
|
+
try {
|
|
1643
|
+
if ((await Promise.race([write, deadline])) === "timeout") {
|
|
1644
|
+
throw new Error("WebSocket output callback deadline exceeded");
|
|
1645
|
+
}
|
|
1646
|
+
} finally {
|
|
1647
|
+
clearTimeout(timer);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
_tripClientOutput(client, reason) {
|
|
1652
|
+
if (client.outputFailure) return;
|
|
1653
|
+
client.outputFailure = reason;
|
|
1654
|
+
this._clearClientOutput(client, { preserveFailure: true });
|
|
1655
|
+
this.emit("client-error", {
|
|
1656
|
+
clientId: this._clientIdForSocket(client.ws),
|
|
1657
|
+
error: reason,
|
|
1658
|
+
code: "CC_WS_OUTPUT_BACKPRESSURE",
|
|
1659
|
+
});
|
|
1660
|
+
try {
|
|
1661
|
+
client.ws.close(1013, "WebSocket output backpressure");
|
|
1662
|
+
} catch (_err) {
|
|
1663
|
+
client.ws.terminate?.();
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
_clearClientOutput(client, { preserveFailure = false } = {}) {
|
|
1668
|
+
if (!Array.isArray(client.outputQueue)) {
|
|
1669
|
+
client.outputQueue = [];
|
|
1670
|
+
}
|
|
1671
|
+
client.outputQueue.length = 0;
|
|
1672
|
+
client.outputQueuedBytes = 0;
|
|
1673
|
+
if (!preserveFailure && !client.outputFailure) {
|
|
1674
|
+
client.outputFailure = "closed";
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
_clientIdForSocket(ws) {
|
|
1679
|
+
for (const [clientId, client] of this.clients) {
|
|
1680
|
+
if (client.ws === ws) return clientId;
|
|
1681
|
+
}
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
flowControlStatus() {
|
|
1686
|
+
return Object.freeze({
|
|
1687
|
+
limits: Object.freeze({
|
|
1688
|
+
maxPendingMessages: this.maxPendingMessages,
|
|
1689
|
+
maxPendingMessageBytes: this.maxPendingMessageBytes,
|
|
1690
|
+
maxQueuedOutputMessages: this.maxQueuedOutputMessages,
|
|
1691
|
+
maxQueuedOutputBytes: this.maxQueuedOutputBytes,
|
|
1692
|
+
maxSocketBufferedBytes: this.maxSocketBufferedBytes,
|
|
1693
|
+
slowConsumerTimeoutMs: this.slowConsumerTimeoutMs,
|
|
1694
|
+
maxCommandOutputBytes: this.maxCommandOutputBytes,
|
|
1695
|
+
}),
|
|
1696
|
+
clients: Object.freeze(
|
|
1697
|
+
[...this.clients.entries()].map(([clientId, client]) =>
|
|
1698
|
+
Object.freeze({
|
|
1699
|
+
clientId,
|
|
1700
|
+
pendingMessages: client.pendingMessages,
|
|
1701
|
+
pendingMessageBytes: client.pendingMessageBytes,
|
|
1702
|
+
queuedOutputMessages: client.outputQueue.length,
|
|
1703
|
+
queuedOutputBytes: client.outputQueuedBytes,
|
|
1704
|
+
socketBufferedBytes: Number(client.ws.bufferedAmount || 0),
|
|
1705
|
+
outputFailure: client.outputFailure,
|
|
1706
|
+
}),
|
|
1707
|
+
),
|
|
1708
|
+
),
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1353
1712
|
/** Mirror canonical Agent Session output to paired Remote Session clients. */
|
|
1354
1713
|
_mirrorRemoteSessionEvent(hostWs, data) {
|
|
1355
1714
|
if (
|