chainlesschain 0.165.10 → 0.166.0

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 (188) hide show
  1. package/completions/_cc +2 -1
  2. package/completions/cc.bash +2 -2
  3. package/completions/cc.fish +2 -2
  4. package/completions/cc.ps1 +2 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-DoxapYEM.js → AIOps-D5_pwQ7M.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-DyiHvT2O.js → ActionButton-DgoYO32D.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-CAX06POB.js → Analytics-BIpoZ4QH.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-JXxcyn0R.js → AppLayout-BJ3XrCEe.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-40nZDb9N.js → Artifacts-DUFs_izZ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-B_wc8Kcq.js → Audit-BZdPwwfL.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-CReWOiQY.js → BackgroundAgents-DK9Fn-8B.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-sXrv9fE6.js → Backup-Bd6bOhV_.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-CHWmIERI.js → BaseInput-CRnWlXrd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-DgQrMZCb.js → Chat-D1G_yEl6.js} +6 -6
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Clpgj0fi.js → ChatBubbleRenderer-DDAlEtm-.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-DmRwzt3e.js → Checkbox-CP35SH7R.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-tllAfrrc.js → Codegen-DugeL4pj.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-CA_wqwEp.js → Col-DPuJsvKg.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DQ_szKAv.js → Community-CZqNjNNp.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CjkqX60k.js → Compact-eeyIPEAb.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CfDNkDex.js → Compliance-MGzTw87k.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-PINc6CPv.js → Cowork-D3goVIA5.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-snYq4WCj.js → Cron-9HA8hLLG.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-CKR7he4B.js → Crosschain-DCSOX3l6.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-Bp5xJSbE.js → DID-ji3lN5Bc.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-Dn-y6txl.js → Dashboard-DlZvF1E5.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-D0uEeunr.js → Dropdown-2FyZcow9.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-DIhjyeNw.js → EmailListRenderer-qH2bSpSY.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D2Pv1Hnb.js → FamilyGuardDashboard-Bfx47wZj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-D6oSp4P4.js → Federation-DhGcZbIv.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-s6bzlfQs.js → FormItemContext-BMgi9_YQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-B_NTgGP9.js → GenericCardRenderer-DugMac-4.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-Dk1lCoBr.js → Git-D943BpH-.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-xOnLBQzd.js → Governance-DZ66a8Aa.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-BFVHy-0I.js → Inference-B00Mp2tN.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-BXzITB9_.js → KnowledgeGraph-BdwXXO2Q.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BD2M4NPu.js → Logs-_MVXyYWc.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-CDSwkj7Y.js → MarkdownRenderer-hOTeNyi7.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-awpllI_9.js → Marketplace-CxeHRsnO.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-tL0IgbIX.js → McpTools-BOieTxZ4.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-CrptLGSN.js → Memory-BQD26CM3.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DZX-l_BS.js → MobileBridge-CPJ8ZlvR.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-D7qRVu0y.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-dQ5-FjpQ.js → Mtc-BOyndGuk.js} +6 -6
  46. package/src/assets/web-panel/assets/{MtcAudit-U0nDxDlG.js → MtcAudit-CXQ_8ZoR.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-D1PpAtiq.js → Multisig-32Oh9KbH.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-phZr6nyZ.js → NLProgramming-CFAd00CX.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-BPzSSUrX.js → Notes-BBCY_qJA.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-AypFEc7p.js → NotificationSettings-uaQ0PdmW.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-S3k1qjT-.js → OrderTableRenderer-kpBxJ-SG.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BvdQXFPR.js → Organization-CmFn7Oeo.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-CUz_jbQC.js → Overflow-R_avat3a.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-C0xQ7H_2.js → P2P-BIhLFmkY.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DJKMjfB8.js → PdhVaultBrowser-DTJjk-_i.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-dkMtmDwK.js → Permissions-CkzYv8Yb.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-CyHE7fnn.js → PersonalDataHub--0QvN2Nz.js} +5 -5
  58. package/src/assets/web-panel/assets/{Pipeline-_dU0Nwy3.js → Pipeline-Bfw5gQCH.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-fqLyg57f.js → Privacy-DicjKfax.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-COybCr5d.js → ProjectInit-DUl_ykBp.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-B4DngMZW.js → ProjectSettings-CPYDvN8a.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-D0bMwUBm.js → Projects-DnZ9_fAr.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-Ba-7WNA8.js → Providers-BX1jP31w.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-X-I8dDMT.js → QrScannerModal-COab5qUl.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BCUP0a90.js → QuickAsk-CdKQ_JZ0.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DtFNmiQZ.js → Recommend-BPFHg8pK.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-INR5CIAI.js → RemoteSession-CqrfTJvO.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-B6NfaCc6.js → Reputation-DM-l8Pb3.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-Dmz76txh.js → Row-CKGBTeCw.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-B7qyFCea.js → RssFeed-Wb0YHIkc.js} +2 -2
  71. package/src/assets/web-panel/assets/{Search-Bi6eS0oY.js → Search-DiBjxKv-.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-D9_xim1L.js → Security-BAVEmbWP.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-PI1O-uYU.js → Services-A_a3TIRn.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-B8BrJnk8.js → Skeleton-CloHJ-I7.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-DvSm92dJ.js → Skills-CkvV5hKa.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-BWyhCoRo.js → Sla-BYozxxsV.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-BL9H27jl.js → SpeechSettings-CWianiZF.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-B7r--6yN.js → SyncSettings-BR4G75do.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-sc64p2Jt.js → Tasks-D6gyUVrM.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-DEK55RVb.js → Templates-DbfYIRzT.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-DElr8R1l.js → Tenant-BHutvukT.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BywaLBgz.js → Terminal-CYEiy2wJ.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-B5NcgLyS.js → TimelineRenderer-D9s1AxfE.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-COFYqr6A.js → Tokens-ij9vxnoy.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-CfnVS3ij.js → Trigger-Dq_DCVeQ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-Bz94Gu5k.js → Trust-Bs6wdfoH.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-Be10drSk.js → UkeySign-CLazyr5t.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-CUB_cMem.js → VideoEditing-viWotIHA.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-DxBJJuvG.js → Wallet-DlZ-A78d.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-BZx5lwT8.js → WebAuthn-DSmOND3Y.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-B4sJikPT.js → WorkflowEditor-BempLOJi.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-Wl-95AgM.js → chat-CyJzf4JI.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-DUSwYVCg.js → colors-C62q7WrL.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-DkKRDHcU.js → compact-item-7dYM1KW3.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-B5nAVC_Y.js → createContext-CYTy7z2Q.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-BdTg_AMF.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-rtotsRlA.js → hasIn-DHoclUqm.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DUS7KK-i.js → index-2C8Xx2UO.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-JWQxvZ4x.js → index-3pI7AfmD.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-03w73yRM.js → index-4gWv-Ubg.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDWNrtvg.js → index-94ktimZG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CbqotoPt.js → index-9uR7rc9a.js} +1 -1
  103. package/src/assets/web-panel/assets/index-B6WvFV4N.js +1 -0
  104. package/src/assets/web-panel/assets/index-BKgfptzA.js +1 -0
  105. package/src/assets/web-panel/assets/{index-Bj6mJ7tw.js → index-BMRmxFU_.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B_KDhTdt.js → index-BMxvWauP.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cv38Cwx0.js → index-BghdJ87W.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DoBh4Ezg.js → index-Bzz0aJ86.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-1J_Q-Ovw.js → index-C2efftJb.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CJWpTdCW.js → index-C5YWYlW9.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BxAMRZR0.js → index-CVTnruDt.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CUqLWyY0.js → index-CW86F2Zw.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-X36Wyd1S.js → index-CZRBvN6i.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-D7zQADRc.js → index-CjeViaU-.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C3lcOYkZ.js → index-CnEZ4ZHj.js} +3 -3
  116. package/src/assets/web-panel/assets/{index-cKc70R9h.js → index-D6BC4mmz.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Ba8raWtd.js → index-DA-_807C.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CN2LNCWW.js → index-DAYanuSk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-yt994jkW.js → index-DHgSRimK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-btxpIsXs.js → index-DJzZaPlb.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CAdTY3kv.js → index-DK84lAS0.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DCCTgytM.js → index-DKdv8cju.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C7kGJlSA.js → index-DNLD3nZY.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DupOVIEb.js → index-DOFAwpUK.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CskW4gDB.js → index-DYKwGrAn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DQhlLyT6.js → index-DghnbMlY.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-INU8TpRu.js → index-DhJjifZe.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CUDraKtq.js → index-DkczsbiP.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BMHNKa11.js → index-DvEtHpLc.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CAtnIdx_.js → index-Dyq9tK5u.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-YANfaEyE.js → index-LK1MZoBj.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DfNeBiyO.js → index-NuNCstRO.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-D7Nx-jzS.js → index-QXtrdrub.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-Dnbdqjxz.js → index-XrsAHHQN.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-Ch5SqZaK.js → index-ZXYp76IB.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-mwPG3TJQ.js → index-lHfNxiB5.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-Ck8wTHsR.js → initDefaultProps-CwCl5sFQ.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-teBWI8ni.js → motion-BFJFy70N.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-BdxzRStQ.js → move-ByrtVGq8.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DMpcDDsk.js → mtc-parser-BFiH1mT3.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-BwkyHyWr.js → omit-bdvgpivj.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-SlaxZUQd.js → pickAttrs-BlfuuE3s.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BlEZZ3cs.js → placementArrow-t2SC3DMy.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-Cl5uK6S5.js → responsiveObserve-CoEXv2Li.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-LlOJWbeE.js → slide-BfXCf0Fu.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-DxbwIpQR.js → statusUtils-DNsdyOuS.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-G_P9gils.js → styleChecker-9aHukmoG.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-BDS7KDuK.js → useFlexGapSupport-dTkPxzHN.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-Xx3R_4Qk.js → useFs-DINwfItP.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-ChOtRWax.js → usePersonalDataHub-Bj_RriBV.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-CAUrMwRC.js → vnode-BzRAZrTc.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-Choqv7b5.js → zoom-CamPjkeK.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agent.js +1 -1
  157. package/src/commands/graph.js +100 -0
  158. package/src/commands/orchestrate.js +85 -18
  159. package/src/commands/serve.js +39 -0
  160. package/src/commands/team.js +2 -0
  161. package/src/data/changelog.json +14 -1
  162. package/src/generated/cc-agent-protocol.schema.json +1964 -0
  163. package/src/lib/app-server/bounded-queue.js +87 -0
  164. package/src/lib/app-server/cli-agent-kernel-adapter.js +309 -0
  165. package/src/lib/app-server/protocol.js +125 -0
  166. package/src/lib/app-server/rollout-store.js +603 -0
  167. package/src/lib/app-server/server.js +922 -0
  168. package/src/lib/app-server/sqlite-rollout-store.js +315 -0
  169. package/src/lib/app-server/stdio-transport.js +118 -0
  170. package/src/lib/codex-app-server-adapter.js +249 -0
  171. package/src/lib/graph-kernel/adapters.js +238 -0
  172. package/src/lib/graph-kernel/compiler.js +911 -0
  173. package/src/lib/graph-kernel/eval.js +207 -0
  174. package/src/lib/graph-kernel/event-store.js +101 -0
  175. package/src/lib/graph-kernel/index.js +7 -0
  176. package/src/lib/graph-kernel/runtime.js +2660 -0
  177. package/src/lib/graph-kernel/trace-reducer.js +315 -0
  178. package/src/lib/graph-kernel/trigger-adapter.js +183 -0
  179. package/src/lib/orchestrator.js +2 -1
  180. package/src/lib/record-replay/index.js +1 -0
  181. package/src/lib/record-replay/skill-recorder.js +386 -0
  182. package/src/lib/webhook-security.js +159 -0
  183. package/src/repl/agent-repl.js +5 -4
  184. package/src/repl/prompt-interactions.js +11 -1
  185. package/src/assets/web-panel/assets/MobileProjects-krNyADYi.js +0 -1
  186. package/src/assets/web-panel/assets/devWarning-DFO12EfA.js +0 -1
  187. package/src/assets/web-panel/assets/index-DaDlUWea.js +0 -1
  188. package/src/assets/web-panel/assets/index-xn89TjWJ.js +0 -1
@@ -0,0 +1,922 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { BoundedAsyncQueue, QueueOverloadedError } from "./bounded-queue.js";
3
+ import { CliAgentKernelAdapter } from "./cli-agent-kernel-adapter.js";
4
+ import { JsonlRolloutStore } from "./rollout-store.js";
5
+ import {
6
+ APP_SERVER_MIN_PROTOCOL_VERSION,
7
+ APP_SERVER_PROTOCOL_VERSION,
8
+ APP_SERVER_SCHEMA,
9
+ JSON_RPC_ERROR,
10
+ JsonRpcError,
11
+ negotiateProtocol,
12
+ rpcError,
13
+ rpcNotification,
14
+ rpcResult,
15
+ validateAppServerMessage,
16
+ } from "./protocol.js";
17
+
18
+ const TERMINAL_TURN_STATUSES = new Set(["completed", "failed", "interrupted"]);
19
+
20
+ function stableValue(value) {
21
+ if (Array.isArray(value)) return value.map(stableValue);
22
+ if (!value || typeof value !== "object") return value;
23
+ return Object.fromEntries(
24
+ Object.keys(value)
25
+ .filter((key) => value[key] !== undefined)
26
+ .sort()
27
+ .map((key) => [key, stableValue(value[key])]),
28
+ );
29
+ }
30
+
31
+ function digest(value) {
32
+ return `sha256:${createHash("sha256")
33
+ .update(JSON.stringify(stableValue(value)))
34
+ .digest("hex")}`;
35
+ }
36
+
37
+ function safeId(value, fallback) {
38
+ const text = String(value || "").trim();
39
+ if (/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$/u.test(text)) return text;
40
+ return fallback;
41
+ }
42
+
43
+ function requireObject(value, label = "params") {
44
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
45
+ throw new JsonRpcError(
46
+ JSON_RPC_ERROR.INVALID_PARAMS,
47
+ `${label} must be an object`,
48
+ );
49
+ }
50
+ return value;
51
+ }
52
+
53
+ function requiredString(value, label) {
54
+ if (typeof value !== "string" || !value.trim()) {
55
+ throw new JsonRpcError(
56
+ JSON_RPC_ERROR.INVALID_PARAMS,
57
+ `${label} is required`,
58
+ );
59
+ }
60
+ return value.trim();
61
+ }
62
+
63
+ function textInput(value) {
64
+ if (typeof value === "string") return value.trim();
65
+ if (!Array.isArray(value)) return "";
66
+ return value
67
+ .filter((item) => item?.type === "text" && typeof item.text === "string")
68
+ .map((item) => item.text)
69
+ .join("\n")
70
+ .trim();
71
+ }
72
+
73
+ function turnProjection(turn) {
74
+ return Object.freeze({
75
+ id: turn.id,
76
+ threadId: turn.threadId,
77
+ status: turn.status,
78
+ createdAt: turn.createdAt,
79
+ completedAt: turn.completedAt,
80
+ revision: turn.revision,
81
+ terminalEvidence: turn.terminalEvidence,
82
+ });
83
+ }
84
+
85
+ function publicError(error) {
86
+ if (error instanceof JsonRpcError) return error;
87
+ if (error?.code === "CC_ROLLOUT_THREAD_NOT_FOUND") {
88
+ return new JsonRpcError(JSON_RPC_ERROR.NOT_FOUND, error.message);
89
+ }
90
+ if (
91
+ error?.code === "CC_ROLLOUT_IDEMPOTENCY_CONFLICT" ||
92
+ error?.code === "CC_APP_SERVER_TURN_ACTIVE"
93
+ ) {
94
+ return new JsonRpcError(JSON_RPC_ERROR.CONFLICT, error.message);
95
+ }
96
+ return error;
97
+ }
98
+
99
+ export class CcAppServer {
100
+ constructor({
101
+ send,
102
+ store = new JsonlRolloutStore(),
103
+ kernel = new CliAgentKernelAdapter(),
104
+ now = Date.now,
105
+ createId = randomUUID,
106
+ maxQueuedRequests = 256,
107
+ maxQueuedRequestBytes = 4 * 1024 * 1024,
108
+ maxConcurrentRequests = 8,
109
+ requestTimeoutMs = 120_000,
110
+ interruptSettlementMs = 30_000,
111
+ } = {}) {
112
+ if (typeof send !== "function") {
113
+ throw new TypeError("CcAppServer requires a send(message) function");
114
+ }
115
+ this.send = send;
116
+ this.store = store;
117
+ this.kernel = kernel;
118
+ this.now = now;
119
+ this.createId = createId;
120
+ this.requestTimeoutMs = requestTimeoutMs;
121
+ this.interruptSettlementMs = interruptSettlementMs;
122
+ this.initialized = false;
123
+ this.negotiated = null;
124
+ this.client = null;
125
+ this.closed = false;
126
+ this.turns = new Map();
127
+ this.activeTurns = new Map();
128
+ this.pendingClientRequests = new Map();
129
+ this.completedRequests = new Map();
130
+ this.requestQueue = new BoundedAsyncQueue({
131
+ maxItems: maxQueuedRequests,
132
+ maxBytes: maxQueuedRequestBytes,
133
+ sizeOf: (entry) =>
134
+ Buffer.byteLength(JSON.stringify(entry.message), "utf8"),
135
+ });
136
+ this.workers = Array.from(
137
+ { length: Math.max(1, Number(maxConcurrentRequests) || 8) },
138
+ () => this._work(),
139
+ );
140
+ }
141
+
142
+ async _work() {
143
+ for await (const entry of this.requestQueue) {
144
+ try {
145
+ const response = await this._handleRequest(entry.message);
146
+ if (response) await this.send(response);
147
+ entry.resolve(response);
148
+ } catch (error) {
149
+ const response = rpcError(entry.message?.id, publicError(error));
150
+ try {
151
+ await this.send(response);
152
+ } finally {
153
+ entry.resolve(response);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ async receive(message) {
160
+ if (this.closed) {
161
+ throw new JsonRpcError(
162
+ JSON_RPC_ERROR.INTERNAL_ERROR,
163
+ "App Server is closed",
164
+ );
165
+ }
166
+ const validation = validateAppServerMessage(message);
167
+ if (!validation.ok) {
168
+ const response = rpcError(
169
+ message?.id,
170
+ new JsonRpcError(
171
+ JSON_RPC_ERROR.INVALID_REQUEST,
172
+ "Invalid JSON-RPC message",
173
+ { errors: validation.errors.slice(0, 32) },
174
+ ),
175
+ );
176
+ await this.send(response);
177
+ return response;
178
+ }
179
+ if (message.method == null && Object.hasOwn(message, "id")) {
180
+ return this._settleClientRequest(message);
181
+ }
182
+ if (!Object.hasOwn(message, "id")) {
183
+ const response = rpcError(
184
+ null,
185
+ new JsonRpcError(
186
+ JSON_RPC_ERROR.INVALID_REQUEST,
187
+ "client notifications are not supported",
188
+ ),
189
+ );
190
+ await this.send(response);
191
+ return response;
192
+ }
193
+ return new Promise((resolve) => {
194
+ try {
195
+ this.requestQueue.push({ message, resolve });
196
+ } catch (error) {
197
+ if (!(error instanceof QueueOverloadedError)) throw error;
198
+ const response = rpcError(
199
+ message.id,
200
+ new JsonRpcError(
201
+ JSON_RPC_ERROR.OVERLOADED,
202
+ "App Server is overloaded",
203
+ { retry_after_ms: error.retryAfterMs },
204
+ ),
205
+ );
206
+ void Promise.resolve(this.send(response)).finally(() =>
207
+ resolve(response),
208
+ );
209
+ }
210
+ });
211
+ }
212
+
213
+ _settleClientRequest(message) {
214
+ const pending = this.pendingClientRequests.get(String(message.id));
215
+ if (!pending) return false;
216
+ this.pendingClientRequests.delete(String(message.id));
217
+ clearTimeout(pending.timer);
218
+ if (message.error) {
219
+ pending.reject(
220
+ new JsonRpcError(
221
+ Number(message.error.code) || JSON_RPC_ERROR.INTERNAL_ERROR,
222
+ String(message.error.message || "Client request failed"),
223
+ message.error.data,
224
+ ),
225
+ );
226
+ } else {
227
+ pending.resolve(message.result);
228
+ }
229
+ return true;
230
+ }
231
+
232
+ async _requestClient(method, params) {
233
+ const id = `server:${this.createId()}`;
234
+ const response = new Promise((resolve, reject) => {
235
+ const timer = setTimeout(() => {
236
+ this.pendingClientRequests.delete(id);
237
+ reject(
238
+ new JsonRpcError(
239
+ JSON_RPC_ERROR.INTERRUPTED,
240
+ `Client request timed out: ${method}`,
241
+ ),
242
+ );
243
+ }, this.requestTimeoutMs);
244
+ timer.unref?.();
245
+ this.pendingClientRequests.set(id, { resolve, reject, timer, method });
246
+ });
247
+ try {
248
+ await this.send({ jsonrpc: "2.0", id, method, params });
249
+ } catch (error) {
250
+ const pending = this.pendingClientRequests.get(id);
251
+ if (pending) {
252
+ clearTimeout(pending.timer);
253
+ this.pendingClientRequests.delete(id);
254
+ }
255
+ throw error;
256
+ }
257
+ return response;
258
+ }
259
+
260
+ async _handleRequest(message) {
261
+ const method = message.method;
262
+ if (method === "initialize") return this._initialize(message);
263
+ if (!this.initialized) {
264
+ throw new JsonRpcError(
265
+ JSON_RPC_ERROR.NOT_INITIALIZED,
266
+ "initialize must be called first",
267
+ );
268
+ }
269
+ const handlers = {
270
+ "thread/start": () => this._threadStart(message.params),
271
+ "thread/resume": () => this._threadResume(message.params),
272
+ "thread/fork": () => this._threadFork(message.params),
273
+ "thread/read": () => this._threadRead(message.params),
274
+ "thread/list": () => this._threadList(message.params),
275
+ "thread/archive": () => this._threadArchive(message.params),
276
+ "turn/start": () => this._turnStart(message.params),
277
+ "turn/interrupt": () => this._turnInterrupt(message.params),
278
+ };
279
+ const handler = handlers[method];
280
+ if (!handler) {
281
+ throw new JsonRpcError(
282
+ JSON_RPC_ERROR.METHOD_NOT_FOUND,
283
+ `Unknown App Server method: ${method}`,
284
+ );
285
+ }
286
+ const key = message.params?.idempotencyKey;
287
+ const cacheKey =
288
+ typeof key === "string" && key ? `${method}\0${key}` : null;
289
+ if (cacheKey && this.completedRequests.has(cacheKey)) {
290
+ return rpcResult(message.id, this.completedRequests.get(cacheKey));
291
+ }
292
+ const result = await handler();
293
+ if (cacheKey) {
294
+ this.completedRequests.set(cacheKey, result);
295
+ if (this.completedRequests.size > 10_000) {
296
+ this.completedRequests.delete(
297
+ this.completedRequests.keys().next().value,
298
+ );
299
+ }
300
+ }
301
+ return rpcResult(message.id, result);
302
+ }
303
+
304
+ _initialize(message) {
305
+ if (this.initialized) {
306
+ throw new JsonRpcError(
307
+ JSON_RPC_ERROR.CONFLICT,
308
+ "App Server connection is already initialized",
309
+ );
310
+ }
311
+ const params = requireObject(message.params);
312
+ this.negotiated = negotiateProtocol(params);
313
+ this.client = Object.freeze({ ...params.client });
314
+ this.initialized = true;
315
+ return rpcResult(message.id, {
316
+ server: { name: "chainlesschain-cc-app-server", version: "1" },
317
+ protocolVersion: this.negotiated.protocolVersion,
318
+ minimumProtocolVersion: APP_SERVER_MIN_PROTOCOL_VERSION,
319
+ features: this.negotiated.features,
320
+ downgraded: this.negotiated.downgraded,
321
+ transports: ["stdio"],
322
+ websocket: { stability: "experimental" },
323
+ limits: this.requestQueue.snapshot(),
324
+ schema: {
325
+ id: APP_SERVER_SCHEMA.$id,
326
+ version: APP_SERVER_PROTOCOL_VERSION,
327
+ },
328
+ });
329
+ }
330
+
331
+ async _notify(method, params, rollout = null) {
332
+ if (rollout) {
333
+ this.store.append({
334
+ threadId: rollout.threadId,
335
+ turnId: rollout.turnId,
336
+ itemId: rollout.itemId,
337
+ eventType: method.replaceAll("/", "."),
338
+ toolUseId: rollout.toolUseId,
339
+ approvalId: rollout.approvalId,
340
+ traceId: rollout.traceId,
341
+ parentId: rollout.parentId,
342
+ idempotencyKey: rollout.idempotencyKey,
343
+ payload: params,
344
+ });
345
+ }
346
+ await this.send(rpcNotification(method, params));
347
+ }
348
+
349
+ async _threadStart(rawParams) {
350
+ const params = requireObject(rawParams);
351
+ const thread = this.store.start({
352
+ threadId: params.threadId || this.createId(),
353
+ title: params.title ?? null,
354
+ metadata: {
355
+ ...(params.metadata || {}),
356
+ agentOptions: params.agentOptions || {},
357
+ },
358
+ });
359
+ await this._notify("thread/updated", { thread });
360
+ return { thread };
361
+ }
362
+
363
+ _threadResume(rawParams) {
364
+ const params = requireObject(rawParams);
365
+ const threadId = requiredString(params.threadId, "threadId");
366
+ const thread = this.store.resume(threadId);
367
+ const events = this.store.read(threadId, {
368
+ afterSeq: params.afterEventSeq || 0,
369
+ limit: params.limit || 10_000,
370
+ });
371
+ return { thread, events };
372
+ }
373
+
374
+ async _threadFork(rawParams) {
375
+ const params = requireObject(rawParams);
376
+ const sourceThreadId = requiredString(params.threadId, "threadId");
377
+ const requestId = requiredString(
378
+ params.idempotencyKey || this.createId(),
379
+ "idempotencyKey",
380
+ );
381
+ const kernelThreadId = await this.kernel.forkThread?.(
382
+ sourceThreadId,
383
+ requestId,
384
+ );
385
+ const thread = this.store.forkThread(sourceThreadId, {
386
+ threadId: kernelThreadId || params.newThreadId || this.createId(),
387
+ title: params.title ?? null,
388
+ });
389
+ await this._notify("thread/updated", { thread });
390
+ return { thread, historyForked: Boolean(kernelThreadId) };
391
+ }
392
+
393
+ _threadRead(rawParams) {
394
+ const params = requireObject(rawParams);
395
+ const threadId = requiredString(params.threadId, "threadId");
396
+ return {
397
+ thread: this.store.resume(threadId),
398
+ events: this.store.read(threadId, {
399
+ afterSeq: params.afterEventSeq || 0,
400
+ limit: params.limit || 10_000,
401
+ }),
402
+ };
403
+ }
404
+
405
+ _threadList(rawParams = {}) {
406
+ const params = rawParams == null ? {} : requireObject(rawParams);
407
+ return {
408
+ threads: this.store.list({
409
+ includeArchived: params.includeArchived === true,
410
+ limit: params.limit || 100,
411
+ }),
412
+ };
413
+ }
414
+
415
+ async _threadArchive(rawParams) {
416
+ const params = requireObject(rawParams);
417
+ const threadId = requiredString(params.threadId, "threadId");
418
+ if (this.activeTurns.has(threadId)) {
419
+ throw new JsonRpcError(
420
+ JSON_RPC_ERROR.CONFLICT,
421
+ "cannot archive a thread with an active turn",
422
+ );
423
+ }
424
+ await this.kernel.closeThread?.(threadId);
425
+ const thread = this.store.archive(threadId);
426
+ await this._notify("thread/updated", { thread });
427
+ return { thread };
428
+ }
429
+
430
+ async _turnStart(rawParams) {
431
+ const params = requireObject(rawParams);
432
+ const threadId = requiredString(params.threadId, "threadId");
433
+ const input = textInput(params.input);
434
+ if (!input) {
435
+ throw new JsonRpcError(
436
+ JSON_RPC_ERROR.INVALID_PARAMS,
437
+ "turn input must contain non-empty text",
438
+ );
439
+ }
440
+ if (this.activeTurns.has(threadId)) {
441
+ throw new JsonRpcError(
442
+ JSON_RPC_ERROR.CONFLICT,
443
+ "thread already has an active turn",
444
+ );
445
+ }
446
+ const thread = this.store.resume(threadId);
447
+ if (thread.status === "archived") {
448
+ throw new JsonRpcError(
449
+ JSON_RPC_ERROR.CONFLICT,
450
+ "archived threads cannot start turns",
451
+ );
452
+ }
453
+ const id = safeId(params.turnId, `turn:${this.createId()}`);
454
+ const createdAt = new Date(this.now()).toISOString();
455
+ const turn = {
456
+ id,
457
+ threadId,
458
+ status: "queued",
459
+ createdAt,
460
+ completedAt: null,
461
+ revision: 0,
462
+ terminalEvidence: null,
463
+ interruptRequested: false,
464
+ assistantStarted: false,
465
+ legacyItemIds: new Map(),
466
+ };
467
+ this.turns.set(id, turn);
468
+ const userItemId = `${id}:user`;
469
+ this.store.append({
470
+ threadId,
471
+ turnId: id,
472
+ itemId: userItemId,
473
+ eventType: "item.completed",
474
+ idempotencyKey: `turn-input:${id}`,
475
+ payload: {
476
+ item: {
477
+ id: userItemId,
478
+ threadId,
479
+ turnId: id,
480
+ kind: "user_message",
481
+ status: "completed",
482
+ content: { text: input },
483
+ createdAt,
484
+ completedAt: createdAt,
485
+ },
486
+ },
487
+ });
488
+ turn.status = "running";
489
+ turn.revision += 1;
490
+ await this._notify(
491
+ "turn/started",
492
+ { turn: turnProjection(turn) },
493
+ {
494
+ threadId,
495
+ turnId: id,
496
+ idempotencyKey: `turn-started:${id}`,
497
+ },
498
+ );
499
+ const active = {
500
+ turn,
501
+ settled: null,
502
+ };
503
+ active.settled = this._runTurn(active, input, params.options || {});
504
+ active.settled.catch(() => {});
505
+ this.activeTurns.set(threadId, active);
506
+ return { turn: turnProjection(turn) };
507
+ }
508
+
509
+ async _runTurn(active, input, options) {
510
+ const { turn } = active;
511
+ try {
512
+ const result = await this.kernel.startTurn({
513
+ threadId: turn.threadId,
514
+ turnId: turn.id,
515
+ input,
516
+ options,
517
+ emit: (event) => this._emitKernelEvent(turn, event),
518
+ requestApproval: (event) => this._requestApproval(turn, event),
519
+ });
520
+ if (turn.interruptRequested) {
521
+ await this._completeTurn(turn, "interrupted", result);
522
+ } else if (result?.is_error === true || result?.subtype === "error") {
523
+ await this._completeTurn(turn, "failed", result);
524
+ } else {
525
+ await this._completeTurn(turn, "completed", result);
526
+ }
527
+ } catch (error) {
528
+ await this._completeTurn(
529
+ turn,
530
+ turn.interruptRequested ? "interrupted" : "failed",
531
+ {
532
+ is_error: true,
533
+ error: String(error?.message || error).slice(0, 4096),
534
+ code: error?.code || null,
535
+ },
536
+ );
537
+ } finally {
538
+ if (this.activeTurns.get(turn.threadId) === active) {
539
+ this.activeTurns.delete(turn.threadId);
540
+ }
541
+ }
542
+ return turnProjection(turn);
543
+ }
544
+
545
+ async _ensureAssistantStarted(turn, traceId = null) {
546
+ if (turn.assistantStarted) return `${turn.id}:assistant`;
547
+ turn.assistantStarted = true;
548
+ const itemId = `${turn.id}:assistant`;
549
+ await this._notify(
550
+ "item/started",
551
+ {
552
+ item: {
553
+ id: itemId,
554
+ threadId: turn.threadId,
555
+ turnId: turn.id,
556
+ kind: "assistant_message",
557
+ status: "started",
558
+ createdAt: new Date(this.now()).toISOString(),
559
+ },
560
+ },
561
+ {
562
+ threadId: turn.threadId,
563
+ turnId: turn.id,
564
+ itemId,
565
+ traceId,
566
+ idempotencyKey: `assistant-started:${turn.id}`,
567
+ },
568
+ );
569
+ return itemId;
570
+ }
571
+
572
+ async _emitKernelEvent(turn, event) {
573
+ if (TERMINAL_TURN_STATUSES.has(turn.status)) return;
574
+ const traceId = safeId(event?.trace_id, null);
575
+ if (
576
+ event?.type === "stream_event" &&
577
+ event.event?.type === "content_block_delta"
578
+ ) {
579
+ const itemId = await this._ensureAssistantStarted(turn, traceId);
580
+ await this._notify(
581
+ "item/delta",
582
+ {
583
+ threadId: turn.threadId,
584
+ turnId: turn.id,
585
+ itemId,
586
+ delta: event.event.delta,
587
+ },
588
+ {
589
+ threadId: turn.threadId,
590
+ turnId: turn.id,
591
+ itemId,
592
+ traceId,
593
+ },
594
+ );
595
+ return;
596
+ }
597
+ if (event?.type === "tool_use") {
598
+ const toolUseId = safeId(
599
+ event.id,
600
+ `tool:${turn.id}:${turn.revision + 1}`,
601
+ );
602
+ const itemId = `${turn.id}:tool:${toolUseId}`;
603
+ turn.legacyItemIds.set(toolUseId, itemId);
604
+ await this._notify(
605
+ "tool/requested",
606
+ {
607
+ threadId: turn.threadId,
608
+ turnId: turn.id,
609
+ itemId,
610
+ toolUseId,
611
+ tool: event.tool,
612
+ args: event.args || {},
613
+ },
614
+ {
615
+ threadId: turn.threadId,
616
+ turnId: turn.id,
617
+ itemId,
618
+ toolUseId,
619
+ traceId,
620
+ idempotencyKey: `tool-requested:${turn.id}:${toolUseId}`,
621
+ },
622
+ );
623
+ return;
624
+ }
625
+ if (event?.type === "tool_result") {
626
+ const toolUseId = safeId(
627
+ event.id,
628
+ `tool:${turn.id}:${turn.revision + 1}`,
629
+ );
630
+ const itemId =
631
+ turn.legacyItemIds.get(toolUseId) || `${turn.id}:tool:${toolUseId}`;
632
+ await this._notify(
633
+ "tool/result",
634
+ {
635
+ threadId: turn.threadId,
636
+ turnId: turn.id,
637
+ itemId,
638
+ toolUseId,
639
+ tool: event.tool,
640
+ isError: event.is_error === true,
641
+ error: event.error ?? null,
642
+ result: event.result ?? null,
643
+ permissionDecision: event.permission_decision ?? null,
644
+ },
645
+ {
646
+ threadId: turn.threadId,
647
+ turnId: turn.id,
648
+ itemId,
649
+ toolUseId,
650
+ traceId,
651
+ idempotencyKey: `tool-result:${turn.id}:${toolUseId}`,
652
+ },
653
+ );
654
+ return;
655
+ }
656
+ if (event?.type === "approval_request") {
657
+ turn.status = "waiting_approval";
658
+ turn.revision += 1;
659
+ const request = this._approvalRequest(turn, event);
660
+ await this._notify(
661
+ "approval/requested",
662
+ { request },
663
+ {
664
+ threadId: turn.threadId,
665
+ turnId: turn.id,
666
+ itemId: request.binding.itemId,
667
+ approvalId: request.id,
668
+ traceId,
669
+ idempotencyKey: `approval-requested:${turn.id}:${request.id}`,
670
+ },
671
+ );
672
+ return;
673
+ }
674
+ if (event?.type === "approval_resolved") {
675
+ turn.status = "running";
676
+ turn.revision += 1;
677
+ await this._notify(
678
+ "approval/resolved",
679
+ {
680
+ threadId: turn.threadId,
681
+ turnId: turn.id,
682
+ approvalId: safeId(event.id, `${turn.id}:approval`),
683
+ approved: event.approved === true,
684
+ via: event.via || "unknown",
685
+ },
686
+ {
687
+ threadId: turn.threadId,
688
+ turnId: turn.id,
689
+ approvalId: safeId(event.id, `${turn.id}:approval`),
690
+ traceId,
691
+ idempotencyKey: `approval-resolved:${turn.id}:${event.id}:${event.via}`,
692
+ },
693
+ );
694
+ return;
695
+ }
696
+ if (event?.type === "plan_update") {
697
+ await this._notify(
698
+ "item/completed",
699
+ {
700
+ item: {
701
+ id: `${turn.id}:plan:${event.plan_version || turn.revision + 1}`,
702
+ threadId: turn.threadId,
703
+ turnId: turn.id,
704
+ kind: "plan",
705
+ status: "completed",
706
+ content: event,
707
+ createdAt: new Date(this.now()).toISOString(),
708
+ completedAt: new Date(this.now()).toISOString(),
709
+ },
710
+ },
711
+ {
712
+ threadId: turn.threadId,
713
+ turnId: turn.id,
714
+ itemId: `${turn.id}:plan:${event.plan_version || turn.revision + 1}`,
715
+ traceId,
716
+ },
717
+ );
718
+ }
719
+ }
720
+
721
+ _approvalRequest(turn, event) {
722
+ const approvalId = safeId(event.id, `${turn.id}:approval`);
723
+ const itemId = `${turn.id}:approval:${approvalId}`;
724
+ const operation = {
725
+ tool: event.tool ?? null,
726
+ command: event.command ?? null,
727
+ rule: event.rule ?? null,
728
+ legacyBinding: event.binding ?? null,
729
+ };
730
+ return {
731
+ id: approvalId,
732
+ binding: {
733
+ threadId: turn.threadId,
734
+ turnId: turn.id,
735
+ itemId,
736
+ attemptId: null,
737
+ operationDigest: digest(operation),
738
+ policyDigest: digest({ rule: event.rule, risk: event.risk }),
739
+ workspaceDigest: digest({ cwd: this.kernel.cwd || process.cwd() }),
740
+ cwd: this.kernel.cwd || process.cwd(),
741
+ nonce: approvalId,
742
+ expiresAt: new Date(this.now() + this.requestTimeoutMs).toISOString(),
743
+ },
744
+ operation,
745
+ risk: ["low", "medium", "high", "critical"].includes(event.risk)
746
+ ? event.risk
747
+ : "high",
748
+ reason: event.reason || "The Agent Kernel requires approval",
749
+ requestedPermissions: [],
750
+ };
751
+ }
752
+
753
+ async _requestApproval(turn, event) {
754
+ const request = this._approvalRequest(turn, event);
755
+ const result = await this._requestClient("approval/decide", { request });
756
+ const decision = requireObject(result, "approval decision");
757
+ const allowed = new Set([
758
+ "acceptOnce",
759
+ "acceptForTurn",
760
+ "acceptForSession",
761
+ "decline",
762
+ "cancel",
763
+ ]);
764
+ if (!allowed.has(decision.kind)) {
765
+ throw new JsonRpcError(
766
+ JSON_RPC_ERROR.INVALID_PARAMS,
767
+ "client returned an invalid approval decision",
768
+ );
769
+ }
770
+ return decision;
771
+ }
772
+
773
+ async _completeTurn(turn, status, result) {
774
+ if (TERMINAL_TURN_STATUSES.has(turn.status)) return;
775
+ if (turn.assistantStarted) {
776
+ const itemId = `${turn.id}:assistant`;
777
+ await this._notify(
778
+ "item/completed",
779
+ {
780
+ item: {
781
+ id: itemId,
782
+ threadId: turn.threadId,
783
+ turnId: turn.id,
784
+ kind: "assistant_message",
785
+ status:
786
+ status === "completed"
787
+ ? "completed"
788
+ : status === "interrupted"
789
+ ? "cancelled"
790
+ : "failed",
791
+ content: { text: result?.result || "" },
792
+ createdAt: turn.createdAt,
793
+ completedAt: new Date(this.now()).toISOString(),
794
+ },
795
+ },
796
+ {
797
+ threadId: turn.threadId,
798
+ turnId: turn.id,
799
+ itemId,
800
+ idempotencyKey: `assistant-completed:${turn.id}`,
801
+ },
802
+ );
803
+ }
804
+ turn.status = status;
805
+ turn.completedAt = new Date(this.now()).toISOString();
806
+ turn.revision += 1;
807
+ const event = this.store.append({
808
+ threadId: turn.threadId,
809
+ turnId: turn.id,
810
+ eventType: `turn.${status}`,
811
+ idempotencyKey: `turn-terminal:${turn.id}`,
812
+ payload: {
813
+ status,
814
+ result: result?.result ?? null,
815
+ error: result?.error ?? null,
816
+ code: result?.code ?? null,
817
+ },
818
+ });
819
+ turn.terminalEvidence = {
820
+ status:
821
+ status === "completed"
822
+ ? "succeeded"
823
+ : status === "interrupted"
824
+ ? "cancelled"
825
+ : "failed",
826
+ eventDigest: event.hash,
827
+ outputDigest:
828
+ typeof result?.result === "string" ? digest(result.result) : null,
829
+ artifactIds: [],
830
+ commit: null,
831
+ testReceiptIds: [],
832
+ };
833
+ await this.send(
834
+ rpcNotification("turn/completed", {
835
+ turn: turnProjection(turn),
836
+ error:
837
+ status === "failed"
838
+ ? {
839
+ code: result?.code || "CC_AGENT_TURN_FAILED",
840
+ message: String(result?.error || "Agent turn failed").slice(
841
+ 0,
842
+ 4096,
843
+ ),
844
+ }
845
+ : null,
846
+ }),
847
+ );
848
+ await this._notify("thread/updated", {
849
+ thread: this.store.resume(turn.threadId),
850
+ });
851
+ }
852
+
853
+ async _turnInterrupt(rawParams) {
854
+ const params = requireObject(rawParams);
855
+ const threadId = requiredString(params.threadId, "threadId");
856
+ const turnId = requiredString(params.turnId, "turnId");
857
+ const active = this.activeTurns.get(threadId);
858
+ if (!active || active.turn.id !== turnId) {
859
+ const known = this.turns.get(turnId);
860
+ if (known && TERMINAL_TURN_STATUSES.has(known.status)) {
861
+ return { turn: turnProjection(known), alreadyTerminal: true };
862
+ }
863
+ throw new JsonRpcError(
864
+ JSON_RPC_ERROR.NOT_FOUND,
865
+ "active turn was not found",
866
+ );
867
+ }
868
+ active.turn.interruptRequested = true;
869
+ await this.kernel.interruptTurn(threadId, turnId);
870
+ let timer;
871
+ const timeout = new Promise((_, reject) => {
872
+ timer = setTimeout(
873
+ () =>
874
+ reject(
875
+ new JsonRpcError(
876
+ JSON_RPC_ERROR.INTERRUPTED,
877
+ "turn interruption has not physically settled",
878
+ { retry_after_ms: 100 },
879
+ ),
880
+ ),
881
+ this.interruptSettlementMs,
882
+ );
883
+ timer.unref?.();
884
+ });
885
+ try {
886
+ await Promise.race([active.settled, timeout]);
887
+ } finally {
888
+ clearTimeout(timer);
889
+ }
890
+ return { turn: turnProjection(active.turn), physicallySettled: true };
891
+ }
892
+
893
+ status() {
894
+ return Object.freeze({
895
+ initialized: this.initialized,
896
+ client: this.client,
897
+ negotiated: this.negotiated,
898
+ queue: this.requestQueue.snapshot(),
899
+ activeTurns: this.activeTurns.size,
900
+ pendingClientRequests: this.pendingClientRequests.size,
901
+ closed: this.closed,
902
+ });
903
+ }
904
+
905
+ async close() {
906
+ if (this.closed) return;
907
+ this.closed = true;
908
+ this.requestQueue.close();
909
+ for (const pending of this.pendingClientRequests.values()) {
910
+ clearTimeout(pending.timer);
911
+ pending.reject(
912
+ new JsonRpcError(
913
+ JSON_RPC_ERROR.INTERRUPTED,
914
+ "App Server connection closed",
915
+ ),
916
+ );
917
+ }
918
+ this.pendingClientRequests.clear();
919
+ await this.kernel.close?.();
920
+ await Promise.all(this.workers);
921
+ }
922
+ }