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
@@ -109,6 +109,7 @@ export class CcAppServer {
109
109
  maxConcurrentRequests = 8,
110
110
  requestTimeoutMs = 120_000,
111
111
  interruptSettlementMs = 30_000,
112
+ transport = "stdio",
112
113
  } = {}) {
113
114
  if (typeof send !== "function") {
114
115
  throw new TypeError("CcAppServer requires a send(message) function");
@@ -120,6 +121,7 @@ export class CcAppServer {
120
121
  this.createId = createId;
121
122
  this.requestTimeoutMs = requestTimeoutMs;
122
123
  this.interruptSettlementMs = interruptSettlementMs;
124
+ this.transport = transport === "websocket" ? "websocket" : "stdio";
123
125
  this.initialized = false;
124
126
  this.negotiated = null;
125
127
  this.client = null;
@@ -319,7 +321,7 @@ export class CcAppServer {
319
321
  minimumProtocolVersion: APP_SERVER_MIN_PROTOCOL_VERSION,
320
322
  features: this.negotiated.features,
321
323
  downgraded: this.negotiated.downgraded,
322
- transports: ["stdio"],
324
+ transports: [this.transport],
323
325
  websocket: { stability: "experimental" },
324
326
  limits: this.requestQueue.snapshot(),
325
327
  schema: {
@@ -0,0 +1,546 @@
1
+ import { createHash, timingSafeEqual } from "node:crypto";
2
+ import { EventEmitter } from "node:events";
3
+ import fs from "node:fs";
4
+ import { createServer as createHttpServer } from "node:http";
5
+ import { createServer as createHttpsServer } from "node:https";
6
+ import { resolve as resolvePath } from "node:path";
7
+ import { createSecureContext } from "node:tls";
8
+ import { WebSocketServer } from "ws";
9
+
10
+ import { BoundedAsyncQueue, QueueOverloadedError } from "./bounded-queue.js";
11
+ import { CcAppServer } from "./server.js";
12
+ import { JSON_RPC_ERROR, JsonRpcError, rpcError } from "./protocol.js";
13
+
14
+ export const APP_SERVER_WEBSOCKET_PROTOCOL =
15
+ "chainlesschain.app-server.experimental.v1";
16
+ export const APP_SERVER_WEBSOCKET_PATH = "/app-server";
17
+
18
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "::1", "[::1]", "localhost"]);
19
+ const MAX_TLS_FILE_BYTES = 1024 * 1024;
20
+
21
+ function boundedInteger(value, fallback, minimum = 1, maximum = 1_000_000) {
22
+ const number = Number(value);
23
+ if (!Number.isSafeInteger(number)) return fallback;
24
+ return Math.min(maximum, Math.max(minimum, number));
25
+ }
26
+
27
+ export function isLoopbackHost(host) {
28
+ return LOOPBACK_HOSTS.has(
29
+ String(host || "")
30
+ .trim()
31
+ .toLowerCase(),
32
+ );
33
+ }
34
+
35
+ export function validateWebSocketAppServerOptions(options = {}) {
36
+ const host = String(options.host || "127.0.0.1").trim();
37
+ const remote = !isLoopbackHost(host);
38
+ const token = typeof options.token === "string" ? options.token : "";
39
+ const tlsCertPath = options.tlsCertPath
40
+ ? resolvePath(options.tlsCertPath)
41
+ : null;
42
+ const tlsKeyPath = options.tlsKeyPath
43
+ ? resolvePath(options.tlsKeyPath)
44
+ : null;
45
+
46
+ if (Buffer.byteLength(token, "utf8") < 32) {
47
+ throw new Error(
48
+ "App Server WebSocket binding requires a token of at least 32 bytes",
49
+ );
50
+ }
51
+ if (remote && options.allowRemote !== true) {
52
+ throw new Error(
53
+ "Non-loopback App Server WebSocket binding requires --allow-remote",
54
+ );
55
+ }
56
+ if (remote && (!tlsCertPath || !tlsKeyPath)) {
57
+ throw new Error(
58
+ "Non-loopback App Server WebSocket binding requires TLS certificate and key",
59
+ );
60
+ }
61
+ if ((tlsCertPath && !tlsKeyPath) || (!tlsCertPath && tlsKeyPath)) {
62
+ throw new Error(
63
+ "App Server WebSocket TLS requires both certificate and key",
64
+ );
65
+ }
66
+
67
+ return Object.freeze({
68
+ host,
69
+ remote,
70
+ token,
71
+ tlsCertPath,
72
+ tlsKeyPath,
73
+ secure: Boolean(tlsCertPath && tlsKeyPath),
74
+ });
75
+ }
76
+
77
+ function constantTimeTokenEqual(expected, actual) {
78
+ const expectedDigest = createHash("sha256").update(expected, "utf8").digest();
79
+ const actualDigest = createHash("sha256").update(actual, "utf8").digest();
80
+ return timingSafeEqual(expectedDigest, actualDigest);
81
+ }
82
+
83
+ function parseProtocols(request) {
84
+ return new Set(
85
+ String(request.headers?.["sec-websocket-protocol"] || "")
86
+ .split(",")
87
+ .map((value) => value.trim())
88
+ .filter(Boolean),
89
+ );
90
+ }
91
+
92
+ function presentedToken(request, protocols) {
93
+ const authorization = String(request.headers?.authorization || "");
94
+ const bearer = /^Bearer\s+(.+)$/iu.exec(authorization)?.[1];
95
+ if (bearer) return bearer;
96
+ const encoded = [...protocols]
97
+ .find((protocol) => protocol.startsWith("bearer."))
98
+ ?.slice("bearer.".length);
99
+ if (!encoded) return "";
100
+ try {
101
+ return Buffer.from(encoded, "base64url").toString("utf8");
102
+ } catch {
103
+ return "";
104
+ }
105
+ }
106
+
107
+ function wait(milliseconds) {
108
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
109
+ }
110
+
111
+ function readTlsFile(filePath, label, fsImpl) {
112
+ let descriptor = null;
113
+ try {
114
+ const link = fsImpl.lstatSync(filePath);
115
+ if (link.isSymbolicLink()) {
116
+ throw new Error(`App Server TLS ${label} must not be a symbolic link`);
117
+ }
118
+ descriptor = fsImpl.openSync(
119
+ filePath,
120
+ fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
121
+ );
122
+ const stat = fsImpl.fstatSync(descriptor);
123
+ if (!stat.isFile() || stat.size <= 0 || stat.size > MAX_TLS_FILE_BYTES) {
124
+ throw new Error(
125
+ `App Server TLS ${label} must be a non-empty regular file no larger than 1 MiB`,
126
+ );
127
+ }
128
+ if (
129
+ label === "private key" &&
130
+ process.platform !== "win32" &&
131
+ (stat.mode & 0o077) !== 0
132
+ ) {
133
+ throw new Error(
134
+ "App Server TLS private key must not be accessible by group or other users",
135
+ );
136
+ }
137
+ return fsImpl.readFileSync(descriptor);
138
+ } finally {
139
+ if (descriptor != null) fsImpl.closeSync(descriptor);
140
+ }
141
+ }
142
+
143
+ export class WebSocketConnectionTransport extends EventEmitter {
144
+ constructor({
145
+ socket,
146
+ maxQueuedMessages = 256,
147
+ maxQueuedBytes = 4 * 1024 * 1024,
148
+ maxBufferedBytes = 2 * 1024 * 1024,
149
+ slowConsumerTimeoutMs = 5_000,
150
+ } = {}) {
151
+ super();
152
+ if (!socket || typeof socket.send !== "function") {
153
+ throw new TypeError("WebSocket transport requires a socket");
154
+ }
155
+ this.socket = socket;
156
+ this.maxBufferedBytes = boundedInteger(maxBufferedBytes, 2 * 1024 * 1024);
157
+ this.slowConsumerTimeoutMs = boundedInteger(
158
+ slowConsumerTimeoutMs,
159
+ 5_000,
160
+ 1,
161
+ 120_000,
162
+ );
163
+ this.queue = new BoundedAsyncQueue({
164
+ maxItems: boundedInteger(maxQueuedMessages, 256),
165
+ maxBytes: boundedInteger(maxQueuedBytes, 4 * 1024 * 1024),
166
+ sizeOf: (frame) => Buffer.byteLength(frame, "utf8"),
167
+ });
168
+ this.closed = false;
169
+ this.failure = null;
170
+ this.pump = this._pump();
171
+ this.pump.catch(() => {});
172
+ }
173
+
174
+ async _waitUntilWritable(frameBytes) {
175
+ const deadline = Date.now() + this.slowConsumerTimeoutMs;
176
+ while (
177
+ Number(this.socket.bufferedAmount || 0) + frameBytes >
178
+ this.maxBufferedBytes
179
+ ) {
180
+ if (Date.now() >= deadline) {
181
+ throw new QueueOverloadedError(
182
+ "App Server WebSocket slow consumer exceeded the buffer deadline",
183
+ { retryAfterMs: 1_000 },
184
+ );
185
+ }
186
+ await wait(10);
187
+ }
188
+ }
189
+
190
+ async _sendFrame(frame) {
191
+ if (this.socket.readyState !== 1) {
192
+ throw new Error("App Server WebSocket is not open");
193
+ }
194
+ const frameBytes = Buffer.byteLength(frame, "utf8");
195
+ await this._waitUntilWritable(frameBytes);
196
+ await new Promise((resolve, reject) => {
197
+ this.socket.send(frame, { binary: false }, (error) =>
198
+ error ? reject(error) : resolve(),
199
+ );
200
+ });
201
+ }
202
+
203
+ async _pump() {
204
+ try {
205
+ for await (const frame of this.queue) await this._sendFrame(frame);
206
+ } catch (error) {
207
+ this.failure = error;
208
+ this.queue.close(error);
209
+ this._tripSlowConsumer();
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ _tripSlowConsumer() {
215
+ if (this.socket.readyState === 0 || this.socket.readyState === 1) {
216
+ try {
217
+ this.socket.close(1013, "App Server slow consumer");
218
+ } catch {
219
+ this.socket.terminate?.();
220
+ }
221
+ }
222
+ this.emit("breaker", this.failure);
223
+ }
224
+
225
+ send(message) {
226
+ if (this.failure) return Promise.reject(this.failure);
227
+ if (this.closed)
228
+ return Promise.reject(new Error("WebSocket transport closed"));
229
+ const frame = JSON.stringify(message);
230
+ try {
231
+ this.queue.push(frame);
232
+ return Promise.resolve();
233
+ } catch (error) {
234
+ if (error instanceof QueueOverloadedError) {
235
+ this.failure = error;
236
+ this._tripSlowConsumer();
237
+ }
238
+ return Promise.reject(error);
239
+ }
240
+ }
241
+
242
+ async close() {
243
+ if (this.closed) return;
244
+ this.closed = true;
245
+ this.queue.close();
246
+ await this.pump.catch(() => {});
247
+ }
248
+ }
249
+
250
+ export class WebSocketAppServerHost extends EventEmitter {
251
+ constructor(options = {}) {
252
+ super();
253
+ this.options = options;
254
+ this.binding = validateWebSocketAppServerOptions(options);
255
+ this.port = boundedInteger(options.port, 18800, 0, 65_535);
256
+ this.path = options.path || APP_SERVER_WEBSOCKET_PATH;
257
+ this.maxConnections = boundedInteger(options.maxConnections, 10, 1, 1_000);
258
+ this.maxPayloadBytes = boundedInteger(options.maxPayloadBytes, 1024 * 1024);
259
+ this.maxPendingReceives = boundedInteger(options.maxPendingReceives, 512);
260
+ this.cleanupTimeoutMs = boundedInteger(
261
+ options.cleanupTimeoutMs,
262
+ 10_000,
263
+ 1,
264
+ 120_000,
265
+ );
266
+ this.store = options.store;
267
+ this.kernelFactory = options.kernelFactory;
268
+ this.serverFactory = options.serverFactory;
269
+ this.WebSocketServerClass = options.WebSocketServerClass || WebSocketServer;
270
+ this.createHttpServer = options.createHttpServer || createHttpServer;
271
+ this.createHttpsServer = options.createHttpsServer || createHttpsServer;
272
+ this.fs = options.fs || fs;
273
+ this.createSecureContext =
274
+ options.createSecureContext || createSecureContext;
275
+ this.connections = new Map();
276
+ this.cleanups = new Set();
277
+ this.httpServer = null;
278
+ this.wss = null;
279
+ this.on("error", () => {});
280
+ }
281
+
282
+ _authorize(info, done) {
283
+ const protocols = parseProtocols(info.req);
284
+ if (!protocols.has(APP_SERVER_WEBSOCKET_PROTOCOL)) {
285
+ done(false, 426, "App Server WebSocket subprotocol required");
286
+ return;
287
+ }
288
+ if (
289
+ this.binding.token &&
290
+ !constantTimeTokenEqual(
291
+ this.binding.token,
292
+ presentedToken(info.req, protocols),
293
+ )
294
+ ) {
295
+ done(false, 401, "Unauthorized");
296
+ return;
297
+ }
298
+ if (this.connections.size >= this.maxConnections) {
299
+ done(false, 503, "Connection limit reached");
300
+ return;
301
+ }
302
+ done(true);
303
+ }
304
+
305
+ _createHttpHost() {
306
+ const requestHandler = (_request, response) => {
307
+ response.writeHead(404, {
308
+ "content-type": "text/plain; charset=utf-8",
309
+ "x-content-type-options": "nosniff",
310
+ });
311
+ response.end("Not Found");
312
+ };
313
+ if (!this.binding.secure) return this.createHttpServer(requestHandler);
314
+ const cert = readTlsFile(this.binding.tlsCertPath, "certificate", this.fs);
315
+ const key = readTlsFile(this.binding.tlsKeyPath, "private key", this.fs);
316
+ try {
317
+ this.createSecureContext({ cert, key, minVersion: "TLSv1.2" });
318
+ } catch {
319
+ throw new Error(
320
+ "App Server TLS certificate and private key are invalid or do not match",
321
+ );
322
+ }
323
+ return this.createHttpsServer(
324
+ {
325
+ cert,
326
+ key,
327
+ minVersion: "TLSv1.2",
328
+ },
329
+ requestHandler,
330
+ );
331
+ }
332
+
333
+ async start() {
334
+ if (this.httpServer)
335
+ throw new Error("App Server WebSocket already started");
336
+ this.httpServer = this._createHttpHost();
337
+ this.wss = new this.WebSocketServerClass({
338
+ server: this.httpServer,
339
+ path: this.path,
340
+ maxPayload: this.maxPayloadBytes,
341
+ perMessageDeflate: false,
342
+ clientTracking: true,
343
+ verifyClient: (info, done) => this._authorize(info, done),
344
+ handleProtocols: (protocols) =>
345
+ protocols.has(APP_SERVER_WEBSOCKET_PROTOCOL)
346
+ ? APP_SERVER_WEBSOCKET_PROTOCOL
347
+ : false,
348
+ });
349
+ this.wss.on("connection", (socket, request) =>
350
+ this._accept(socket, request),
351
+ );
352
+ this.wss.on("error", (error) => this.emit("error", error));
353
+
354
+ await new Promise((resolve, reject) => {
355
+ const onError = (error) => {
356
+ this.httpServer.off("listening", onListening);
357
+ reject(error);
358
+ };
359
+ const onListening = () => {
360
+ this.httpServer.off("error", onError);
361
+ resolve();
362
+ };
363
+ this.httpServer.once("error", onError);
364
+ this.httpServer.once("listening", onListening);
365
+ this.httpServer.listen(this.port, this.binding.host);
366
+ });
367
+ const address = this.httpServer.address();
368
+ const port =
369
+ typeof address === "object" && address ? address.port : this.port;
370
+ this.port = port;
371
+ const displayHost = this.binding.host.includes(":")
372
+ ? `[${this.binding.host.replace(/^\[|\]$/gu, "")}]`
373
+ : this.binding.host;
374
+ return Object.freeze({
375
+ host: this.binding.host,
376
+ port,
377
+ path: this.path,
378
+ secure: this.binding.secure,
379
+ stability: "experimental",
380
+ url: `${this.binding.secure ? "wss" : "ws"}://${displayHost}:${port}${this.path}`,
381
+ });
382
+ }
383
+
384
+ _accept(socket, request) {
385
+ const transport = new WebSocketConnectionTransport({
386
+ socket,
387
+ maxQueuedMessages: this.options.maxQueuedMessages,
388
+ maxQueuedBytes: this.options.maxQueuedBytes,
389
+ maxBufferedBytes: this.options.maxBufferedBytes,
390
+ slowConsumerTimeoutMs: this.options.slowConsumerTimeoutMs,
391
+ });
392
+ const send = (message) => transport.send(message);
393
+ const server = this.serverFactory
394
+ ? this.serverFactory({ send, request, transport: "websocket" })
395
+ : new CcAppServer({
396
+ send,
397
+ store: this.store,
398
+ kernel: this.kernelFactory?.(),
399
+ transport: "websocket",
400
+ maxQueuedRequests: this.options.maxQueuedRequests,
401
+ maxQueuedRequestBytes: this.options.maxQueuedRequestBytes,
402
+ maxConcurrentRequests: this.options.maxConcurrentRequests,
403
+ requestTimeoutMs: this.options.requestTimeoutMs,
404
+ interruptSettlementMs: this.options.interruptSettlementMs,
405
+ });
406
+ const context = {
407
+ socket,
408
+ transport,
409
+ server,
410
+ pending: new Set(),
411
+ closing: false,
412
+ cleanup: null,
413
+ };
414
+ this.connections.set(socket, context);
415
+ socket.on("message", (data, isBinary) =>
416
+ this._receive(context, data, isBinary),
417
+ );
418
+ socket.on("close", () => void this._cleanup(context));
419
+ socket.on("error", (error) => {
420
+ this.emit("connection-error", error);
421
+ void this._cleanup(context);
422
+ });
423
+ transport.on("breaker", () => void this._cleanup(context));
424
+ this.emit("connection", { remoteAddress: request.socket?.remoteAddress });
425
+ }
426
+
427
+ _receive(context, data, isBinary) {
428
+ if (context.closing) return;
429
+ if (isBinary) {
430
+ context.socket.close(1003, "JSON text frames required");
431
+ return;
432
+ }
433
+ const frame = Buffer.isBuffer(data) ? data : Buffer.from(data);
434
+ if (frame.length > this.maxPayloadBytes) {
435
+ context.socket.close(1009, "App Server frame too large");
436
+ return;
437
+ }
438
+ let message;
439
+ try {
440
+ message = JSON.parse(frame.toString("utf8"));
441
+ } catch {
442
+ this._sendControl(
443
+ context,
444
+ rpcError(
445
+ null,
446
+ new JsonRpcError(JSON_RPC_ERROR.PARSE_ERROR, "Invalid JSON"),
447
+ ),
448
+ );
449
+ return;
450
+ }
451
+
452
+ const isClientResponse =
453
+ message?.method == null && Object.hasOwn(message || {}, "id");
454
+ if (!isClientResponse && context.pending.size >= this.maxPendingReceives) {
455
+ this._sendControl(
456
+ context,
457
+ rpcError(
458
+ message?.id,
459
+ new JsonRpcError(
460
+ JSON_RPC_ERROR.OVERLOADED,
461
+ "App Server WebSocket receive queue is overloaded",
462
+ { retry_after_ms: 100 },
463
+ ),
464
+ ),
465
+ );
466
+ return;
467
+ }
468
+
469
+ const pending = Promise.resolve()
470
+ .then(() => context.server.receive(message))
471
+ .catch((error) => {
472
+ this.emit("connection-error", error);
473
+ void this._cleanup(context);
474
+ });
475
+ context.pending.add(pending);
476
+ pending.finally(() => context.pending.delete(pending));
477
+ }
478
+
479
+ _sendControl(context, message) {
480
+ void context.transport.send(message).catch((error) => {
481
+ this.emit("connection-error", error);
482
+ void this._cleanup(context);
483
+ });
484
+ }
485
+
486
+ async _cleanup(context) {
487
+ if (context.cleanup) return context.cleanup;
488
+ context.closing = true;
489
+ this.connections.delete(context.socket);
490
+ context.cleanup = this._finishCleanup(context);
491
+ this.cleanups.add(context.cleanup);
492
+ void context.cleanup.then(
493
+ () => this.cleanups.delete(context.cleanup),
494
+ () => this.cleanups.delete(context.cleanup),
495
+ );
496
+ return context.cleanup;
497
+ }
498
+
499
+ async _finishCleanup(context) {
500
+ const settlement = Promise.allSettled([
501
+ context.transport.close(),
502
+ Promise.resolve().then(() => context.server.close?.()),
503
+ ...context.pending,
504
+ ]);
505
+ let timer;
506
+ const deadline = new Promise((resolve) => {
507
+ timer = setTimeout(resolve, this.cleanupTimeoutMs, "timeout");
508
+ timer.unref?.();
509
+ });
510
+ try {
511
+ if ((await Promise.race([settlement, deadline])) === "timeout") {
512
+ this.emit(
513
+ "connection-error",
514
+ new Error("App Server WebSocket cleanup deadline exceeded"),
515
+ );
516
+ context.socket.terminate?.();
517
+ }
518
+ } finally {
519
+ clearTimeout(timer);
520
+ }
521
+ }
522
+
523
+ async close() {
524
+ const contexts = [...this.connections.values()];
525
+ for (const context of contexts) {
526
+ try {
527
+ context.socket.close(1001, "App Server shutting down");
528
+ } catch {
529
+ context.socket.terminate?.();
530
+ }
531
+ }
532
+ await Promise.all(contexts.map((context) => this._cleanup(context)));
533
+ await Promise.allSettled([...this.cleanups]);
534
+ for (const context of contexts) {
535
+ if (context.socket.readyState !== 3) context.socket.terminate?.();
536
+ }
537
+ if (this.wss) {
538
+ await new Promise((resolve) => this.wss.close(() => resolve()));
539
+ this.wss = null;
540
+ }
541
+ if (this.httpServer) {
542
+ await new Promise((resolve) => this.httpServer.close(() => resolve()));
543
+ this.httpServer = null;
544
+ }
545
+ }
546
+ }
@@ -53,6 +53,10 @@ const VSCODE_COMMANDS = [
53
53
  "chainlesschain.sessions.workbench",
54
54
  "chainlesschain.automation.center",
55
55
  "chainlesschain.remote.control",
56
+ "chainlesschain.appServerPilot.status",
57
+ "chainlesschain.appServerPilot.startThread",
58
+ "chainlesschain.appServerPilot.startTurn",
59
+ "chainlesschain.appServerPilot.interrupt",
56
60
  "chainlesschain.usage.show",
57
61
  "chainlesschain.plugins.manage",
58
62
  "chainlesschain.worktree.tasks",
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { describe, it, expect } from "vitest";
15
15
  import { createRequire } from "module";
16
+ import { readFileSync } from "node:fs";
16
17
 
17
18
  const require = createRequire(import.meta.url);
18
19
  const {
@@ -24,6 +25,7 @@ const {
24
25
  wrapLegacyMessage,
25
26
  validateCodingAgentEvent,
26
27
  mapLegacyType,
28
+ projectAgentStreamMessage,
27
29
  } = require("../coding-agent-events.cjs");
28
30
 
29
31
  const VALID_TYPE = Object.values(CODING_AGENT_EVENT_TYPES)[0];
@@ -47,6 +49,40 @@ describe("mapLegacyType", () => {
47
49
  });
48
50
  });
49
51
 
52
+ describe("projectAgentStreamMessage", () => {
53
+ it("projects the shared canonical approval fixture without losing binding", () => {
54
+ const [request, resolved] = readFileSync(
55
+ new URL(
56
+ "../../../../agent-sdk/__fixtures__/protocol/interaction.ndjson",
57
+ import.meta.url,
58
+ ),
59
+ "utf8",
60
+ )
61
+ .split(/\r?\n/u)
62
+ .filter(Boolean)
63
+ .slice(0, 2)
64
+ .map((line) => JSON.parse(line));
65
+
66
+ expect(projectAgentStreamMessage(request)).toEqual({
67
+ type: CODING_AGENT_EVENT_TYPES.APPROVAL_REQUESTED,
68
+ payload: request,
69
+ });
70
+ expect(projectAgentStreamMessage(request).payload.binding).toBe(
71
+ "ab_0123456789abcdef0123456789abcdef",
72
+ );
73
+ expect(projectAgentStreamMessage(resolved)).toEqual({
74
+ type: CODING_AGENT_EVENT_TYPES.APPROVAL_GRANTED,
75
+ payload: resolved,
76
+ });
77
+ });
78
+
79
+ it("keeps unknown future stream events outside the semantic mapper", () => {
80
+ expect(
81
+ projectAgentStreamMessage({ type: "future_event_v9", value: true }),
82
+ ).toBe(null);
83
+ });
84
+ });
85
+
50
86
  describe("CodingAgentSequenceTracker", () => {
51
87
  it("increments strictly per requestId", () => {
52
88
  const t = newTracker();