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.
Files changed (168) hide show
  1. package/README.md +32 -0
  2. package/package.json +2 -1
  3. package/src/assets/web-panel/assets/{AIOps-HcOh0ZbH.js → AIOps-DhCWWRmS.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DWjDqBhd.js → ActionButton-BGbZcxSE.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-C6FS-y3W.js → Analytics-CUJNYrmU.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DCN5cldF.js → AppLayout-ParuJRmc.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-Crd-KE8P.js → Artifacts-BMESCkte.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BexuTQZc.js → Audit-B614pUXR.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-Dydkh-b5.js → BackgroundAgents-BKkkvkQl.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-BYq21wMP.js → Backup-CoFiC_kv.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-CmNZP6yM.js → BaseInput-Cu4OHz1W.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-J6bD0zCl.js → Chat-mxb_SR22.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-MEoaygnL.js → ChatBubbleRenderer-CsqO-fiV.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-Ce-9vNmK.js → Checkbox-BJpZz-5L.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-zf7-Y-Eh.js → Codegen-ng7fYMi8.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-D-TkY0FD.js → Col-D751NOsU.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-l300NA-1.js → Community-BPVTHvTC.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-D-9Q_hxc.js → Compact-CY42ARxJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-DAJ8lLiQ.js → Compliance-Ec3zabvu.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-xmzZ967S.js → Cowork-Do0oOlj8.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-CeGqO5W4.js → Cron-BXu6LGra.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DXN0AE-4.js → Crosschain-Dg1CsWLA.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CtIKASPr.js → DID-D1B8O6C9.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-DqRwT8NX.js → Dashboard-DJFnfIVk.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-ddIVljO1.js → Dropdown-Be6KCHb7.js} +1 -1
  26. package/src/assets/web-panel/assets/EmailListRenderer-9L4EdmcR.js +1 -0
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-PnlAUGcG.js → FamilyGuardDashboard-CWt6WUoP.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BlmLuQrL.js → Federation-Ba7bRkZh.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-dDovBj1Y.js → FormItemContext-CNa8GTAM.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DTvGshFr.js → GenericCardRenderer-DGHg2fio.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DuFR2xgA.js → Git-DJAgN16e.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-9EaNcJuq.js → Governance-DqIapin6.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-B-iYWH_r.js → Inference-B00_KwDW.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-BGBaKiLX.js → KnowledgeGraph-D1_ZCehX.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-G3wmXzfa.js → Logs-22jGgUmw.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-hk0YwjTR.js → MarkdownRenderer-DOIQQUTp.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-BNARBeAv.js → Marketplace-ClIyNRL6.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DST2cSSK.js → McpTools-BhPsaaJr.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-piwDLsVH.js → Memory-BXNDHgEH.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-D-W8zHdm.js → MobileBridge-BeSEujLm.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-CwaYHlVP.js → MobileProjects-DKJjWZ6t.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-C2dxnN7Q.js → Mtc-CsX4lo0h.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-BwPZsV3N.js → MtcAudit-CdGiWPEN.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-CUt8w2al.js → Multisig-C1aJmYGd.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DwxErx27.js → NLProgramming-ClOwZKGW.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-B-mXAZ84.js → Notes-xqZJkp5X.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-Bd1fBAzd.js → NotificationSettings-D_8hg6E5.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CCInnavw.js → OrderTableRenderer-BNMgCFHY.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-DTTONYZT.js → Organization-DQjjPQ9u.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-H6lCZ1rE.js → Overflow-D0g51CZb.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-DyBZzeYW.js → P2P-BqDCDnon.js} +2 -2
  52. package/src/assets/web-panel/assets/PdhVaultBrowser-BDMyV-DM.js +7 -0
  53. package/src/assets/web-panel/assets/{Permissions-B5nSQwsr.js → Permissions-D9Mk4hzw.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CACaQtlY.js → PersonalDataHub-Cksd0Ft-.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-5y-DR_Bw.js → Pipeline-ALaa1U0w.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BMSA3Ehd.js → Privacy-M-mmVDtr.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-VCOj1dTa.js → ProjectInit-Bn39Z5S7.js} +2 -2
  58. package/src/assets/web-panel/assets/ProjectSettings-6Xmsc-zo.js +2 -0
  59. package/src/assets/web-panel/assets/Projects-C2KI2sH1.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-DZ1FNKUR.js → Providers-FLNmdOWT.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-6DKc9UBX.js → QrScannerModal-C5PNS5N7.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-Dyd0Ckrk.js → QuickAsk-CHSVdvQA.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-CYW4-GJH.js → Recommend-BRwvF4m9.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DLrfPy4F.js → RemoteSession-DeAfmrU2.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-DfqPo1FI.js → Reputation-Be_YQzm9.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-CR89H2x5.js → Row-Bv3cbL2A.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-B9QY7R-F.js → RssFeed-DsTYAdrh.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-CUDnzu4w.js → Search-2gch6_kT.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CJ0CtM_Z.js → Security-pkS2eKdn.js} +3 -3
  70. package/src/assets/web-panel/assets/{Services-CyrQ6F5U.js → Services-D1ns1NlD.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DcwnKatQ.js → Skeleton-B-9nVmze.js} +1 -1
  72. package/src/assets/web-panel/assets/Skills-V7TqlXWR.js +1 -0
  73. package/src/assets/web-panel/assets/{Sla-CWh-awh7.js → Sla-CI9DZg-s.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BaUMNzx7.js → SpeechSettings-CH0mQsl3.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-D1Ijwwxl.js → SyncSettings-BHeEVZyN.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-DFqjHi3T.js → Tasks-DOld05sg.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-Ct-9_ki3.js → Templates--GvWvJEB.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-zBkgyilh.js → Tenant-L-FgZD-r.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-VVO2Hjfi.js → Terminal-DMNfs5hK.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CX9pIkx3.js → TimelineRenderer-DFzInCAj.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DHaD6e_m.js → Tokens-Bnh73Gam.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CFq9j3WV.js → Trigger-BO-fMwq8.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DuF7gIYX.js → Trust-CX-HXcRl.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DbuidXJX.js → UkeySign-C8AOFGVL.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B8IA9_qE.js → VideoEditing-B_bJ6BO2.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-DdCglSIK.js → Wallet-Ccd3u1t4.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-CeteRsA8.js → WebAuthn-DVSL5e6o.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DJhIbG__.js → WorkflowEditor-DqFq6xGO.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-srOi_Wef.js → chat-s5SEbhS8.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-HpVHsNGK.js → colors-CRFmX1eW.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-DsLhrqWe.js → compact-item-DCofr-Ii.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-aOasIUuE.js → createContext-duu0_qGr.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-0bFBr86K.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D5BVMimS.js → hasIn-B0KKSHGi.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DV4y5rUj.js → index-4aqbfxrp.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-ChGUPEwS.js → index-B24nGxlB.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BCp70IlO.js → index-BCnLafAV.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CQXp_uNd.js → index-BDGNTPr6.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DiXsis7m.js → index-BERyKHMq.js} +1 -1
  100. package/src/assets/web-panel/assets/index-BJCitHZ6.js +1 -0
  101. package/src/assets/web-panel/assets/{index-V0gpVTbS.js → index-BM48QU3P.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C4tPtHU0.js → index-BMaM-Yex.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-vQ6jy8Ew.js → index-BRpzktuV.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Bu6TVdgQ.js → index-BWWfogVi.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D2XRXkZU.js → index-B_8atrbf.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CvhVCHk-.js → index-BugberH-.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CUUs12L9.js → index-C3PtUFgB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CGVHW0nF.js → index-C5chs_dS.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DcLBzIiI.js → index-CDCDbWnv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CQEoGqMG.js → index-CIiqGHl0.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dum_r_oY.js → index-CQWeRqsN.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BD64r0Tx.js → index-CRjnopxx.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D1xZMQrN.js → index-CUFMz21M.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BTwDhhvl.js → index-CaDLq49Y.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-lFOfvZDn.js → index-Ch8O1mrO.js} +3 -3
  116. package/src/assets/web-panel/assets/{index-BNbsjeEc.js → index-CpnGJDxL.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-LSz0ZOB8.js → index-CxvnkXVj.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Ow3SRXRX.js → index-D6sAHm_C.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CSxJ4oRd.js → index-D8sxQTSI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DRCIPsdm.js → index-DDLVcra4.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-q--LtRh9.js → index-DFzJlpwu.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Dpj-rEoV.js → index-DHHrnUkx.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BGBvkNN3.js → index-DJrRZ5um.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-P6IMLmLT.js → index-DUT3dpdB.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CVp_dHCs.js → index-DY6ZHvJw.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-fZnlPk7H.js → index-DeJMQynL.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-dPFzlII1.js → index-DjZvE3_P.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B615KD2o.js → index-DtPSAUza.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-b0Y0hDhX.js → index-NiXCoVfh.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BigtYchO.js → index-PR4YRMxw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DQ5j1VKg.js → index-SbxDTh6S.js} +1 -1
  132. package/src/assets/web-panel/assets/index-WmqZ5PwS.js +1 -0
  133. package/src/assets/web-panel/assets/{index-kYqi6zYp.js → index-oxcFCNJ4.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-1NU9yNpr.js → initDefaultProps-Ncgc0S0H.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BZk2hQ6c.js → motion-BBobPHz-.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-BNvublME.js → move-Dgeb9hea.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DWk5BUoL.js → mtc-parser-onxX64FZ.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DztYxX3x.js → omit-D2OZXzri.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DURL8mtg.js → pickAttrs-J5iBCavx.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-B820R56z.js → placementArrow-CE4U8sO6.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-DUAkGJL1.js → responsiveObserve-Dl64-wdo.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-uZQeNSo6.js → slide-DRFC8TgC.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-CGR1wFVq.js → statusUtils-Bh1xb87a.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BIIPOMsQ.js → styleChecker-DlG_dRlk.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-rIB9c0g2.js → useFlexGapSupport-5qmSh3pc.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CrJe1BJi.js → useFs-C_fMqkQA.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-DyEoD3Qm.js → usePersonalDataHub-D0Aih7lk.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CjohPxI0.js → vnode-Cv17kLab.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-C5POL1_6.js → zoom-B8Mzvavx.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/serve.js +62 -9
  153. package/src/data/changelog.json +13 -0
  154. package/src/gateways/ws/ws-server.js +392 -33
  155. package/src/generated/cc-agent-protocol.schema.json +1442 -1
  156. package/src/lib/app-server/server.js +3 -1
  157. package/src/lib/app-server/websocket-transport.js +546 -0
  158. package/src/lib/public-ide-capability-manifest.js +4 -0
  159. package/src/runtime/__tests__/coding-agent-events.test.js +36 -0
  160. package/src/runtime/coding-agent-events.cjs +82 -0
  161. package/src/assets/web-panel/assets/EmailListRenderer-CcsFzV4y.js +0 -1
  162. package/src/assets/web-panel/assets/PdhVaultBrowser-jj7LhT04.js +0 -7
  163. package/src/assets/web-panel/assets/ProjectSettings-CJaXO_iE.js +0 -2
  164. package/src/assets/web-panel/assets/Projects-C21-MQ3H.js +0 -1
  165. package/src/assets/web-panel/assets/Skills-BhKkYF3_.js +0 -1
  166. package/src/assets/web-panel/assets/devWarning-B_acr36n.js +0 -1
  167. package/src/assets/web-panel/assets/index-BnLYpdox.js +0 -1
  168. 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
- const message = JSON.parse(data.toString("utf8"));
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
- await import("../../lib/permission-authority.js");
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
- // Timeout handling
1039
- const timer = setTimeout(() => {
1040
- if (this.processes.has(id)) {
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
- this.processes.delete(id);
1182
+ }
1183
+ this.processes.delete(id);
1184
+ if (send) {
1047
1185
  this._send(ws, {
1048
1186
  id,
1049
1187
  type: "error",
1050
- code: "COMMAND_TIMEOUT",
1051
- message: `Command timed out after ${this.timeout}ms`,
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
- this._send(ws, {
1069
- id,
1070
- type: "stream-data",
1071
- channel: "stdout",
1072
- data: data.toString("utf8"),
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) this._send(ws, message);
1087
- else pendingStderr.push(message);
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) => stdoutChunks.push(data));
1107
- child.stderr.on("data", (data) => stderrChunks.push(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
- clearTimeout(timer);
1130
- this.processes.delete(id);
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
- await import("../../harness/background-task-manager.js");
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 === ws.OPEN) {
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(JSON.stringify(data));
1558
+ ws.send(frame);
1346
1559
  this._mirrorRemoteSessionEvent(ws, data);
1560
+ return true;
1347
1561
  } catch (_err) {
1348
- // Connection may have just closed
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 (