chainlesschain 0.166.112 → 0.166.115

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 (176) hide show
  1. package/README.md +16 -6
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-sOceeExN.js → AIOps-BsoSoDNb.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-BQs-vRUr.js → ActionButton-Dcx7Gm5t.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-sfAHHMhA.js → Analytics-S1hsBIz3.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-0gAvE0c8.js → AppLayout-B2j067qM.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-Dh9s2Qmd.js → Artifacts-D5iGV9ic.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-CITm26SU.js → Audit-C8ChVvXu.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-BiMbBOh0.js → BackgroundAgents-Cg97AAJS.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-CNpnhI3h.js → Backup-CyxU9IUg.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-BI_lltkp.js → BaseInput-CKlWjA8I.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-BgfB88xY.js → Chat-C-EqZza6.js} +5 -5
  16. package/src/assets/web-panel/assets/ChatBubbleRenderer-BRFiXKPX.js +1 -0
  17. package/src/assets/web-panel/assets/{Checkbox-4xy9rS3j.js → Checkbox-kkI13tSJ.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-D93h9ihS.js → Codegen-NlqEDwHK.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BRf3STY-.js → Col-CfepEu79.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-f187ziSL.js → Community-BRo0qLXM.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-D4hXWuo1.js → Compact-BMu8TP0E.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-C6cYKI6H.js → Compliance-B7Q3Moef.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-C1i1f8LD.js → Cowork-BVwt7Xmc.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BacCE4LA.js → Cron-CgBv20Qy.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-DXztxOoH.js → Crosschain-CwcSBJk2.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-rpf4HN8x.js → DID-d20rCiIm.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-h4bgtH7L.js → Dashboard-PpZaUYpS.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-CyQnoHmu.js → Dropdown-DjsLdX_Z.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-FCtp4AcB.js → EmailListRenderer-CaPkrtxn.js} +1 -1
  30. package/src/assets/web-panel/assets/{EvolutionSettings-JbISHTWR.js → EvolutionSettings-DDY8cQnE.js} +1 -1
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DCIQLyfK.js → FamilyGuardDashboard-VQavMjyj.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-DDbFpwja.js → Federation-D0GvKQgI.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-CXF0jtXb.js → FormItemContext-Dl_TWHmb.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-CUaNfAM0.js → GenericCardRenderer-Bny2uagG.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-Bhoz2gDf.js → Git-B01KXq47.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-FiiXHe8e.js → Governance-F4c4Vwo1.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-brpTW2Q3.js → Inference-_025Q-8K.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-BCWP-f6k.js → KnowledgeGraph-DwbTmQb1.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-CxYcduoK.js → Logs-CnNo0hCm.js} +2 -2
  40. package/src/assets/web-panel/assets/{MarkdownRenderer-12vD1nC1.js → MarkdownRenderer-D8_iZief.js} +1 -1
  41. package/src/assets/web-panel/assets/{Marketplace-DANdmW7l.js → Marketplace-Dkz5bv7F.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-Bi_EKkb5.js → McpTools-CBtRwDW_.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-DecoMbYw.js → Memory-Y9jnyTb7.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-DrOhFKp0.js → MobileBridge-Br1urkOz.js} +1 -1
  45. package/src/assets/web-panel/assets/MobileProjects-DnsyuerX.js +1 -0
  46. package/src/assets/web-panel/assets/{Mtc-SLPII1iw.js → Mtc-he3ZzieF.js} +4 -4
  47. package/src/assets/web-panel/assets/{MtcAudit-DWoVORYy.js → MtcAudit-BadphAuN.js} +2 -2
  48. package/src/assets/web-panel/assets/{Multisig-Dxpnmq-m.js → Multisig-3_pVLymK.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-kNTpNqCP.js → NLProgramming-DmtmHfsI.js} +1 -1
  50. package/src/assets/web-panel/assets/Notes-CE4L2O55.js +7 -0
  51. package/src/assets/web-panel/assets/{NotificationSettings-D3Qt8Gkl.js → NotificationSettings-B7QmfJn9.js} +1 -1
  52. package/src/assets/web-panel/assets/OrderTableRenderer-O0wRSyJ0.js +1 -0
  53. package/src/assets/web-panel/assets/{Organization-EhRl4JoJ.js → Organization-BrlMSsyT.js} +2 -2
  54. package/src/assets/web-panel/assets/{Overflow-BTt5C_5W.js → Overflow-CY4ULPrZ.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-CeR2jyb0.js → P2P-kIANZYKd.js} +2 -2
  56. package/src/assets/web-panel/assets/{PdhVaultBrowser-BzD3OHWq.js → PdhVaultBrowser-DEgB4rvF.js} +5 -5
  57. package/src/assets/web-panel/assets/{Permissions-BOPyeRlz.js → Permissions-CWxywRp5.js} +3 -3
  58. package/src/assets/web-panel/assets/{PersonalDataHub-DcmNNsfO.js → PersonalDataHub-DAg7iCbr.js} +4 -4
  59. package/src/assets/web-panel/assets/{Pipeline-DuL3kwWC.js → Pipeline-Brc6ONnR.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-CkQRdlSU.js → Privacy-CcinYTL8.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-moE_vQ1P.js → ProjectInit-Bz8XEsom.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-PnTWi4NQ.js → ProjectSettings-CsOMlSTZ.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-DZvbT3D7.js → Projects-BeSOM6wi.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-CkcrHYy9.js → Providers-CeunOQiH.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-nYNFGf_H.js → QrScannerModal-CN2a2_Fb.js} +1 -1
  66. package/src/assets/web-panel/assets/QuickAsk-CVeLif3e.js +1 -0
  67. package/src/assets/web-panel/assets/{Recommend-CmYN_NaS.js → Recommend-1k9WOEGM.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-BuOdNEjA.js → RemoteSession-Q8UCqIMw.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-BgkheZou.js → Reputation-CkrCgXKM.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-DvA3klj1.js → Row-JFdvuTzi.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-CgkIL3PG.js → RssFeed-C74JHCmk.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-Cv2twJdL.js → Search-DrJiVO1T.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-XrD2UNU6.js → Security-BJzNlSJq.js} +3 -3
  74. package/src/assets/web-panel/assets/{Services-LujCntCK.js → Services-D4TLMnJm.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-ZZCOsxPX.js → Skeleton-cswTDqhh.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-BEXGkOrU.js → Skills-Bv0rM1BS.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-CIqmCgdD.js → Sla-D9rE7hNh.js} +1 -1
  78. package/src/assets/web-panel/assets/SpeechSettings-Bfhvin8B.js +1 -0
  79. package/src/assets/web-panel/assets/{SyncSettings-BVGgUs0p.js → SyncSettings-BwnMG8kq.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-C1lh68l5.js → Tasks-wJvU24cB.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-BG4zNAFx.js → Templates-CZbRU3tN.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-t2o6i5fY.js → Tenant-_jbpFJPZ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-CH8UDdVO.js → Terminal-BzNm5WKZ.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-B5sYY1IO.js → TimelineRenderer-bmybWjuS.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-D0y7QkG5.js → Tokens-o1WDcqVQ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-BA2nKko0.js → Trigger-BB1D6sIB.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-CrFKCQIs.js → Trust-CLxdCfy2.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-B3x_-Zun.js → UkeySign-B9LS6vS0.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-TEwxbLH6.js → VideoEditing-Bg0-wxjz.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-Larp5rrt.js → Wallet-Bw0G2F6P.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-DfWbEIZe.js → WebAuthn-irEiNY9b.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-8LKiKExY.js → WorkflowEditor-Bh08FbJs.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-ByxYOvYW.js → chat-De-FPjVT.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-Dh4yAClh.js → colors-ygdyvinU.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-f5n2j1WZ.js → compact-item-BQ9jH9KV.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-DIprYuXL.js → createContext-BFuVSyox.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-c2I2Rn4_.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-CvYeap2I.js → hasIn-i19GtR1P.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DlTwZIeX.js → index-28DjsABm.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CJjc8ObW.js → index-4HoFz1MA.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-xU0eHLIH.js → index-B1IsP_JO.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bk8oWRk6.js → index-B2LieObB.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BlImvgb7.js → index-BBYiCI1d.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DlJCDpeN.js → index-BGMxK155.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Du435yzt.js → index-BKblSCL9.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BS_dpgzu.js → index-BKlU5VZy.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CTqJNul4.js → index-BLMtCKAY.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BvyFQF8I.js → index-BPUCvc8i.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CzabQZ7I.js → index-BVhtukYX.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BsU46c7D.js → index-BaaydJcC.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DshB3-Up.js → index-Bhf-MuUh.js} +25 -25
  112. package/src/assets/web-panel/assets/{index-uDiVjYrS.js → index-Bw5pTpnI.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CcvA1nae.js → index-C61w3omz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-7eG1J-GT.js → index-CBVcGaxk.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BoayvJ48.js → index-CMNjaSir.js} +1 -1
  116. package/src/assets/web-panel/assets/index-CTs4dYcV.js +1 -0
  117. package/src/assets/web-panel/assets/{index-CD0s5ESV.js → index-CWfgeaG4.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B0AXUhiJ.js → index-CXrk8OYO.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BaXSBxy-.js → index-CgoSgmjK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DaZVtafQ.js → index-CpdXhhMY.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-bt-0zMmH.js → index-Cq9k4-Ws.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C5aH2Pj2.js → index-DAboGZTw.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DGyTzElU.js → index-DL7ewjD2.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DI8jC7gQ.js → index-DS1IwwGL.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-c_CxUQl3.js → index-DTY20pWu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DlULGx8u.js → index-DVnFgasM.js} +1 -1
  127. package/src/assets/web-panel/assets/index-DeuvaW15.js +1 -0
  128. package/src/assets/web-panel/assets/{index-6VEWxOyC.js → index-DgjLaB7k.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DjvON8oh.js → index-DlwxUZQL.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CpR0zD38.js → index-DuuOX5u3.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DUj-sL9U.js → index-IXkif6J_.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-hwbaD3Nk.js → index-We2nK3ju.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CIZQtz8G.js → index-_9tUBcLm.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-fpWv4u3Z.js → index-cDJsyAkI.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-3ROY-lYB.js → index-l5-J0jHx.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-C2RGykST.js → index-m8waSAyv.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-DmcAL4hd.js → index-vXtH3o6O.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-DRuE92W6.js → initDefaultProps-B_EZHeqN.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-6Sgma5Vd.js → motion-BrVb_Gy1.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-BbQsH8sb.js → move-CKxVRL_J.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BdC4INal.js → mtc-parser-C80rvQ1J.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-DeC5m_bi.js → omit-De5vD7BR.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-DuehpBws.js → pickAttrs-BTyEDjth.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-CJEhv2ij.js → placementArrow-BWR94AjR.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-Dyw3lDGr.js → responsiveObserve-1g-Vnibk.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-BMaLXTbS.js → slide-Dmg6dYuF.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-c1K_SdJH.js → statusUtils-Ddj6rSab.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-CefBry8k.js → styleChecker-DyP1mPDf.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-Dj2IsZpo.js → useFlexGapSupport-D3d_0YuY.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-RLfB3yqb.js → useFs-M3vq-gE2.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-DbRVH_NA.js → usePersonalDataHub-GtP2TgtK.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-DTn_QVJz.js → vnode-BurE2ATQ.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-DuFR0E7i.js → zoom-Ch-ViiDe.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/data/changelog.json +42 -3
  156. package/src/gateways/ws/llm-chat-protocol.js +124 -41
  157. package/src/gateways/ws/message-dispatcher.js +2 -0
  158. package/src/lib/evolution/evolution-deployment-config.js +15 -6
  159. package/src/lib/evolution/evolution-deployment-loader.js +63 -5
  160. package/src/lib/evolution/evolution-evidence-projector.js +48 -10
  161. package/src/lib/evolution/governed-model-turn.js +2 -2
  162. package/src/lib/evolution/local-ui-evolution-deployment.js +141 -55
  163. package/src/lib/evolution/local-ui-evolution-host.mjs +26 -8
  164. package/src/lib/evolution/local-ui-evolution-runtime.js +46 -23
  165. package/src/lib/evolution/opaque-hex-id.js +8 -0
  166. package/src/lib/read-file-loop-guard.js +2 -2
  167. package/src/runtime/agent-core.js +28 -19
  168. package/src/assets/web-panel/assets/ChatBubbleRenderer-BDP3fGoz.js +0 -1
  169. package/src/assets/web-panel/assets/MobileProjects-rRwP16dN.js +0 -1
  170. package/src/assets/web-panel/assets/Notes-D1uXCfVd.js +0 -7
  171. package/src/assets/web-panel/assets/OrderTableRenderer-qvaah2-m.js +0 -1
  172. package/src/assets/web-panel/assets/QuickAsk-53WNo7xT.js +0 -1
  173. package/src/assets/web-panel/assets/SpeechSettings-DkVbwJox.js +0 -1
  174. package/src/assets/web-panel/assets/devWarning-DRIge35Z.js +0 -1
  175. package/src/assets/web-panel/assets/index-Bq24rTYx.js +0 -1
  176. package/src/assets/web-panel/assets/index-D2f18WsK.js +0 -1
@@ -38,12 +38,35 @@ import {
38
38
  streamOpenAI,
39
39
  streamAnthropic,
40
40
  streamGemini,
41
+ STREAM_STALL_MS,
41
42
  } from "../../lib/chat-core.js";
42
43
  import { BUILT_IN_PROVIDERS } from "../../lib/llm-providers.js";
43
44
  import { resolveLlmCreds } from "./llm-creds.js";
44
45
  import { prepareGovernedModelTurn } from "../../lib/evolution/governed-model-turn.js";
45
46
  import { normalizeOllamaBaseUrl } from "../../lib/ollama-base-url.js";
46
47
  import { normalizeProviderBaseUrl } from "../../lib/provider-protocol.js";
48
+ import { raceWithAbort } from "../../lib/abort-utils.js";
49
+
50
+ const activeChats = new WeakMap();
51
+ const CHAT_SETUP_TIMEOUT_MS = 60_000;
52
+
53
+ /** A request id is scoped to its originating socket, never another client. */
54
+ export function cancelLlmChat(ws, id) {
55
+ activeChats.get(ws)?.get(id)?.abort(new Error("Chat cancelled"));
56
+ }
57
+
58
+ async function waitForChatPhase(operation, controller, phase) {
59
+ const timer = setTimeout(
60
+ () => controller.abort(new Error(`Chat ${phase} timed out after 60s`)),
61
+ CHAT_SETUP_TIMEOUT_MS,
62
+ );
63
+ timer.unref?.();
64
+ try {
65
+ return await raceWithAbort(operation, controller.signal);
66
+ } finally {
67
+ clearTimeout(timer);
68
+ }
69
+ }
47
70
 
48
71
  function validateMessages(messages) {
49
72
  if (!Array.isArray(messages) || messages.length === 0)
@@ -77,7 +100,17 @@ export async function handleLlmChat(server, id, ws, message) {
77
100
  return;
78
101
  }
79
102
 
80
- const creds = resolveLlmCreds(server, message);
103
+ let creds;
104
+ try {
105
+ creds = resolveLlmCreds(server, message);
106
+ } catch (error) {
107
+ send({
108
+ type: `${topic}.result`,
109
+ ok: false,
110
+ error: error?.message || String(error),
111
+ });
112
+ return;
113
+ }
81
114
  if (!creds || !creds.provider) {
82
115
  send({
83
116
  type: `${topic}.result`,
@@ -100,6 +133,7 @@ export async function handleLlmChat(server, id, ws, message) {
100
133
 
101
134
  let accumulator = "";
102
135
  const onToken = (token) => {
136
+ controller.signal.throwIfAborted();
103
137
  if (typeof token !== "string" || token.length === 0) return;
104
138
  accumulator += token;
105
139
  send({
@@ -114,59 +148,99 @@ export async function handleLlmChat(server, id, ws, message) {
114
148
  };
115
149
 
116
150
  const controller = new AbortController();
151
+ let requests = null;
152
+ if (ws && (typeof ws === "object" || typeof ws === "function")) {
153
+ requests = activeChats.get(ws);
154
+ if (!requests) activeChats.set(ws, (requests = new Map()));
155
+ if (requests.has(id)) {
156
+ send({
157
+ type: `${topic}.result`,
158
+ ok: false,
159
+ error: "Chat request is already active",
160
+ });
161
+ return;
162
+ }
163
+ requests.set(id, controller);
164
+ }
117
165
  const disconnect = () =>
118
166
  controller.abort(new Error("Chat connection closed"));
167
+ const onStall = (elapsedMs, timeoutMs) => {
168
+ if (controller.signal.aborted) return;
169
+ send({
170
+ type: `${topic}.status`,
171
+ status: {
172
+ stage: "waiting",
173
+ provider: creds.provider,
174
+ model: creds.model,
175
+ elapsedMs,
176
+ timeoutMs,
177
+ },
178
+ });
179
+ };
119
180
  ws?.once?.("close", disconnect);
120
181
  try {
121
- const turn = await prepareGovernedModelTurn(
122
- server.evolutionCompositionFactory ?? null,
123
- {
182
+ const turn = await waitForChatPhase(
183
+ prepareGovernedModelTurn(server.evolutionCompositionFactory ?? null, {
124
184
  mode: "ws-llm-chat",
125
185
  messages: message.messages,
126
186
  signal: controller.signal,
127
- },
187
+ }),
188
+ controller,
189
+ "initialization",
128
190
  );
129
191
  const streamOptions = {
130
192
  requireCompletion: turn.governed,
131
193
  signal: controller.signal,
132
194
  };
195
+ // Tell the browser the configured deadline even when waiting hints are
196
+ // disabled, so a slow local model gets its full loading budget.
197
+ onStall(0, STREAM_STALL_MS);
133
198
  const baseUrl =
134
199
  creds.provider === "ollama"
135
200
  ? normalizeOllamaBaseUrl(creds.baseUrl)
136
201
  : normalizeProviderBaseUrl(creds.baseUrl);
137
202
  if (creds.provider === "ollama") {
138
- await streamOllama(
139
- turn.messages,
140
- creds.model,
141
- baseUrl,
142
- onToken,
143
- onUsage,
144
- undefined,
145
- streamOptions,
203
+ await raceWithAbort(
204
+ streamOllama(
205
+ turn.messages,
206
+ creds.model,
207
+ baseUrl,
208
+ onToken,
209
+ onUsage,
210
+ onStall,
211
+ streamOptions,
212
+ ),
213
+ controller.signal,
146
214
  );
147
215
  } else if (creds.provider === "anthropic") {
148
216
  const url = baseUrl;
149
- await streamAnthropic(
150
- turn.messages,
151
- creds.model,
152
- url,
153
- creds.apiKey,
154
- onToken,
155
- onUsage,
156
- undefined,
157
- streamOptions,
217
+ await raceWithAbort(
218
+ streamAnthropic(
219
+ turn.messages,
220
+ creds.model,
221
+ url,
222
+ creds.apiKey,
223
+ onToken,
224
+ onUsage,
225
+ onStall,
226
+ streamOptions,
227
+ ),
228
+ controller.signal,
158
229
  );
159
230
  } else if (creds.provider === "gemini") {
160
231
  const url = baseUrl;
161
- await streamGemini(
162
- turn.messages,
163
- creds.model,
164
- url,
165
- creds.apiKey,
166
- onToken,
167
- onUsage,
168
- undefined,
169
- streamOptions,
232
+ await raceWithAbort(
233
+ streamGemini(
234
+ turn.messages,
235
+ creds.model,
236
+ url,
237
+ creds.apiKey,
238
+ onToken,
239
+ onUsage,
240
+ onStall,
241
+ streamOptions,
242
+ ),
243
+ controller.signal,
170
244
  );
171
245
  } else {
172
246
  // OpenAI-compatible — covers openai / volcengine / deepseek / dashscope /
@@ -174,19 +248,26 @@ export async function handleLlmChat(server, id, ws, message) {
174
248
  // BUILT_IN_PROVIDERS already applied above.
175
249
  const def = BUILT_IN_PROVIDERS[creds.provider];
176
250
  const url = baseUrl || def?.baseUrl;
177
- await streamOpenAI(
178
- turn.messages,
179
- creds.model,
180
- url,
181
- creds.apiKey,
182
- onToken,
183
- onUsage,
184
- undefined,
185
- streamOptions,
251
+ await raceWithAbort(
252
+ streamOpenAI(
253
+ turn.messages,
254
+ creds.model,
255
+ url,
256
+ creds.apiKey,
257
+ onToken,
258
+ onUsage,
259
+ onStall,
260
+ streamOptions,
261
+ ),
262
+ controller.signal,
186
263
  );
187
264
  }
188
265
 
189
- await turn.complete(accumulator);
266
+ await waitForChatPhase(
267
+ turn.complete(accumulator),
268
+ controller,
269
+ "completion",
270
+ );
190
271
  send({
191
272
  type: `${topic}.result`,
192
273
  ok: true,
@@ -205,6 +286,8 @@ export async function handleLlmChat(server, id, ws, message) {
205
286
  error: err?.message || String(err),
206
287
  });
207
288
  } finally {
289
+ requests?.delete(id);
290
+ if (requests?.size === 0) activeChats.delete(ws);
208
291
  ws?.removeListener?.("close", disconnect);
209
292
  }
210
293
  }
@@ -30,6 +30,7 @@ import {
30
30
  handleRemoteSessionTerminal,
31
31
  } from "./remote-session-protocol.js";
32
32
  import { RemoteCommandLedger } from "../../harness/remote-command-ledger.js";
33
+ import { cancelLlmChat } from "./llm-chat-protocol.js";
33
34
 
34
35
  /**
35
36
  * Reconnect-safe command execution. When a message carries a stable
@@ -293,6 +294,7 @@ export function createWsMessageDispatcher(server) {
293
294
  "chat.intent.classify-followup": () =>
294
295
  server._handleChatIntentClassifyFollowup(id, ws, message),
295
296
  "llm.chat": () => server._handleLlmChat(id, ws, message),
297
+ "llm.chat.cancel": () => cancelLlmChat(ws, id),
296
298
  };
297
299
 
298
300
  // Static core routes take precedence and are dispatched directly. The
@@ -200,6 +200,16 @@ export async function configureEvolutionDeployment(
200
200
  options,
201
201
  );
202
202
  updateEvolutionDeploymentProfile((current) => {
203
+ if (
204
+ options.expectedProfile &&
205
+ JSON.stringify(current) !== JSON.stringify(options.expectedProfile)
206
+ ) {
207
+ const error = new Error(
208
+ "Evolution deployment profile changed during local migration",
209
+ );
210
+ error.code = "EVOLUTION_DEPLOYMENT_PROFILE_CHANGED";
211
+ throw error;
212
+ }
203
213
  if (options.expectedProfileAbsent === true && current) {
204
214
  const error = new Error(
205
215
  "Evolution deployment profile was configured during local initialization",
@@ -272,12 +282,11 @@ export async function configureEvolutionDeployment(
272
282
  revisionFloors,
273
283
  activeTrustRootDigest: verified.descriptor.trustRootDigest,
274
284
  revokedDescriptorRevisions,
275
- deploymentMode:
276
- preserveLocalMode
277
- ? "local"
278
- : preserveTestMode
279
- ? "test"
280
- : deploymentMode || "managed",
285
+ deploymentMode: preserveLocalMode
286
+ ? "local"
287
+ : preserveTestMode
288
+ ? "test"
289
+ : deploymentMode || "managed",
281
290
  testPrivateKeyPath:
282
291
  deploymentMode === "test"
283
292
  ? testPrivateKeyPath || null
@@ -622,7 +622,9 @@ async function loadBuiltInFactories(commandName) {
622
622
  import("./evolution-eval-process-supervisor.js"),
623
623
  import("./governed-skill-synthesis-attestor-trust-approval-client.js"),
624
624
  import("./governed-skill-synthesis-attestor-trust-operations-client.js"),
625
- import("./governed-skill-synthesis-attestor-trust-operations-cli-host.js"),
625
+ import(
626
+ "./governed-skill-synthesis-attestor-trust-operations-cli-host.js"
627
+ ),
626
628
  ]);
627
629
  factories.createEvolutionWorkbenchCliHost = createEvolutionWorkbenchCliHost;
628
630
  factories.createEvolutionWorkbenchReviewRuntime =
@@ -1027,9 +1029,39 @@ export async function loadEvolutionDeploymentCommandDependencies(
1027
1029
  importModule = (url) => import(url),
1028
1030
  additionalFactories = {},
1029
1031
  initializeDefaultLocalUi = false,
1032
+ initializeDefaultLocalModels = true,
1030
1033
  } = {},
1031
1034
  ) {
1032
1035
  if (!SUPPORTED_COMMANDS.has(commandName)) return null;
1036
+ const localModelDependencies = () =>
1037
+ dependencies(
1038
+ {
1039
+ evolutionCompositionFactory: async (context) => {
1040
+ const { initializeDefaultLocalUiEvolutionDeployment } = await import(
1041
+ "./local-ui-evolution-deployment.js"
1042
+ );
1043
+ await initializeDefaultLocalUiEvolutionDeployment({ env, cwd });
1044
+ const admitted = await loadEvolutionDeploymentCommandDependencies(
1045
+ commandName,
1046
+ {
1047
+ env,
1048
+ cwd,
1049
+ read,
1050
+ resolveRealPath,
1051
+ importModule,
1052
+ additionalFactories,
1053
+ initializeDefaultLocalModels: false,
1054
+ },
1055
+ );
1056
+ if (typeof admitted?.evolutionCompositionFactory !== "function")
1057
+ throw new Error(
1058
+ "Local model command requires an authenticated Evolution deployment",
1059
+ );
1060
+ return admitted.evolutionCompositionFactory(context);
1061
+ },
1062
+ },
1063
+ commandName,
1064
+ );
1033
1065
  let descriptorPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_DESCRIPTOR;
1034
1066
  let trustRootPath = env.CHAINLESSCHAIN_EVOLUTION_DEPLOYMENT_TRUST_ROOT;
1035
1067
  if (
@@ -1061,6 +1093,15 @@ export async function loadEvolutionDeploymentCommandDependencies(
1061
1093
  "Saved evolution deployment profile is disabled",
1062
1094
  );
1063
1095
  }
1096
+ if (
1097
+ initializeDefaultLocalModels &&
1098
+ ["agent", "ask", "chat"].includes(commandName) &&
1099
+ !saved.profile
1100
+ ) {
1101
+ // Registration/help is read-only. Bootstrap only when a real command
1102
+ // requests its authenticated composition, then re-read current policy.
1103
+ return localModelDependencies();
1104
+ }
1064
1105
  if (!saved.error && saved.profile?.enabled) {
1065
1106
  descriptorPath = saved.profile.descriptorPath;
1066
1107
  trustRootPath = saved.profile.trustRootPath;
@@ -1111,19 +1152,35 @@ export async function loadEvolutionDeploymentCommandDependencies(
1111
1152
  const localProfile =
1112
1153
  fromSavedProfile && saved.profile.deploymentMode === "local";
1113
1154
  if (localProfile) {
1155
+ const generation = saved.profile.descriptorPath.endsWith(
1156
+ "deployment-v2.json",
1157
+ )
1158
+ ? 2
1159
+ : 1;
1114
1160
  const localHostPath = join(
1115
1161
  dirname(saved.filePath),
1116
1162
  "local-ui-host",
1117
- "ui-host.mjs",
1163
+ generation === 2 ? "ui-host-v2.mjs" : "ui-host.mjs",
1118
1164
  );
1119
1165
  const canonicalLocalHostPath = await realpath(localHostPath);
1120
1166
  if (
1121
- descriptor.commands.length !== 1 ||
1122
- descriptor.commands[0] !== "ui" ||
1167
+ !(
1168
+ ["ui"].join(",") === descriptor.commands.join(",") ||
1169
+ "agent,ask,chat,ui" === descriptor.commands.join(",")
1170
+ ) ||
1123
1171
  verified.modulePath !== canonicalLocalHostPath
1124
1172
  ) {
1125
- throw new Error("Local Evolution deployment is not the protected UI host");
1173
+ throw new Error(
1174
+ "Local Evolution deployment is not the protected UI host",
1175
+ );
1126
1176
  }
1177
+ if (
1178
+ initializeDefaultLocalModels &&
1179
+ ["agent", "ask", "chat", "ui"].includes(commandName) &&
1180
+ descriptor.commands.length === 1 &&
1181
+ descriptor.commands[0] === "ui"
1182
+ )
1183
+ return localModelDependencies();
1127
1184
  }
1128
1185
  if (!descriptor.commands.includes(commandName)) {
1129
1186
  return deniedAgentDependencies(
@@ -1154,6 +1211,7 @@ export async function loadEvolutionDeploymentCommandDependencies(
1154
1211
  createLocalUiEvolutionComposition: (options = {}) =>
1155
1212
  createLocalUiEvolutionComposition({
1156
1213
  ...options,
1214
+ commandName,
1157
1215
  localDirectory: join(dirname(saved.filePath), "local-ui-host"),
1158
1216
  env,
1159
1217
  cwd,
@@ -93,6 +93,8 @@ const TRUSTED_PAYLOAD_COMMITMENT_PURPOSE =
93
93
  const LEGACY_LOCAL_UI_TENANT_ID_PATTERN = /^local-ui-[a-f0-9]{32}$/u;
94
94
  const FORMATTED_LOCAL_UI_TENANT_ID_PATTERN =
95
95
  /^local-ui-(?:[a-f0-9]{8}-){3}[a-f0-9]{8}$/u;
96
+ const ENCODED_LOCAL_UI_TENANT_ID_PATTERN =
97
+ /^local-ui-(?:[a-p]{8}-){3}[a-p]{8}$/u;
96
98
 
97
99
  const INPUT_KEYS = new Set(["sourceEnvelope", "payload"]);
98
100
  const SOURCE_VERIFICATION_KEYS = new Set([
@@ -877,17 +879,32 @@ function normalizeId(value, label, maximum = 256) {
877
879
  return value;
878
880
  }
879
881
 
880
- function normalizeTenantId(value, label) {
881
- if (
882
+ function isLocalUiTenantId(value) {
883
+ return (
882
884
  typeof value === "string" &&
883
885
  (LEGACY_LOCAL_UI_TENANT_ID_PATTERN.test(value) ||
884
- FORMATTED_LOCAL_UI_TENANT_ID_PATTERN.test(value))
885
- ) {
886
- return value;
887
- }
886
+ FORMATTED_LOCAL_UI_TENANT_ID_PATTERN.test(value) ||
887
+ ENCODED_LOCAL_UI_TENANT_ID_PATTERN.test(value))
888
+ );
889
+ }
890
+
891
+ function normalizeTenantId(value, label) {
892
+ if (isLocalUiTenantId(value)) return value;
888
893
  return normalizeId(value, label);
889
894
  }
890
895
 
896
+ function normalizeLocalAttestationId(value, label, tenantId, suffix) {
897
+ // A bound local identity is already admitted as tenant metadata. Inspect its
898
+ // fixed reference structure without treating its random digits as content;
899
+ // preserve the original signed bytes and scan every other identifier fully.
900
+ const scanValue =
901
+ isLocalUiTenantId(tenantId) && value === `local-ui-${tenantId}-${suffix}`
902
+ ? `local-ui-local-identity-${suffix}`
903
+ : value;
904
+ normalizeId(scanValue, label);
905
+ return value;
906
+ }
907
+
891
908
  function normalizeDigest(
892
909
  value,
893
910
  label,
@@ -946,7 +963,6 @@ function normalizeOpaqueUri(value, { label, schemes, tenantId }) {
946
963
  `${label} is invalid`,
947
964
  );
948
965
  }
949
- assertNoKnownSecrets(value);
950
966
  if (
951
967
  /[?#@\\%\s]/u.test(value) ||
952
968
  hasUriControlOrWhitespace(value) ||
@@ -985,7 +1001,11 @@ function normalizeOpaqueUri(value, { label, schemes, tenantId }) {
985
1001
  parsed.port ||
986
1002
  parsed.search ||
987
1003
  parsed.hash ||
988
- parsed.hostname !== formatLocalUiTenantReferenceId(tenantId) ||
1004
+ (parsed.hostname !== formatLocalUiTenantReferenceId(tenantId) &&
1005
+ !(
1006
+ LEGACY_LOCAL_UI_TENANT_ID_PATTERN.test(tenantId) &&
1007
+ parsed.hostname === tenantId
1008
+ )) ||
989
1009
  !decodedPath.startsWith("/") ||
990
1010
  decodedPath
991
1011
  .split("/")
@@ -996,6 +1016,14 @@ function normalizeOpaqueUri(value, { label, schemes, tenantId }) {
996
1016
  `${label} is outside the tenant-scoped URI policy`,
997
1017
  );
998
1018
  }
1019
+ // Only the exact, bound local host is identity metadata. Keep the complete
1020
+ // original path in the scan, and never rewrite persisted URI/digest bytes.
1021
+ const localPrefix = `${scheme}://${parsed.hostname}`;
1022
+ const scanValue =
1023
+ isLocalUiTenantId(tenantId) && value.startsWith(`${localPrefix}/`)
1024
+ ? `${scheme}://local-ui-identity${value.slice(localPrefix.length)}`
1025
+ : value;
1026
+ assertNoKnownSecrets(scanValue);
999
1027
  return value;
1000
1028
  }
1001
1029
 
@@ -2291,8 +2319,18 @@ function validateAttestation(value, receipt) {
2291
2319
  "projection attestation is invalid or unbound",
2292
2320
  );
2293
2321
  }
2294
- normalizeId(value.keyId, "attestation keyId");
2295
- normalizeId(value.issuer, "attestation issuer");
2322
+ normalizeLocalAttestationId(
2323
+ value.keyId,
2324
+ "attestation keyId",
2325
+ receipt.tenantId,
2326
+ "attestation",
2327
+ );
2328
+ normalizeLocalAttestationId(
2329
+ value.issuer,
2330
+ "attestation issuer",
2331
+ receipt.tenantId,
2332
+ "evolution-projector",
2333
+ );
2296
2334
  return deepFreeze(cloneCanonical(value));
2297
2335
  }
2298
2336
 
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { types } from "node:util";
3
3
  import { captureAgentEvolutionRuntimeComposition } from "./agent-evolution-runtime-composition-brand.js";
4
4
  import { captureAgentEvolutionIngress } from "./agent-evolution-ingress.js";
5
- import { formatOpaqueHexId } from "./opaque-hex-id.js";
5
+ import { encodeOpaqueHexId } from "./opaque-hex-id.js";
6
6
 
7
7
  export function readEvolutionCompositionFactory(dependencies = {}) {
8
8
  if (
@@ -44,7 +44,7 @@ export async function prepareGovernedModelTurn(
44
44
  }
45
45
  if (typeof factory !== "function")
46
46
  throw new TypeError("Invalid evolution composition factory");
47
- const runId = `${mode}-${formatOpaqueHexId(
47
+ const runId = `${mode}-${encodeOpaqueHexId(
48
48
  randomUUID().replaceAll("-", ""),
49
49
  )}`;
50
50
  const composition = captureAgentEvolutionRuntimeComposition(