@skaile/workspaces 0.29.0 → 0.30.1

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 (472) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/{asset-feeds-2V7LZIG7.js → asset-feeds-IWOBYROH.js} +14 -13
  3. package/dist/asset-feeds-IWOBYROH.js.map +1 -0
  4. package/dist/asset-manager/index.js +12 -12
  5. package/dist/asset-manager/installer.js +11 -11
  6. package/dist/asset-manager/renderers.js +2 -2
  7. package/dist/asset-manager/src/index.d.ts +27 -0
  8. package/dist/asset-manager/src/index.d.ts.map +1 -1
  9. package/dist/asset-manager/src/install-agent.d.ts.map +1 -1
  10. package/dist/asset-manager/src/installer.d.ts +1 -2
  11. package/dist/asset-manager/src/installer.d.ts.map +1 -1
  12. package/dist/asset-manager/src/renderers.d.ts.map +1 -1
  13. package/dist/bridge/drivers/claude-sdk.js +324 -226
  14. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  15. package/dist/bridge/drivers/codex.js +3 -3
  16. package/dist/bridge/drivers/codex.js.map +1 -1
  17. package/dist/bridge/drivers/continue.js +4 -4
  18. package/dist/bridge/drivers/echo.js +2 -2
  19. package/dist/bridge/drivers/echo.js.map +1 -1
  20. package/dist/bridge/drivers/gemini.js +5 -5
  21. package/dist/bridge/drivers/goose.js +4 -4
  22. package/dist/bridge/drivers/omp.js +3 -3
  23. package/dist/bridge/drivers/opencode.js +4 -4
  24. package/dist/bridge/drivers/qwen.js +5 -5
  25. package/dist/bridge/index.js +4 -4
  26. package/dist/bridge/src/drivers/claude-sdk.d.ts +64 -0
  27. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  28. package/dist/bridge/src/drivers/echo.d.ts +1 -1
  29. package/dist/bridge/src/drivers/echo.d.ts.map +1 -1
  30. package/dist/bridge/src/error-classifier.d.ts.map +1 -1
  31. package/dist/bridge/src/normalizer.d.ts +5 -0
  32. package/dist/bridge/src/normalizer.d.ts.map +1 -1
  33. package/dist/{chunk-LDUNTZB6.js → chunk-26L2B6P6.js} +2 -2
  34. package/dist/{chunk-LDUNTZB6.js.map → chunk-26L2B6P6.js.map} +1 -1
  35. package/dist/{chunk-FWHOU3CX.js → chunk-2VKVWN5E.js} +6 -6
  36. package/dist/{chunk-FWHOU3CX.js.map → chunk-2VKVWN5E.js.map} +1 -1
  37. package/dist/{chunk-2FS4FPZ7.js → chunk-4AGIEU7V.js} +44 -62
  38. package/dist/chunk-4AGIEU7V.js.map +1 -0
  39. package/dist/{chunk-ZONCCO5N.js → chunk-4R6DROGN.js} +30 -32
  40. package/dist/chunk-4R6DROGN.js.map +1 -0
  41. package/dist/{chunk-3QQSSRYN.js → chunk-5DQOZFWJ.js} +416 -345
  42. package/dist/chunk-5DQOZFWJ.js.map +1 -0
  43. package/dist/{chunk-XFQ2OXYR.js → chunk-5MJJYIL4.js} +3 -3
  44. package/dist/{chunk-XFQ2OXYR.js.map → chunk-5MJJYIL4.js.map} +1 -1
  45. package/dist/{chunk-AKSR434V.js → chunk-5XAQDOMU.js} +79 -56
  46. package/dist/chunk-5XAQDOMU.js.map +1 -0
  47. package/dist/{chunk-MBOR7D65.js → chunk-67MFR5LS.js} +3 -3
  48. package/dist/{chunk-MBOR7D65.js.map → chunk-67MFR5LS.js.map} +1 -1
  49. package/dist/{chunk-7GWYRVAX.js → chunk-6LW7Q53Z.js} +162 -165
  50. package/dist/chunk-6LW7Q53Z.js.map +1 -0
  51. package/dist/{chunk-SF4GWVR5.js → chunk-6TCOYFVJ.js} +86 -55
  52. package/dist/chunk-6TCOYFVJ.js.map +1 -0
  53. package/dist/{chunk-QMONOHXT.js → chunk-74UKG2LA.js} +3 -3
  54. package/dist/{chunk-QMONOHXT.js.map → chunk-74UKG2LA.js.map} +1 -1
  55. package/dist/{chunk-JIQLLZ65.js → chunk-7MANNJ3U.js} +534 -235
  56. package/dist/chunk-7MANNJ3U.js.map +1 -0
  57. package/dist/{chunk-7R6W5EQR.js → chunk-ACXLKNIM.js} +2 -2
  58. package/dist/{chunk-7R6W5EQR.js.map → chunk-ACXLKNIM.js.map} +1 -1
  59. package/dist/{chunk-3OKP6F2J.js → chunk-AKYEI3D6.js} +9 -9
  60. package/dist/{chunk-3OKP6F2J.js.map → chunk-AKYEI3D6.js.map} +1 -1
  61. package/dist/{chunk-CMG3GJ6B.js → chunk-DAI754GK.js} +4 -4
  62. package/dist/{chunk-CMG3GJ6B.js.map → chunk-DAI754GK.js.map} +1 -1
  63. package/dist/{chunk-FENG6OO6.js → chunk-FRRTUVUM.js} +4593 -4466
  64. package/dist/chunk-FRRTUVUM.js.map +1 -0
  65. package/dist/{chunk-TV72ZJYN.js → chunk-GXC3TYMQ.js} +60 -47
  66. package/dist/chunk-GXC3TYMQ.js.map +1 -0
  67. package/dist/{chunk-IERB3UXJ.js → chunk-H5YXL4KI.js} +83 -69
  68. package/dist/chunk-H5YXL4KI.js.map +1 -0
  69. package/dist/{chunk-QDJKE557.js → chunk-HRKB2ATT.js} +64 -64
  70. package/dist/chunk-HRKB2ATT.js.map +1 -0
  71. package/dist/{chunk-ZEKWHJBB.js → chunk-IZIFMNTI.js} +3 -3
  72. package/dist/{chunk-ZEKWHJBB.js.map → chunk-IZIFMNTI.js.map} +1 -1
  73. package/dist/{chunk-24UIWON4.js → chunk-JB35MVBN.js} +167 -156
  74. package/dist/chunk-JB35MVBN.js.map +1 -0
  75. package/dist/{chunk-UIZKIUC6.js → chunk-KHZI44SS.js} +3 -3
  76. package/dist/chunk-KHZI44SS.js.map +1 -0
  77. package/dist/{chunk-5YO3H5Q4.js → chunk-KY5NY7LB.js} +3 -3
  78. package/dist/chunk-KY5NY7LB.js.map +1 -0
  79. package/dist/{chunk-MIH45XPY.js → chunk-LA2YSXEE.js} +1974 -1919
  80. package/dist/chunk-LA2YSXEE.js.map +1 -0
  81. package/dist/{chunk-IEJ5IOC3.js → chunk-LFDZYCEO.js} +4 -2
  82. package/dist/chunk-LFDZYCEO.js.map +1 -0
  83. package/dist/{chunk-2NOE4AGD.js → chunk-LIK75OJ5.js} +3 -3
  84. package/dist/{chunk-2NOE4AGD.js.map → chunk-LIK75OJ5.js.map} +1 -1
  85. package/dist/{chunk-PAYAFNXG.js → chunk-NGJ6E344.js} +104 -74
  86. package/dist/chunk-NGJ6E344.js.map +1 -0
  87. package/dist/{chunk-5V5KZQIC.js → chunk-PVMAIVNL.js} +4 -4
  88. package/dist/{chunk-5V5KZQIC.js.map → chunk-PVMAIVNL.js.map} +1 -1
  89. package/dist/{chunk-HK5FRWG6.js → chunk-R6O6YWU7.js} +2 -2
  90. package/dist/{chunk-HK5FRWG6.js.map → chunk-R6O6YWU7.js.map} +1 -1
  91. package/dist/{chunk-PRLD7EXG.js → chunk-SBGHNXJD.js} +2 -2
  92. package/dist/{chunk-PRLD7EXG.js.map → chunk-SBGHNXJD.js.map} +1 -1
  93. package/dist/{chunk-5RBPPBEK.js → chunk-SCJD3FMO.js} +4 -4
  94. package/dist/{chunk-5RBPPBEK.js.map → chunk-SCJD3FMO.js.map} +1 -1
  95. package/dist/{chunk-BZ2JU7IP.js → chunk-TU4VZULO.js} +5 -5
  96. package/dist/{chunk-BZ2JU7IP.js.map → chunk-TU4VZULO.js.map} +1 -1
  97. package/dist/{chunk-CBNMIKUO.js → chunk-VGFYUD3T.js} +158 -108
  98. package/dist/chunk-VGFYUD3T.js.map +1 -0
  99. package/dist/{chunk-Z3XXKR5E.js → chunk-WDMH4UMD.js} +4 -6
  100. package/dist/chunk-WDMH4UMD.js.map +1 -0
  101. package/dist/{chunk-W7W66MDZ.js → chunk-X46FY45B.js} +44 -42
  102. package/dist/chunk-X46FY45B.js.map +1 -0
  103. package/dist/{chunk-JKNWJ64A.js → chunk-XGCXFX7X.js} +2 -2
  104. package/dist/{chunk-JKNWJ64A.js.map → chunk-XGCXFX7X.js.map} +1 -1
  105. package/dist/{chunk-TDZGENVP.js → chunk-XOAF5QAE.js} +2094 -1901
  106. package/dist/chunk-XOAF5QAE.js.map +1 -0
  107. package/dist/{chunk-FQHHCICT.js → chunk-XVDXGQ55.js} +226 -226
  108. package/dist/chunk-XVDXGQ55.js.map +1 -0
  109. package/dist/{chunk-M4KLIEIJ.js → chunk-YBJLYT4S.js} +3 -3
  110. package/dist/chunk-YBJLYT4S.js.map +1 -0
  111. package/dist/{chunk-OKRUTSG7.js → chunk-ZDKJHQY2.js} +2 -2
  112. package/dist/{chunk-OKRUTSG7.js.map → chunk-ZDKJHQY2.js.map} +1 -1
  113. package/dist/{chunk-UBCZVONU.js → chunk-ZME3JDA6.js} +3 -3
  114. package/dist/{chunk-UBCZVONU.js.map → chunk-ZME3JDA6.js.map} +1 -1
  115. package/dist/cli/index.js +2811 -2662
  116. package/dist/cli/index.js.map +1 -1
  117. package/dist/cli/src/asset-feeds.d.ts +0 -9
  118. package/dist/cli/src/asset-feeds.d.ts.map +1 -1
  119. package/dist/cli/src/commands/catalog-cmd.d.ts.map +1 -1
  120. package/dist/cli/src/commands/catalog.d.ts.map +1 -1
  121. package/dist/cli/src/commands/config.d.ts.map +1 -1
  122. package/dist/cli/src/commands/deploy.d.ts.map +1 -1
  123. package/dist/cli/src/commands/integration.d.ts.map +1 -1
  124. package/dist/cli/src/commands/library-cmd.d.ts.map +1 -1
  125. package/dist/cli/src/commands/manage.d.ts.map +1 -1
  126. package/dist/cli/src/commands/preset-cmd.d.ts.map +1 -1
  127. package/dist/cli/src/commands/project.d.ts +1 -1
  128. package/dist/cli/src/commands/project.d.ts.map +1 -1
  129. package/dist/cli/src/commands/show.d.ts.map +1 -1
  130. package/dist/cli/src/commands/source.d.ts.map +1 -1
  131. package/dist/cli/src/commands/validate.d.ts.map +1 -1
  132. package/dist/cli/src/ensure-sources.d.ts.map +1 -1
  133. package/dist/cli/src/flow-discovery.d.ts.map +1 -1
  134. package/dist/cli/src/open-registry.d.ts.map +1 -1
  135. package/dist/cli/src/plugin-store/index.d.ts +4 -6
  136. package/dist/cli/src/plugin-store/index.d.ts.map +1 -1
  137. package/dist/cli/src/plugin-store/paths.d.ts +0 -2
  138. package/dist/cli/src/plugin-store/paths.d.ts.map +1 -1
  139. package/dist/cli/src/setup.d.ts.map +1 -1
  140. package/dist/connectors/config.js +9 -9
  141. package/dist/connectors/index.js +12 -12
  142. package/dist/connectors/rclone.js +2 -2
  143. package/dist/connectors/src/connector-prompt.d.ts.map +1 -1
  144. package/dist/connectors/src/connector-registry.d.ts.map +1 -1
  145. package/dist/connectors/src/connector-types.d.ts +1 -1
  146. package/dist/connectors/src/connector-types.d.ts.map +1 -1
  147. package/dist/connectors/src/index.d.ts +5 -5
  148. package/dist/connectors/src/index.d.ts.map +1 -1
  149. package/dist/connectors/src/managed-gitconfig.d.ts.map +1 -1
  150. package/dist/{connectors-PTCSHCHZ.js → connectors-DY7UBBV5.js} +3 -3
  151. package/dist/{connectors-PTCSHCHZ.js.map → connectors-DY7UBBV5.js.map} +1 -1
  152. package/dist/connectors-shared/src/index.d.ts +1 -1
  153. package/dist/connectors-shared/src/index.d.ts.map +1 -1
  154. package/dist/core/index.js +8 -8
  155. package/dist/core/logging.js +1 -1
  156. package/dist/core/manifest.js +2 -2
  157. package/dist/core/models.js +1 -1
  158. package/dist/core/publish-manifest.js +2 -2
  159. package/dist/core/runtime-assets.js +5 -5
  160. package/dist/core/src/lock.d.ts +0 -9
  161. package/dist/core/src/lock.d.ts.map +1 -1
  162. package/dist/core/src/logging/create-logger.d.ts.map +1 -1
  163. package/dist/core/src/logging/index.d.ts +11 -11
  164. package/dist/core/src/logging/index.d.ts.map +1 -1
  165. package/dist/core/src/logging/log-store.d.ts.map +1 -1
  166. package/dist/core/src/logging/sinks/no-op-sink.d.ts.map +1 -1
  167. package/dist/core/src/logging/sinks/sqlite-sink.d.ts.map +1 -1
  168. package/dist/core/src/manifest.d.ts.map +1 -1
  169. package/dist/core/src/models.d.ts +6 -0
  170. package/dist/core/src/models.d.ts.map +1 -1
  171. package/dist/core/src/publish-manifest.d.ts.map +1 -1
  172. package/dist/core/src/repo-manager.d.ts +0 -12
  173. package/dist/core/src/repo-manager.d.ts.map +1 -1
  174. package/dist/core/src/runtime-assets.d.ts.map +1 -1
  175. package/dist/core/src/store.d.ts +1 -1
  176. package/dist/core/src/store.d.ts.map +1 -1
  177. package/dist/core/src/walker.d.ts.map +1 -1
  178. package/dist/core/src/workspace-config.d.ts +0 -27
  179. package/dist/core/src/workspace-config.d.ts.map +1 -1
  180. package/dist/core/store.js +1 -1
  181. package/dist/core/workspace-config.js +4 -4
  182. package/dist/deploy/index.js +8 -8
  183. package/dist/discovery/index.js +6 -6
  184. package/dist/discovery/src/asset-kind-registry.d.ts +1 -1
  185. package/dist/discovery/src/asset-kind-registry.d.ts.map +1 -1
  186. package/dist/discovery/src/builtin-providers.d.ts.map +1 -1
  187. package/dist/discovery/src/discover-manifest.d.ts +1 -1
  188. package/dist/discovery/src/discover-manifest.d.ts.map +1 -1
  189. package/dist/discovery/src/discover.d.ts +30 -3
  190. package/dist/discovery/src/discover.d.ts.map +1 -1
  191. package/dist/discovery/src/domains.d.ts +144 -0
  192. package/dist/discovery/src/domains.d.ts.map +1 -0
  193. package/dist/discovery/src/index.d.ts +9 -7
  194. package/dist/discovery/src/index.d.ts.map +1 -1
  195. package/dist/discovery/src/source-config.d.ts +1 -1
  196. package/dist/discovery/src/source-config.d.ts.map +1 -1
  197. package/dist/discovery/src/tree-entries.d.ts +13 -1
  198. package/dist/discovery/src/tree-entries.d.ts.map +1 -1
  199. package/dist/ensure-sources-6GW6UBXY.js +84 -0
  200. package/dist/ensure-sources-6GW6UBXY.js.map +1 -0
  201. package/dist/factory-assets/connectors/deploy/adapter.d.ts +1 -1
  202. package/dist/factory-assets/connectors/deploy/adapter.d.ts.map +1 -1
  203. package/dist/factory-assets/connectors/deploy.js +12 -12
  204. package/dist/factory-assets/connectors/devserver/adapter.d.ts +1 -1
  205. package/dist/factory-assets/connectors/devserver/adapter.d.ts.map +1 -1
  206. package/dist/factory-assets/connectors/devserver.js +12 -12
  207. package/dist/factory-assets/connectors/flow/adapter.d.ts +16 -2
  208. package/dist/factory-assets/connectors/flow/adapter.d.ts.map +1 -1
  209. package/dist/factory-assets/connectors/flow/adapter.js +12 -12
  210. package/dist/factory-assets/connectors/flow/engine/flow-kind-provider.d.ts.map +1 -1
  211. package/dist/factory-assets/connectors/flow/engine/index.d.ts +3 -3
  212. package/dist/factory-assets/connectors/flow/engine/index.d.ts.map +1 -1
  213. package/dist/factory-assets/connectors/flow/engine.js +9 -9
  214. package/dist/factory-assets/connectors/flow/index.d.ts +1 -1
  215. package/dist/factory-assets/connectors/flow/index.d.ts.map +1 -1
  216. package/dist/factory-assets/connectors/flow/run-flow.js +15 -15
  217. package/dist/factory-assets/connectors/flow.js +12 -12
  218. package/dist/factory-assets/connectors/git/driver.d.ts +32 -0
  219. package/dist/factory-assets/connectors/git/driver.d.ts.map +1 -1
  220. package/dist/factory-assets/connectors/git.js +12 -12
  221. package/dist/factory-assets/connectors/gmail/adapter.d.ts +1 -1
  222. package/dist/factory-assets/connectors/gmail/adapter.d.ts.map +1 -1
  223. package/dist/factory-assets/connectors/gmail.js +12 -12
  224. package/dist/factory-assets/connectors/googledrive.js +12 -12
  225. package/dist/factory-assets/connectors/local/driver.d.ts +1 -2
  226. package/dist/factory-assets/connectors/local/driver.d.ts.map +1 -1
  227. package/dist/factory-assets/connectors/local.js +12 -12
  228. package/dist/factory-assets/connectors/mattermost/adapter.d.ts +1 -1
  229. package/dist/factory-assets/connectors/mattermost/adapter.d.ts.map +1 -1
  230. package/dist/factory-assets/connectors/mattermost.js +12 -12
  231. package/dist/factory-assets/connectors/memory/adapter.d.ts.map +1 -1
  232. package/dist/factory-assets/connectors/memory.js +12 -12
  233. package/dist/factory-assets/connectors/minio/adapter.d.ts +1 -1
  234. package/dist/factory-assets/connectors/minio/adapter.d.ts.map +1 -1
  235. package/dist/factory-assets/connectors/minio.js +12 -12
  236. package/dist/factory-assets/connectors/postgres/adapter.d.ts +6 -1
  237. package/dist/factory-assets/connectors/postgres/adapter.d.ts.map +1 -1
  238. package/dist/factory-assets/connectors/postgres.js +12 -12
  239. package/dist/factory-assets/connectors/s3/driver.d.ts.map +1 -1
  240. package/dist/factory-assets/connectors/s3.js +12 -12
  241. package/dist/factory-assets/connectors/sharepoint.js +12 -12
  242. package/dist/factory-assets/connectors/sqlite/adapter.d.ts +1 -1
  243. package/dist/factory-assets/connectors/sqlite/adapter.d.ts.map +1 -1
  244. package/dist/factory-assets/connectors/sqlite.js +12 -12
  245. package/dist/factory-assets/connectors/static-server/adapter.d.ts +6 -1
  246. package/dist/factory-assets/connectors/static-server/adapter.d.ts.map +1 -1
  247. package/dist/factory-assets/connectors/static-server.js +12 -12
  248. package/dist/factory-assets/connectors/tunnel/adapter.d.ts +1 -1
  249. package/dist/factory-assets/connectors/tunnel/adapter.d.ts.map +1 -1
  250. package/dist/factory-assets/connectors/tunnel.js +12 -12
  251. package/dist/factory-assets/connectors/webdav.js +12 -12
  252. package/dist/factory-assets/connectors/xstate/adapter.d.ts +1 -1
  253. package/dist/factory-assets/connectors/xstate/adapter.d.ts.map +1 -1
  254. package/dist/factory-assets/connectors/xstate-store/adapter.d.ts +7 -0
  255. package/dist/factory-assets/connectors/xstate-store/adapter.d.ts.map +1 -1
  256. package/dist/factory-assets/connectors/xstate-store.js +12 -12
  257. package/dist/factory-assets/connectors/xstate.js +12 -12
  258. package/dist/flows-JXO3KGC2.js +7 -0
  259. package/dist/{flows-L3GHQD7F.js.map → flows-JXO3KGC2.js.map} +1 -1
  260. package/dist/library/index.js +13 -13
  261. package/dist/library/install.js +9 -9
  262. package/dist/library/src/config.d.ts.map +1 -1
  263. package/dist/library/src/index.d.ts +27 -27
  264. package/dist/library/src/index.d.ts.map +1 -1
  265. package/dist/library/src/install/index.d.ts +2 -2
  266. package/dist/library/src/install/index.d.ts.map +1 -1
  267. package/dist/library/src/install/install-from-manifest.d.ts +0 -21
  268. package/dist/library/src/install/install-from-manifest.d.ts.map +1 -1
  269. package/dist/library/src/knowledge/index.d.ts +2 -2
  270. package/dist/library/src/knowledge/index.d.ts.map +1 -1
  271. package/dist/library/src/knowledge/knowledge-kind-provider.d.ts.map +1 -1
  272. package/dist/library/src/local/library.d.ts +2 -2
  273. package/dist/library/src/local/library.d.ts.map +1 -1
  274. package/dist/library/src/local/store-paths.d.ts.map +1 -1
  275. package/dist/library/src/pin-resolver.d.ts.map +1 -1
  276. package/dist/library/src/preset/apply.d.ts +2 -2
  277. package/dist/library/src/preset/apply.d.ts.map +1 -1
  278. package/dist/library/src/preset/index.d.ts +10 -10
  279. package/dist/library/src/preset/index.d.ts.map +1 -1
  280. package/dist/library/src/preset/parse.d.ts.map +1 -1
  281. package/dist/library/src/preset/resolve-item.d.ts +1 -1
  282. package/dist/library/src/preset/resolve-item.d.ts.map +1 -1
  283. package/dist/library/src/preset/upgrade.d.ts +1 -1
  284. package/dist/library/src/preset/upgrade.d.ts.map +1 -1
  285. package/dist/library/src/remote/index.d.ts +5 -5
  286. package/dist/library/src/remote/index.d.ts.map +1 -1
  287. package/dist/library/src/remote/remote-catalog-source.d.ts +1 -1
  288. package/dist/library/src/remote/remote-catalog-source.d.ts.map +1 -1
  289. package/dist/library/src/remote/rest-catalog-source.d.ts +1 -1
  290. package/dist/library/src/remote/rest-catalog-source.d.ts.map +1 -1
  291. package/dist/library/src/sync/driver.d.ts +1 -1
  292. package/dist/library/src/sync/driver.d.ts.map +1 -1
  293. package/dist/library/src/sync/git-driver.d.ts +1 -1
  294. package/dist/library/src/sync/git-driver.d.ts.map +1 -1
  295. package/dist/library/src/sync/local-driver.d.ts +1 -1
  296. package/dist/library/src/sync/local-driver.d.ts.map +1 -1
  297. package/dist/library/src/workspace-config.d.ts +0 -9
  298. package/dist/library/src/workspace-config.d.ts.map +1 -1
  299. package/dist/open-library-SH6NCJM2.js +23 -0
  300. package/dist/{open-library-3VJY74LQ.js.map → open-library-SH6NCJM2.js.map} +1 -1
  301. package/dist/paths-YSXGJ7TO.js +15 -0
  302. package/dist/{paths-CE66L3AL.js.map → paths-YSXGJ7TO.js.map} +1 -1
  303. package/dist/plugin-registry/src/targets.d.ts +2 -3
  304. package/dist/plugin-registry/src/targets.d.ts.map +1 -1
  305. package/dist/{plugin-store-GL5VKYUA.js → plugin-store-2KMDUF7R.js} +11 -14
  306. package/dist/plugin-store-2KMDUF7R.js.map +1 -0
  307. package/dist/plugins/index.js +1 -1
  308. package/dist/plugins/src/asset-kind-registry.d.ts +1 -1
  309. package/dist/plugins/src/asset-kind-registry.d.ts.map +1 -1
  310. package/dist/plugins/src/catalog-source.d.ts +5 -0
  311. package/dist/plugins/src/catalog-source.d.ts.map +1 -1
  312. package/dist/plugins/src/index.d.ts +5 -5
  313. package/dist/plugins/src/index.d.ts.map +1 -1
  314. package/dist/{provider-GAWKFQ3T.js → provider-ZPT443IX.js} +18 -16
  315. package/dist/provider-ZPT443IX.js.map +1 -0
  316. package/dist/resolver/index.js +1 -1
  317. package/dist/resolver/src/preamble.d.ts.map +1 -1
  318. package/dist/runner/index.js +17 -17
  319. package/dist/runner/src/builtin-capabilities.d.ts +12 -0
  320. package/dist/runner/src/builtin-capabilities.d.ts.map +1 -1
  321. package/dist/runner/src/capability-registry.d.ts.map +1 -1
  322. package/dist/runner/src/compaction/index.d.ts +0 -4
  323. package/dist/runner/src/compaction/index.d.ts.map +1 -1
  324. package/dist/runner/src/compaction/orchestrator.d.ts +23 -2
  325. package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
  326. package/dist/runner/src/composition/compile.d.ts.map +1 -1
  327. package/dist/runner/src/composition/index.d.ts +4 -4
  328. package/dist/runner/src/composition/index.d.ts.map +1 -1
  329. package/dist/runner/src/composition/resolve.d.ts.map +1 -1
  330. package/dist/runner/src/composition/types.d.ts +2 -2
  331. package/dist/runner/src/composition/types.d.ts.map +1 -1
  332. package/dist/runner/src/config.d.ts +2 -2
  333. package/dist/runner/src/config.d.ts.map +1 -1
  334. package/dist/runner/src/define-capability.d.ts +1 -1
  335. package/dist/runner/src/define-capability.d.ts.map +1 -1
  336. package/dist/runner/src/external-mcp.d.ts.map +1 -1
  337. package/dist/runner/src/index.d.ts +9 -9
  338. package/dist/runner/src/index.d.ts.map +1 -1
  339. package/dist/runner/src/logging-bootstrap.d.ts +1 -1
  340. package/dist/runner/src/logging-bootstrap.d.ts.map +1 -1
  341. package/dist/runner/src/manifest.d.ts.map +1 -1
  342. package/dist/runner/src/resource-handler.d.ts.map +1 -1
  343. package/dist/runner/src/resources.d.ts.map +1 -1
  344. package/dist/runner/src/runner-capabilities.d.ts +25 -0
  345. package/dist/runner/src/runner-capabilities.d.ts.map +1 -1
  346. package/dist/runner/src/serve.d.ts.map +1 -1
  347. package/dist/runner/src/session-builder.d.ts.map +1 -1
  348. package/dist/sdk/asset-manager.js +12 -12
  349. package/dist/sdk/bridge.js +4 -4
  350. package/dist/sdk/core.js +8 -8
  351. package/dist/sdk/flow.js +9 -9
  352. package/dist/sdk/index.js +92 -76
  353. package/dist/sdk/index.js.map +1 -1
  354. package/dist/sdk/resolver.js +1 -1
  355. package/dist/sdk/runner.js +17 -17
  356. package/dist/sdk/session.js +1 -1
  357. package/dist/sdk/src/agent-session.d.ts +8 -0
  358. package/dist/sdk/src/agent-session.d.ts.map +1 -1
  359. package/dist/sdk/src/factory.d.ts.map +1 -1
  360. package/dist/sdk/src/index.d.ts +2 -2
  361. package/dist/sdk/src/index.d.ts.map +1 -1
  362. package/dist/sdk/store.js +1 -1
  363. package/dist/sdk/telemetry.js +1 -1
  364. package/dist/secrets/index.js +2 -2
  365. package/dist/secrets/src/index.d.ts +8 -8
  366. package/dist/secrets/src/index.d.ts.map +1 -1
  367. package/dist/secrets/src/lifecycle.d.ts +1 -2
  368. package/dist/secrets/src/lifecycle.d.ts.map +1 -1
  369. package/dist/secrets/src/providers/env.d.ts +1 -1
  370. package/dist/secrets/src/providers/env.d.ts.map +1 -1
  371. package/dist/secrets/src/providers/local.d.ts +1 -1
  372. package/dist/secrets/src/providers/local.d.ts.map +1 -1
  373. package/dist/secrets/src/router.d.ts +1 -1
  374. package/dist/secrets/src/router.d.ts.map +1 -1
  375. package/dist/session/index.js +1 -1
  376. package/dist/session/src/mentions.d.ts.map +1 -1
  377. package/dist/{sessions-2IUX4P3P.js → sessions-DICDEONJ.js} +3 -3
  378. package/dist/{sessions-2IUX4P3P.js.map → sessions-DICDEONJ.js.map} +1 -1
  379. package/dist/{setup-4YMN2BDU.js → setup-6MQ6QWML.js} +198 -175
  380. package/dist/setup-6MQ6QWML.js.map +1 -0
  381. package/dist/skills-BMI6H7HI.js +7 -0
  382. package/dist/{skills-6HJ3WINN.js.map → skills-BMI6H7HI.js.map} +1 -1
  383. package/dist/store/index.js +1 -1
  384. package/dist/store/src/index.d.ts +1 -1
  385. package/dist/store/src/index.d.ts.map +1 -1
  386. package/dist/store/src/resource-client.d.ts +1 -1
  387. package/dist/store/src/resource-client.d.ts.map +1 -1
  388. package/dist/store/src/vue.d.ts.map +1 -1
  389. package/dist/store/vue.js.map +1 -1
  390. package/dist/store-client-LDYF4TK4.js +15 -0
  391. package/dist/{store-client-BCP5LCAZ.js.map → store-client-LDYF4TK4.js.map} +1 -1
  392. package/dist/telemetry/index.js +1 -1
  393. package/dist/telemetry/src/context.d.ts.map +1 -1
  394. package/dist/telemetry/src/index.d.ts +3 -3
  395. package/dist/telemetry/src/index.d.ts.map +1 -1
  396. package/dist/telemetry/src/otel/gen-ai.d.ts +1 -1
  397. package/dist/telemetry/src/otel/gen-ai.d.ts.map +1 -1
  398. package/dist/telemetry/src/otel/provider.d.ts.map +1 -1
  399. package/dist/transport/src/index.d.ts +1 -1
  400. package/dist/transport/src/index.d.ts.map +1 -1
  401. package/dist/tui/index.js +251 -218
  402. package/dist/tui/index.js.map +1 -1
  403. package/dist/tui/src/index.d.ts +2 -2
  404. package/dist/tui/src/index.d.ts.map +1 -1
  405. package/dist/tui/src/repl-renderer.d.ts.map +1 -1
  406. package/dist/tui/src/repl-session.d.ts +1 -1
  407. package/dist/tui/src/repl-session.d.ts.map +1 -1
  408. package/dist/tui/src/repl.d.ts.map +1 -1
  409. package/dist/types/manifests.js +1 -1
  410. package/dist/types/src/envelope.d.ts +1 -1
  411. package/dist/types/src/envelope.d.ts.map +1 -1
  412. package/dist/types/src/manifests/agent.d.ts.map +1 -1
  413. package/dist/types/src/manifests/connector.d.ts.map +1 -1
  414. package/dist/types/src/manifests/contract.d.ts.map +1 -1
  415. package/dist/types/src/manifests/index.d.ts +16 -16
  416. package/dist/types/src/manifests/index.d.ts.map +1 -1
  417. package/dist/types/src/manifests/mcp-server.d.ts.map +1 -1
  418. package/dist/types/src/manifests/mount.d.ts.map +1 -1
  419. package/dist/types/src/manifests/persona.d.ts.map +1 -1
  420. package/dist/types/src/manifests/preset.d.ts +0 -18
  421. package/dist/types/src/manifests/preset.d.ts.map +1 -1
  422. package/dist/types/src/manifests/prompt.d.ts.map +1 -1
  423. package/dist/types/src/manifests/ruleset.d.ts.map +1 -1
  424. package/dist/types/src/manifests/skill.d.ts.map +1 -1
  425. package/dist/validator-VF5M764T.js +5 -0
  426. package/dist/{validator-47SQUW3J.js.map → validator-VF5M764T.js.map} +1 -1
  427. package/dist/workspace-plugin/adapters/mcp.js +6 -6
  428. package/dist/workspace-plugin/adapters/omp.js +76 -69
  429. package/dist/workspace-plugin/adapters/omp.js.map +1 -1
  430. package/dist/workspace-plugin/index.js +2 -2
  431. package/dist/workspace-plugin/src/adapters/omp.d.ts.map +1 -1
  432. package/dist/workspace-plugin/src/tools/connectors.d.ts.map +1 -1
  433. package/dist/workspace-plugin/src/tools/flows.d.ts.map +1 -1
  434. package/dist/workspace-plugin/src/tools/sessions.d.ts.map +1 -1
  435. package/dist/workspace-plugin/src/tools/skills.d.ts.map +1 -1
  436. package/dist/workspace-plugin/src/tools/validator.d.ts.map +1 -1
  437. package/package.json +1 -2
  438. package/dist/asset-feeds-2V7LZIG7.js.map +0 -1
  439. package/dist/chunk-24UIWON4.js.map +0 -1
  440. package/dist/chunk-2FS4FPZ7.js.map +0 -1
  441. package/dist/chunk-3QQSSRYN.js.map +0 -1
  442. package/dist/chunk-5YO3H5Q4.js.map +0 -1
  443. package/dist/chunk-7GWYRVAX.js.map +0 -1
  444. package/dist/chunk-AKSR434V.js.map +0 -1
  445. package/dist/chunk-CBNMIKUO.js.map +0 -1
  446. package/dist/chunk-FENG6OO6.js.map +0 -1
  447. package/dist/chunk-FQHHCICT.js.map +0 -1
  448. package/dist/chunk-IEJ5IOC3.js.map +0 -1
  449. package/dist/chunk-IERB3UXJ.js.map +0 -1
  450. package/dist/chunk-JIQLLZ65.js.map +0 -1
  451. package/dist/chunk-M4KLIEIJ.js.map +0 -1
  452. package/dist/chunk-MIH45XPY.js.map +0 -1
  453. package/dist/chunk-PAYAFNXG.js.map +0 -1
  454. package/dist/chunk-QDJKE557.js.map +0 -1
  455. package/dist/chunk-SF4GWVR5.js.map +0 -1
  456. package/dist/chunk-TDZGENVP.js.map +0 -1
  457. package/dist/chunk-TV72ZJYN.js.map +0 -1
  458. package/dist/chunk-UIZKIUC6.js.map +0 -1
  459. package/dist/chunk-W7W66MDZ.js.map +0 -1
  460. package/dist/chunk-Z3XXKR5E.js.map +0 -1
  461. package/dist/chunk-ZONCCO5N.js.map +0 -1
  462. package/dist/ensure-sources-BNSRO5G7.js +0 -74
  463. package/dist/ensure-sources-BNSRO5G7.js.map +0 -1
  464. package/dist/flows-L3GHQD7F.js +0 -7
  465. package/dist/open-library-3VJY74LQ.js +0 -23
  466. package/dist/paths-CE66L3AL.js +0 -15
  467. package/dist/plugin-store-GL5VKYUA.js.map +0 -1
  468. package/dist/provider-GAWKFQ3T.js.map +0 -1
  469. package/dist/setup-4YMN2BDU.js.map +0 -1
  470. package/dist/skills-6HJ3WINN.js +0 -7
  471. package/dist/store-client-BCP5LCAZ.js +0 -15
  472. package/dist/validator-47SQUW3J.js +0 -5
@@ -1,20 +1,20 @@
1
- import { WorkspacePlugin } from './chunk-XFQ2OXYR.js';
1
+ import { WorkspacePlugin } from './chunk-5MJJYIL4.js';
2
2
  import { WebSocketServerTransport } from './chunk-WQ7DE5UC.js';
3
3
  import { assembleSystemPrompt, buildCapabilitiesPromptSection } from './chunk-W3UDISS2.js';
4
4
  import { PROTOCOL_VERSION } from './chunk-TDSRLMDB.js';
5
- import { EventNormalizer } from './chunk-AKSR434V.js';
6
- import { classifyClaudeSdkError } from './chunk-PAYAFNXG.js';
7
- import { createDriver } from './chunk-ZEKWHJBB.js';
5
+ import { EventNormalizer } from './chunk-5XAQDOMU.js';
6
+ import { classifyClaudeSdkError } from './chunk-NGJ6E344.js';
7
+ import { createDriver } from './chunk-IZIFMNTI.js';
8
8
  import { deployCatalogEntry, undeployCatalogEntry } from './chunk-LV2HPH3C.js';
9
- import { registerBuiltinConnectors, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildConnectorPromptSection, buildSdkConnectorTools } from './chunk-FENG6OO6.js';
10
- import { loadConnectorDeclarations, PreMintedSecretProvider, InMemorySecretProvider } from './chunk-CMG3GJ6B.js';
9
+ import { registerBuiltinConnectors, buildConnectorPromptSection, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildSdkConnectorTools } from './chunk-FRRTUVUM.js';
10
+ import { loadConnectorDeclarations, PreMintedSecretProvider, InMemorySecretProvider } from './chunk-DAI754GK.js';
11
11
  import { renderStimulusPrompt, buildOrchestratorPrompt } from './chunk-DEYYRFQU.js';
12
- import { resolveSettings, resolveApiKey, providerEnvKey } from './chunk-M4KLIEIJ.js';
13
- import { resolveRuntimeAssets } from './chunk-FWHOU3CX.js';
14
- import { resolveSkWorkspaceConfig, resolveAgentDir, stageMaterializedSkills, loadMcpServerDeclarations, COMPACTION_DEFAULTS, validateAssetRecipeAttr } from './chunk-3QQSSRYN.js';
15
- import { parseAssetRef } from './chunk-IEJ5IOC3.js';
12
+ import { resolveSettings, resolveApiKey, providerEnvKey } from './chunk-YBJLYT4S.js';
13
+ import { resolveRuntimeAssets } from './chunk-2VKVWN5E.js';
14
+ import { resolveSkWorkspaceConfig, resolveAgentDir, stageMaterializedSkills, loadMcpServerDeclarations, COMPACTION_DEFAULTS, validateAssetRecipeAttr } from './chunk-5DQOZFWJ.js';
15
+ import { parseAssetRef } from './chunk-LFDZYCEO.js';
16
16
  import { resolveDriverPaths } from './chunk-K5GBV4SA.js';
17
- import { getLogStore, resolveLogStoreConfig, createLogStoreFromConfig, OnLogBridgeSink, WsLogSink, registerLogStore, resetLogStore, createLogger } from './chunk-24UIWON4.js';
17
+ import { getLogStore, resolveLogStoreConfig, createLogStoreFromConfig, OnLogBridgeSink, WsLogSink, registerLogStore, resetLogStore, createLogger } from './chunk-JB35MVBN.js';
18
18
  import { __require } from './chunk-NSBPE2FW.js';
19
19
  import pc from 'picocolors';
20
20
  import { Marked } from 'marked';
@@ -183,48 +183,6 @@ var MarkdownStreamer = class {
183
183
  }
184
184
  }
185
185
  };
186
-
187
- // runner/src/context.ts
188
- var PLATFORM_DESCRIPTIONS = {
189
- skaile: "You are an AI assistant operating within the Skaile platform, an enterprise AI workspace. Users interact with you through a chat interface in their browser."
190
- };
191
- function buildContextSection(context) {
192
- if (!context) return void 0;
193
- const hasAny = context.platform !== void 0 || context.multi_user !== void 0 || context.session_model !== void 0;
194
- if (!hasAny) return void 0;
195
- const lines = ["## Platform", ""];
196
- if (context.platform) {
197
- const description = PLATFORM_DESCRIPTIONS[context.platform];
198
- if (description) {
199
- lines.push(description);
200
- } else {
201
- lines.push(`You are running inside the ${context.platform} platform.`);
202
- }
203
- lines.push("");
204
- }
205
- if (context.multi_user === true) {
206
- lines.push(
207
- "Multiple users may participate in the same session. Be aware that messages in this conversation may come from different users."
208
- );
209
- lines.push("");
210
- } else if (context.multi_user === false) {
211
- lines.push("This is a single-user session.");
212
- lines.push("");
213
- }
214
- if (context.session_model === "persistent") {
215
- lines.push(
216
- "Your work persists across interactions. Files you create or modify in the workspace are saved and available in future sessions."
217
- );
218
- lines.push("");
219
- } else if (context.session_model === "ephemeral") {
220
- lines.push(
221
- "This session is ephemeral. Work you do does not persist beyond the current interaction."
222
- );
223
- lines.push("");
224
- }
225
- while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
226
- return lines.join("\n");
227
- }
228
186
  function defineCapability(args) {
229
187
  const inputSchema = z5.toJSONSchema(args.input, {
230
188
  target: "draft-07",
@@ -629,6 +587,48 @@ async function openLocalIndexIfAvailable(log) {
629
587
  return STUB_LIBRARY;
630
588
  }
631
589
  }
590
+
591
+ // runner/src/context.ts
592
+ var PLATFORM_DESCRIPTIONS = {
593
+ skaile: "You are an AI assistant operating within the Skaile platform, an enterprise AI workspace. Users interact with you through a chat interface in their browser."
594
+ };
595
+ function buildContextSection(context) {
596
+ if (!context) return void 0;
597
+ const hasAny = context.platform !== void 0 || context.multi_user !== void 0 || context.session_model !== void 0;
598
+ if (!hasAny) return void 0;
599
+ const lines = ["## Platform", ""];
600
+ if (context.platform) {
601
+ const description = PLATFORM_DESCRIPTIONS[context.platform];
602
+ if (description) {
603
+ lines.push(description);
604
+ } else {
605
+ lines.push(`You are running inside the ${context.platform} platform.`);
606
+ }
607
+ lines.push("");
608
+ }
609
+ if (context.multi_user === true) {
610
+ lines.push(
611
+ "Multiple users may participate in the same session. Be aware that messages in this conversation may come from different users."
612
+ );
613
+ lines.push("");
614
+ } else if (context.multi_user === false) {
615
+ lines.push("This is a single-user session.");
616
+ lines.push("");
617
+ }
618
+ if (context.session_model === "persistent") {
619
+ lines.push(
620
+ "Your work persists across interactions. Files you create or modify in the workspace are saved and available in future sessions."
621
+ );
622
+ lines.push("");
623
+ } else if (context.session_model === "ephemeral") {
624
+ lines.push(
625
+ "This session is ephemeral. Work you do does not persist beyond the current interaction."
626
+ );
627
+ lines.push("");
628
+ }
629
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
630
+ return lines.join("\n");
631
+ }
632
632
  var execFileAsync = promisify(execFile);
633
633
  var PROBE_TIMEOUT_MS = 1500;
634
634
  var VERSION_PROBES = [
@@ -893,63 +893,72 @@ function resolveExternalMcpDeclarations(projectDir, sessionId) {
893
893
  mcpLog.warn("MCP server skipped: reserved id", { id: decl.id });
894
894
  continue;
895
895
  }
896
- let resolvedDecl = decl;
897
- if (decl.recipe) {
898
- const recipeLogKey = decl.recipe.flake ? { flake: decl.recipe.flake } : { attr: decl.recipe.attr };
899
- try {
900
- const recipeKey = decl.recipe.flake ? `mcps.${decl.id}` : decl.recipe.attr ?? `mcps.${decl.id}`;
901
- const outPath = resolveRecipePath(nixFlakeRef, recipeKey, recipeCache);
902
- const outPaths = /* @__PURE__ */ new Map([[decl.id, outPath]]);
903
- const resolvedCommand = decl.command ? substituteRecipeTemplating(decl.command, outPaths) : decl.command;
904
- const resolvedArgs = decl.args?.map((a) => substituteRecipeTemplating(a, outPaths));
905
- const resolvedEnv = substituteRecipeMap(decl.env, outPaths);
906
- resolvedDecl = {
907
- ...decl,
908
- command: resolvedCommand,
909
- args: resolvedArgs,
910
- env: resolvedEnv
911
- };
912
- const recipeLog = createLogger({
913
- kind: "mcp",
914
- subkind: decl.id,
915
- instance: "recipe-resolve"
916
- });
917
- const allResolved = [
918
- resolvedCommand ?? "",
919
- ...resolvedArgs ?? [],
920
- ...Object.values(resolvedEnv ?? {})
921
- ];
922
- for (const v of allResolved) {
923
- if (v.includes("${recipe:")) {
924
- recipeLog.warn("unresolved recipe marker after substitution", {
925
- value: v,
926
- declId: decl.id
927
- });
928
- }
929
- }
930
- const missingPaths = validateSubstitutedPaths(resolvedCommand, resolvedArgs);
931
- if (missingPaths.length > 0) {
932
- recipeLog.error("MCP server skipped: substituted path does not exist", void 0, {
933
- id: decl.id,
934
- ...recipeLogKey,
935
- missing: missingPaths
936
- });
937
- continue;
938
- }
939
- recipeLog.info("recipe substitution complete", { ...recipeLogKey, outPath });
940
- } catch (err) {
941
- createLogger({ kind: "mcp", subkind: decl.id, instance: "recipe-resolve" }).error(
942
- "MCP server skipped: recipe resolution failed",
943
- err,
944
- { id: decl.id, ...recipeLogKey, flakeRef: nixFlakeRef }
945
- );
946
- continue;
947
- }
896
+ if (!decl.recipe) {
897
+ resolved.push(decl);
898
+ continue;
948
899
  }
949
- resolved.push(resolvedDecl);
900
+ const resolvedDecl = resolveRecipeDeclaration(decl, nixFlakeRef, recipeCache);
901
+ if (resolvedDecl) resolved.push(resolvedDecl);
950
902
  }
951
903
  return resolved;
952
904
  }
905
+ function resolveRecipeDeclaration(decl, nixFlakeRef, recipeCache) {
906
+ const recipe = decl.recipe;
907
+ const recipeLogKey = recipe.flake ? { flake: recipe.flake } : { attr: recipe.attr };
908
+ try {
909
+ const recipeKey = recipe.flake ? `mcps.${decl.id}` : recipe.attr ?? `mcps.${decl.id}`;
910
+ const outPath = resolveRecipePath(nixFlakeRef, recipeKey, recipeCache);
911
+ const outPaths = /* @__PURE__ */ new Map([[decl.id, outPath]]);
912
+ const resolvedCommand = decl.command ? substituteRecipeTemplating(decl.command, outPaths) : decl.command;
913
+ const resolvedArgs = decl.args?.map((a) => substituteRecipeTemplating(a, outPaths));
914
+ const resolvedEnv = substituteRecipeMap(decl.env, outPaths);
915
+ const resolvedDecl = {
916
+ ...decl,
917
+ command: resolvedCommand,
918
+ args: resolvedArgs,
919
+ env: resolvedEnv
920
+ };
921
+ const recipeLog = createLogger({
922
+ kind: "mcp",
923
+ subkind: decl.id,
924
+ instance: "recipe-resolve"
925
+ });
926
+ warnUnresolvedRecipeMarkers(recipeLog, decl.id, resolvedCommand, resolvedArgs, resolvedEnv);
927
+ const missingPaths = validateSubstitutedPaths(resolvedCommand, resolvedArgs);
928
+ if (missingPaths.length > 0) {
929
+ recipeLog.error("MCP server skipped: substituted path does not exist", void 0, {
930
+ id: decl.id,
931
+ ...recipeLogKey,
932
+ missing: missingPaths
933
+ });
934
+ return void 0;
935
+ }
936
+ recipeLog.info("recipe substitution complete", { ...recipeLogKey, outPath });
937
+ return resolvedDecl;
938
+ } catch (err) {
939
+ createLogger({ kind: "mcp", subkind: decl.id, instance: "recipe-resolve" }).error(
940
+ "MCP server skipped: recipe resolution failed",
941
+ err,
942
+ { id: decl.id, ...recipeLogKey, flakeRef: nixFlakeRef }
943
+ );
944
+ return void 0;
945
+ }
946
+ }
947
+ function warnUnresolvedRecipeMarkers(recipeLog, declId, resolvedCommand, resolvedArgs, resolvedEnv) {
948
+ const allResolved = [
949
+ resolvedCommand ?? "",
950
+ ...resolvedArgs ?? [],
951
+ ...Object.values(resolvedEnv ?? {})
952
+ ];
953
+ for (const v of allResolved) {
954
+ if (v.includes("${recipe:")) {
955
+ recipeLog.warn("unresolved recipe marker after substitution", {
956
+ value: v,
957
+ declId
958
+ });
959
+ }
960
+ }
961
+ }
953
962
  var PASSTHROUGH_INPUT_ZOD = {
954
963
  parse: (v) => v
955
964
  };
@@ -1140,122 +1149,50 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
1140
1149
  for (const w of runtimeAssets.warnings) {
1141
1150
  resLog.warn("runtime asset warning", { code: w.code, message: w.message });
1142
1151
  }
1143
- if (process.env.SKAILE_AUTO_INSTALL_DEPS === "true") {
1144
- const required = runtimeAssets.requiredNpmDeps.required;
1145
- if (required.length > 0) {
1146
- const missing = findMissingPackages(required, projectDir);
1147
- if (missing.length > 0) {
1148
- resLog.info("installing missing npm peer deps", { packages: missing });
1149
- const r = await installNpmPackages(missing, projectDir);
1150
- if (!r.success) resLog.error("npm install failed", void 0, { output: r.output });
1151
- }
1152
- }
1153
- }
1152
+ await maybeInstallNpmPeerDeps(runtimeAssets, projectDir, resLog);
1154
1153
  const connectorDeclarations = loadConnectorDeclarations(projectDir);
1155
1154
  let resourceManager = null;
1156
1155
  if (connectorDeclarations.length > 0) {
1157
- resourceManager = new ConnectorManager(projectDir, {
1158
- secrets: secretProvider,
1159
- catalogEntries: runtimeAssets.catalogEntries,
1156
+ resourceManager = await connectAllConnectors(
1157
+ connectorDeclarations,
1158
+ projectDir,
1159
+ runtimeAssets,
1160
+ secretProvider,
1160
1161
  tokenMediator,
1161
1162
  preMintedSecrets,
1162
- onStatusChange: watch?.onConnectorStatus
1163
- });
1164
- try {
1165
- const report = await resourceManager.connectAll(connectorDeclarations, {
1166
- failOnReadWriteError: true,
1167
- deferMounts: deferFilesystemMounts
1168
- });
1169
- for (const r of report.results) {
1170
- if (r.connected) {
1171
- resLog.info("connector ok", { id: r.id, mountPath: r.mountPath });
1172
- } else {
1173
- resLog.error("connector failed", void 0, { id: r.id, error: r.error });
1174
- }
1175
- }
1176
- } catch (err) {
1177
- if (err instanceof ConnectorStartupError) {
1178
- for (const r of err.report.results) {
1179
- if (r.connected) {
1180
- resLog.info("connector ok", { id: r.id, mountPath: r.mountPath });
1181
- } else {
1182
- resLog.error("connector failed", void 0, { id: r.id, error: r.error });
1183
- }
1184
- }
1185
- }
1186
- throw err;
1187
- }
1163
+ deferFilesystemMounts,
1164
+ watch,
1165
+ resLog
1166
+ );
1188
1167
  }
1189
1168
  const resourcePromptSection = resourceManager ? buildConnectorPromptSection(resourceManager) : "";
1190
1169
  let mcpServers;
1191
1170
  let workspacePluginToShutdown = null;
1192
1171
  let externalMcpManager = null;
1193
1172
  if (driverType === "claude-sdk") {
1194
- if (resourceManager) {
1195
- const connectorsLog = createLogger({ kind: "mcp", subkind: "skaile-connectors" });
1196
- try {
1197
- const server = await buildSdkConnectorTools(resourceManager, { z: z });
1198
- if (server) {
1199
- mcpServers = { "skaile-connectors": server };
1200
- connectorsLog.info("server constructed", {
1201
- connectorCount: resourceManager.listConnectors().length
1202
- });
1203
- } else {
1204
- connectorsLog.debug("server skipped (no tools to expose)");
1205
- }
1206
- } catch (err) {
1207
- connectorsLog.error("server construction failed", err);
1208
- throw err;
1209
- }
1210
- }
1211
- const workspacePlugin = new WorkspacePlugin({
1173
+ const inProcess = await buildInProcessMcpServers(
1174
+ resourceManager,
1212
1175
  projectDir,
1213
- connectorManager: resourceManager ?? void 0,
1214
- driver: driverType,
1215
- provider: sessionMeta?.provider,
1216
- model: sessionMeta?.model
1217
- });
1218
- const wsServer = await workspacePlugin.buildWorkspaceMcpServer();
1219
- if (wsServer) {
1220
- mcpServers = { ...mcpServers ?? {}, "skaile-workspace": wsServer };
1221
- }
1222
- workspacePluginToShutdown = workspacePlugin;
1176
+ driverType,
1177
+ sessionMeta
1178
+ );
1179
+ mcpServers = inProcess.mcpServers;
1180
+ workspacePluginToShutdown = inProcess.workspacePluginToShutdown;
1223
1181
  }
1224
1182
  if (capabilityRegistry) {
1225
- const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1226
- if (resolvedDecls.length > 0) {
1227
- externalMcpManager = new ExternalMcpManager(
1228
- capabilityRegistry,
1229
- secretProvider,
1230
- sessionMeta?.sessionId
1231
- );
1232
- await externalMcpManager.start(resolvedDecls);
1233
- }
1183
+ externalMcpManager = await startExternalMcpServers(
1184
+ capabilityRegistry,
1185
+ projectDir,
1186
+ secretProvider,
1187
+ sessionMeta
1188
+ );
1234
1189
  } else if (driverType === "claude-sdk") {
1235
- const mcpLog = createLogger({
1236
- kind: "mcp",
1237
- subkind: "wiring",
1238
- instance: sessionMeta?.sessionId ?? "no-session"
1239
- });
1240
- const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1241
- for (const decl of resolvedDecls) {
1242
- const serverConfig = toSdkMcpServerConfig(
1243
- decl,
1244
- secretProvider
1245
- );
1246
- if (serverConfig) {
1247
- mcpServers = { ...mcpServers ?? {}, [decl.id]: serverConfig };
1248
- createLogger({ kind: "mcp", subkind: decl.id }).info("MCP server registered", {
1249
- transport: decl.transport ?? "stdio",
1250
- ...decl.transport === "stdio" || !decl.transport ? { command: decl.command, args: decl.args } : { url: decl.url }
1251
- });
1252
- } else {
1253
- mcpLog.error("MCP server config invalid (translation returned null)", void 0, {
1254
- id: decl.id,
1255
- transport: decl.transport
1256
- });
1257
- }
1258
- }
1190
+ mcpServers = wireExternalMcpServersForSdk(
1191
+ mcpServers,
1192
+ projectDir,
1193
+ secretProvider,
1194
+ sessionMeta
1195
+ );
1259
1196
  }
1260
1197
  if (!resourceManager && driverType !== "claude-sdk" && !externalMcpManager?.hasServers())
1261
1198
  return noopResult;
@@ -1306,6 +1243,120 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
1306
1243
  return noopResult;
1307
1244
  }
1308
1245
  }
1246
+ async function maybeInstallNpmPeerDeps(runtimeAssets, projectDir, resLog) {
1247
+ if (process.env.SKAILE_AUTO_INSTALL_DEPS === "true") {
1248
+ const required = runtimeAssets.requiredNpmDeps.required;
1249
+ if (required.length > 0) {
1250
+ const missing = findMissingPackages(required, projectDir);
1251
+ if (missing.length > 0) {
1252
+ resLog.info("installing missing npm peer deps", { packages: missing });
1253
+ const r = await installNpmPackages(missing, projectDir);
1254
+ if (!r.success) resLog.error("npm install failed", void 0, { output: r.output });
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+ function logConnectorReport(results, resLog) {
1260
+ for (const r of results) {
1261
+ if (r.connected) {
1262
+ resLog.info("connector ok", { id: r.id, mountPath: r.mountPath });
1263
+ } else {
1264
+ resLog.error("connector failed", void 0, { id: r.id, error: r.error });
1265
+ }
1266
+ }
1267
+ }
1268
+ async function connectAllConnectors(connectorDeclarations, projectDir, runtimeAssets, secretProvider, tokenMediator, preMintedSecrets, deferFilesystemMounts, watch, resLog) {
1269
+ const resourceManager = new ConnectorManager(projectDir, {
1270
+ secrets: secretProvider,
1271
+ catalogEntries: runtimeAssets.catalogEntries,
1272
+ tokenMediator,
1273
+ preMintedSecrets,
1274
+ onStatusChange: watch?.onConnectorStatus
1275
+ });
1276
+ try {
1277
+ const report = await resourceManager.connectAll(connectorDeclarations, {
1278
+ failOnReadWriteError: true,
1279
+ deferMounts: deferFilesystemMounts
1280
+ });
1281
+ logConnectorReport(report.results, resLog);
1282
+ } catch (err) {
1283
+ if (err instanceof ConnectorStartupError) {
1284
+ logConnectorReport(err.report.results, resLog);
1285
+ }
1286
+ throw err;
1287
+ }
1288
+ return resourceManager;
1289
+ }
1290
+ async function buildInProcessMcpServers(resourceManager, projectDir, driverType, sessionMeta) {
1291
+ let mcpServers;
1292
+ if (resourceManager) {
1293
+ const connectorsLog = createLogger({ kind: "mcp", subkind: "skaile-connectors" });
1294
+ try {
1295
+ const server = await buildSdkConnectorTools(resourceManager, { z: z });
1296
+ if (server) {
1297
+ mcpServers = { "skaile-connectors": server };
1298
+ connectorsLog.info("server constructed", {
1299
+ connectorCount: resourceManager.listConnectors().length
1300
+ });
1301
+ } else {
1302
+ connectorsLog.debug("server skipped (no tools to expose)");
1303
+ }
1304
+ } catch (err) {
1305
+ connectorsLog.error("server construction failed", err);
1306
+ throw err;
1307
+ }
1308
+ }
1309
+ const workspacePlugin = new WorkspacePlugin({
1310
+ projectDir,
1311
+ connectorManager: resourceManager ?? void 0,
1312
+ driver: driverType,
1313
+ provider: sessionMeta?.provider,
1314
+ model: sessionMeta?.model
1315
+ });
1316
+ const wsServer = await workspacePlugin.buildWorkspaceMcpServer();
1317
+ if (wsServer) {
1318
+ mcpServers = { ...mcpServers ?? {}, "skaile-workspace": wsServer };
1319
+ }
1320
+ return { mcpServers, workspacePluginToShutdown: workspacePlugin };
1321
+ }
1322
+ async function startExternalMcpServers(capabilityRegistry, projectDir, secretProvider, sessionMeta) {
1323
+ const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1324
+ if (resolvedDecls.length === 0) return null;
1325
+ const externalMcpManager = new ExternalMcpManager(
1326
+ capabilityRegistry,
1327
+ secretProvider,
1328
+ sessionMeta?.sessionId
1329
+ );
1330
+ await externalMcpManager.start(resolvedDecls);
1331
+ return externalMcpManager;
1332
+ }
1333
+ function wireExternalMcpServersForSdk(mcpServers, projectDir, secretProvider, sessionMeta) {
1334
+ const mcpLog = createLogger({
1335
+ kind: "mcp",
1336
+ subkind: "wiring",
1337
+ instance: sessionMeta?.sessionId ?? "no-session"
1338
+ });
1339
+ const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1340
+ for (const decl of resolvedDecls) {
1341
+ const serverConfig = toSdkMcpServerConfig(
1342
+ decl,
1343
+ secretProvider
1344
+ );
1345
+ if (serverConfig) {
1346
+ mcpServers = { ...mcpServers ?? {}, [decl.id]: serverConfig };
1347
+ createLogger({ kind: "mcp", subkind: decl.id }).info("MCP server registered", {
1348
+ transport: decl.transport ?? "stdio",
1349
+ ...decl.transport === "stdio" || !decl.transport ? { command: decl.command, args: decl.args } : { url: decl.url }
1350
+ });
1351
+ } else {
1352
+ mcpLog.error("MCP server config invalid (translation returned null)", void 0, {
1353
+ id: decl.id,
1354
+ transport: decl.transport
1355
+ });
1356
+ }
1357
+ }
1358
+ return mcpServers;
1359
+ }
1309
1360
  function toSdkMcpServerConfig(decl, secrets) {
1310
1361
  const transport = decl.transport ?? "stdio";
1311
1362
  if (transport === "stdio") {
@@ -1401,45 +1452,17 @@ async function createAgentSession(config) {
1401
1452
  "externalPromptFile is deprecated and no longer injected; use agent.prompt-extensions in skaile.yaml instead"
1402
1453
  );
1403
1454
  }
1404
- let composition = null;
1405
- if (agentDir && manifest?.composes?.length) {
1406
- composition = await resolveAgentComposition({
1407
- agentDir,
1408
- manifest,
1409
- log: sessionLog
1410
- });
1411
- } else if (manifest?.composes?.length && !agentDir) {
1412
- sessionLog.debug("skipping composition resolution \u2014 no agentDir available");
1413
- }
1455
+ const composition = await resolveSessionComposition(agentDir, manifest, projectDir, sessionLog);
1414
1456
  const compositionBlocks = composition?.inlineBlocks ?? [];
1415
1457
  const additionalSections = config.additionalPromptSections ?? [];
1416
- const systemPromptSections = [];
1417
- if (contextSection) systemPromptSections.push({ label: "context", value: contextSection });
1418
- if (userPromptSection) systemPromptSections.push({ label: "prompt", value: userPromptSection });
1419
- if (environmentSection)
1420
- systemPromptSections.push({ label: "environment", value: environmentSection });
1421
- if (resourcePromptSection)
1422
- systemPromptSections.push({ label: "resources", value: resourcePromptSection });
1423
- for (const ext of promptExtensions) {
1424
- systemPromptSections.push({
1425
- label: "prompt-extensions",
1426
- value: ext.value,
1427
- source: ext.source
1428
- });
1429
- }
1430
- for (const block of compositionBlocks) {
1431
- systemPromptSections.push({
1432
- label: "composition",
1433
- value: block.content,
1434
- source: block.ref
1435
- });
1436
- }
1437
- additionalSections.forEach((value, idx) => {
1438
- systemPromptSections.push({
1439
- label: "additional",
1440
- value,
1441
- source: `additional[${idx}]`
1442
- });
1458
+ const systemPromptSections = buildSystemPromptSections({
1459
+ contextSection,
1460
+ userPromptSection,
1461
+ environmentSection,
1462
+ resourcePromptSection,
1463
+ promptExtensions,
1464
+ compositionBlocks,
1465
+ additionalSections
1443
1466
  });
1444
1467
  const systemPrompt = assembleSystemPrompt([
1445
1468
  contextSection,
@@ -1450,36 +1473,26 @@ async function createAgentSession(config) {
1450
1473
  ...compositionBlocks.map((b) => b.content),
1451
1474
  ...additionalSections
1452
1475
  ]);
1453
- if (process.env.SKAILE_LOG_SYSTEM_PROMPT !== "0") {
1454
- const length = systemPrompt?.length ?? 0;
1455
- sessionLog.debug("assembled system prompt", { length });
1456
- sessionLog.debug("system prompt content", { content: systemPrompt ?? "(empty)" });
1457
- }
1476
+ logAssembledSystemPrompt(systemPrompt, sessionLog);
1458
1477
  const thinking = config.thinking ?? wsConfig.agent_config?.default?.thinking;
1459
1478
  const effort = config.effort ?? wsConfig.agent_config?.default?.effort;
1460
1479
  const maxTurns = maxTurnsOverride ?? manifest?.runtime?.max_turns;
1461
- const driver = await createDriver(driverType, {
1462
- cwd: agentCwd,
1480
+ const driver = await createSessionDriver({
1481
+ config,
1482
+ driverType,
1483
+ agentCwd,
1463
1484
  provider,
1464
- model: model ?? manifest?.model?.preferred,
1485
+ model,
1486
+ manifest,
1465
1487
  maxTurns,
1466
- apiKeys: apiKey ? { [provider ?? "anthropic"]: apiKey } : void 0,
1488
+ apiKey,
1467
1489
  systemPrompt,
1468
- tools: manifest?.tools,
1469
- ...thinking ? { thinking } : {},
1470
- ...effort ? { effort } : {},
1471
- ...sessionId ? { sessionId } : {},
1472
- ...resumeSessionId ? { resumeSessionId } : {},
1473
- ...mcpServers ? { mcpServers } : {},
1474
- // Protocol v2 capability dispatch hooks. When provided, the driver builds
1475
- // a synthetic `skaile-capabilities` MCP server alongside any existing
1476
- // mcpServers so capability invocations route through the registry's
1477
- // `invoke()` (which logs + validates + handles fire-and-forget / render).
1478
- ...config.capabilities ? { capabilities: config.capabilities } : {},
1479
- ...config.aiProviderConfigId ? { aiProviderConfigId: config.aiProviderConfigId } : {},
1480
- ...config.onAuthError ? { onAuthError: config.onAuthError } : {},
1481
- // Always forward agentDir/agentName for native identity handling
1482
- ...agentDir ? { agentDir, agentName: manifest?.name } : {}
1490
+ thinking,
1491
+ effort,
1492
+ sessionId,
1493
+ resumeSessionId,
1494
+ mcpServers,
1495
+ agentDir
1483
1496
  });
1484
1497
  if (telemetry && trace) {
1485
1498
  driver.setTelemetry(telemetry, trace);
@@ -1520,8 +1533,110 @@ function loadPromptExtensions(paths, projectDir, log) {
1520
1533
  }
1521
1534
  return result;
1522
1535
  }
1523
-
1524
- // runner/src/agent.ts
1536
+ async function resolveSessionComposition(agentDir, manifest, projectDir, log) {
1537
+ if (agentDir && manifest?.composes?.length) {
1538
+ return resolveAgentComposition({
1539
+ agentDir,
1540
+ manifest,
1541
+ log
1542
+ });
1543
+ }
1544
+ if (manifest?.composes?.length && !agentDir) {
1545
+ log.debug("skipping composition resolution \u2014 no agentDir available");
1546
+ }
1547
+ return null;
1548
+ }
1549
+ function buildSystemPromptSections(parts) {
1550
+ const {
1551
+ contextSection,
1552
+ userPromptSection,
1553
+ environmentSection,
1554
+ resourcePromptSection,
1555
+ promptExtensions,
1556
+ compositionBlocks,
1557
+ additionalSections
1558
+ } = parts;
1559
+ const systemPromptSections = [];
1560
+ if (contextSection) systemPromptSections.push({ label: "context", value: contextSection });
1561
+ if (userPromptSection) systemPromptSections.push({ label: "prompt", value: userPromptSection });
1562
+ if (environmentSection)
1563
+ systemPromptSections.push({ label: "environment", value: environmentSection });
1564
+ if (resourcePromptSection)
1565
+ systemPromptSections.push({ label: "resources", value: resourcePromptSection });
1566
+ for (const ext of promptExtensions) {
1567
+ systemPromptSections.push({
1568
+ label: "prompt-extensions",
1569
+ value: ext.value,
1570
+ source: ext.source
1571
+ });
1572
+ }
1573
+ for (const block of compositionBlocks) {
1574
+ systemPromptSections.push({
1575
+ label: "composition",
1576
+ value: block.content,
1577
+ source: block.ref
1578
+ });
1579
+ }
1580
+ additionalSections.forEach((value, idx) => {
1581
+ systemPromptSections.push({
1582
+ label: "additional",
1583
+ value,
1584
+ source: `additional[${idx}]`
1585
+ });
1586
+ });
1587
+ return systemPromptSections;
1588
+ }
1589
+ function logAssembledSystemPrompt(systemPrompt, log) {
1590
+ if (process.env.SKAILE_LOG_SYSTEM_PROMPT !== "0") {
1591
+ const length = systemPrompt?.length ?? 0;
1592
+ log.debug("assembled system prompt", { length });
1593
+ log.debug("system prompt content", { content: systemPrompt ?? "(empty)" });
1594
+ }
1595
+ }
1596
+ function createSessionDriver(args) {
1597
+ const {
1598
+ config,
1599
+ driverType,
1600
+ agentCwd,
1601
+ provider,
1602
+ model,
1603
+ manifest,
1604
+ maxTurns,
1605
+ apiKey,
1606
+ systemPrompt,
1607
+ thinking,
1608
+ effort,
1609
+ sessionId,
1610
+ resumeSessionId,
1611
+ mcpServers,
1612
+ agentDir
1613
+ } = args;
1614
+ return createDriver(driverType, {
1615
+ cwd: agentCwd,
1616
+ provider,
1617
+ model: model ?? manifest?.model?.preferred,
1618
+ maxTurns,
1619
+ apiKeys: apiKey ? { [provider ?? "anthropic"]: apiKey } : void 0,
1620
+ systemPrompt,
1621
+ tools: manifest?.tools,
1622
+ ...thinking ? { thinking } : {},
1623
+ ...effort ? { effort } : {},
1624
+ ...sessionId ? { sessionId } : {},
1625
+ ...resumeSessionId ? { resumeSessionId } : {},
1626
+ ...mcpServers ? { mcpServers } : {},
1627
+ // Protocol v2 capability dispatch hooks. When provided, the driver builds
1628
+ // a synthetic `skaile-capabilities` MCP server alongside any existing
1629
+ // mcpServers so capability invocations route through the registry's
1630
+ // `invoke()` (which logs + validates + handles fire-and-forget / render).
1631
+ ...config.capabilities ? { capabilities: config.capabilities } : {},
1632
+ ...config.aiProviderConfigId ? { aiProviderConfigId: config.aiProviderConfigId } : {},
1633
+ ...config.onAuthError ? { onAuthError: config.onAuthError } : {},
1634
+ // Always forward agentDir/agentName for native identity handling
1635
+ ...agentDir ? { agentDir, agentName: manifest?.name } : {}
1636
+ });
1637
+ }
1638
+
1639
+ // runner/src/agent.ts
1525
1640
  var PROMPT_AGENT = `${pc.magenta(pc.bold("agent"))} ${pc.dim("\u25B6")} `;
1526
1641
  async function runAgentChat(opts) {
1527
1642
  const sessionId = `chat-${Date.now().toString(36)}`;
@@ -1601,345 +1716,134 @@ async function buildDriver(opts) {
1601
1716
  onLog: opts.onLog
1602
1717
  });
1603
1718
  }
1604
- async function handleResourceRequest(command, manager, emit) {
1605
- const { requestId, connectorId, operation, path: path5, options, content } = command;
1606
- try {
1607
- let data;
1608
- switch (operation) {
1609
- case "list":
1610
- data = await manager.list(
1611
- connectorId,
1612
- path5,
1613
- options
1614
- );
1615
- break;
1616
- case "read":
1617
- data = await manager.read(connectorId, path5);
1618
- break;
1619
- case "write":
1620
- await manager.write(connectorId, path5, content);
1621
- data = true;
1622
- break;
1623
- case "delete":
1624
- data = await manager.delete(connectorId, path5);
1625
- break;
1626
- case "search":
1627
- data = await manager.search(
1628
- connectorId,
1629
- path5,
1630
- options
1631
- );
1632
- break;
1719
+ function keywordSearch(capabilities, query, limit) {
1720
+ const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
1721
+ if (tokens.length === 0) return capabilities.slice(0, limit);
1722
+ const scored = capabilities.map((cap) => {
1723
+ const haystack = [cap.name, cap.description, cap.promptFragment ?? ""].join(" ").toLowerCase();
1724
+ let score = 0;
1725
+ for (const token of tokens) {
1726
+ if (haystack.includes(token)) score += 1;
1633
1727
  }
1634
- emit({ type: "resource_response", requestId, resourceId: connectorId, operation, data });
1635
- } catch (err) {
1636
- emit({
1637
- type: "resource_response",
1638
- requestId,
1639
- resourceId: connectorId,
1640
- operation,
1641
- error: err instanceof Error ? err.message : String(err)
1642
- });
1728
+ return { cap, score };
1729
+ }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
1730
+ return scored.slice(0, limit).map((x) => x.cap);
1731
+ }
1732
+ function builtinCapabilities(registry) {
1733
+ const listFilterSchema = z5.strictObject({
1734
+ side: z5.enum(["client", "agent"]).optional(),
1735
+ originKind: z5.enum(["framework", "client", "agent", "flow", "skill", "mcp", "connector", "mount"]).optional(),
1736
+ scope: z5.enum(["session", "turn"]).optional()
1737
+ });
1738
+ return [
1739
+ defineCapability({
1740
+ name: "__capabilities.list",
1741
+ displayName: "List capabilities",
1742
+ description: "List available capabilities in this session. Optionally filter by side, origin kind, or scope.",
1743
+ side: "agent",
1744
+ origin: { kind: "framework" },
1745
+ kind: "query",
1746
+ userInvokable: false,
1747
+ input: listFilterSchema,
1748
+ handler: async (args) => registry.list({
1749
+ side: args.side,
1750
+ origin: args.originKind,
1751
+ scope: args.scope
1752
+ })
1753
+ }),
1754
+ defineCapability({
1755
+ name: "__capabilities.search",
1756
+ displayName: "Search capabilities",
1757
+ description: "Keyword search across capability names, descriptions, and prompt fragments. Returns the top matches by score (substring match count).",
1758
+ side: "agent",
1759
+ origin: { kind: "framework" },
1760
+ kind: "query",
1761
+ userInvokable: false,
1762
+ input: z5.strictObject({
1763
+ query: z5.string(),
1764
+ limit: z5.number().int().positive().optional()
1765
+ }),
1766
+ handler: async ({ query, limit }) => keywordSearch(registry.list(), query, limit ?? 10)
1767
+ }),
1768
+ defineCapability({
1769
+ name: "__capabilities.describe",
1770
+ displayName: "Describe capability",
1771
+ description: "Return the full Capability descriptor for a single capability by name.",
1772
+ side: "agent",
1773
+ origin: { kind: "framework" },
1774
+ kind: "query",
1775
+ userInvokable: false,
1776
+ input: z5.strictObject({ name: z5.string() }),
1777
+ handler: async ({ name }) => {
1778
+ const cap = registry.resolve(name);
1779
+ if (!cap) return null;
1780
+ const wire = registry.list().find((c) => c.name === name);
1781
+ return wire ?? null;
1782
+ }
1783
+ })
1784
+ ];
1785
+ }
1786
+ function bootstrapCapabilityRegistry(registry) {
1787
+ for (const cap of builtinCapabilities(registry)) {
1788
+ registry.register(cap, "agent");
1643
1789
  }
1644
1790
  }
1645
- function getMimeType(filename) {
1646
- return mime.getType(filename) ?? "application/octet-stream";
1791
+ var DEFAULT_AUDIENCE = ["llm", "user"];
1792
+ function audienceOf(cap) {
1793
+ return cap.audience ?? DEFAULT_AUDIENCE;
1647
1794
  }
1648
- function isBinaryFile(filePath) {
1649
- try {
1650
- const fd = openSync(filePath, "r");
1651
- const buf = Buffer.alloc(8192);
1652
- const bytesRead = readSync(fd, buf, 0, 8192, 0);
1653
- closeSync(fd);
1654
- for (let i = 0; i < bytesRead; i++) {
1655
- if (buf[i] === 0) return true;
1795
+ function hasAudience(cap, target) {
1796
+ return audienceOf(cap).includes(target);
1797
+ }
1798
+ var ORIGIN_PROMPT_ORDER = [
1799
+ "framework",
1800
+ "client",
1801
+ "agent",
1802
+ "flow",
1803
+ "skill",
1804
+ "mcp",
1805
+ "connector",
1806
+ "mount"
1807
+ ];
1808
+ var CapabilityRegistry = class {
1809
+ byName = /* @__PURE__ */ new Map();
1810
+ logger;
1811
+ remoteInvoker;
1812
+ constructor(logger) {
1813
+ this.logger = logger ?? createLogger({ kind: "runner", subkind: "capability-registry" });
1814
+ }
1815
+ /**
1816
+ * Install the platform-bound invoker used by {@link invokeRemote}.
1817
+ *
1818
+ * The runner wires this up after the transport is connected and the
1819
+ * per-session pending-call map is in place. Idempotent — repeated calls
1820
+ * replace the prior invoker.
1821
+ *
1822
+ * @category Runtime
1823
+ * @since 3.0.0
1824
+ */
1825
+ setRemoteInvoker(invoker) {
1826
+ this.remoteInvoker = invoker;
1827
+ }
1828
+ /**
1829
+ * Invoke a `host.*` (or other peer-side) capability on the platform.
1830
+ *
1831
+ * Composes a `capability_invoked` event under the hood and resolves with
1832
+ * the matching `capability_result` payload. Infrastructure failures
1833
+ * (timeout, schema mismatch, transport disconnect) reject with `Error`;
1834
+ * domain failures (e.g. `{ ok: false, code }` for credential mints) ride
1835
+ * in the resolved payload.
1836
+ *
1837
+ * @category Runtime
1838
+ * @since 3.0.0
1839
+ */
1840
+ async invokeRemote(name, input) {
1841
+ if (!this.remoteInvoker) {
1842
+ throw new Error(
1843
+ `Capability '${name}' invoked remotely but no remote invoker is wired. Call CapabilityRegistry.setRemoteInvoker(...) before dispatch.`
1844
+ );
1656
1845
  }
1657
- return false;
1658
- } catch {
1659
- return false;
1660
- }
1661
- }
1662
- function handleMountResourceRequest(command, manager, emit) {
1663
- const { requestId, connectorId: mountId, operation, path: reqPath, content } = command;
1664
- try {
1665
- const entry = manager.get(mountId);
1666
- const mountPath = entry.handle.mountPath;
1667
- if (!mountPath) {
1668
- throw new Error(`Connector "${mountId}" has no projected mount directory`);
1669
- }
1670
- const targetPath = reqPath ? resolve(mountPath, reqPath) : mountPath;
1671
- const rel = relative(mountPath, targetPath);
1672
- if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
1673
- throw new Error("Path traversal outside mount");
1674
- }
1675
- let data;
1676
- switch (operation) {
1677
- case "list": {
1678
- const entries = readdirSync(targetPath, { withFileTypes: true });
1679
- data = entries.filter((e) => !e.name.startsWith(".")).map((e) => {
1680
- const entryPath = reqPath ? join(reqPath, e.name) : e.name;
1681
- const result = {
1682
- name: e.name,
1683
- path: entryPath,
1684
- type: e.isDirectory() ? "directory" : "file"
1685
- };
1686
- if (!e.isDirectory()) {
1687
- try {
1688
- result.size = statSync(join(targetPath, e.name)).size;
1689
- } catch {
1690
- }
1691
- }
1692
- return result;
1693
- });
1694
- break;
1695
- }
1696
- case "read": {
1697
- const contentType = getMimeType(basename(targetPath));
1698
- if (isBinaryFile(targetPath)) {
1699
- const buf = readFileSync(targetPath);
1700
- data = {
1701
- data: buf.toString("base64"),
1702
- contentType,
1703
- encoding: "binary"
1704
- };
1705
- } else {
1706
- data = {
1707
- data: readFileSync(targetPath, "utf-8"),
1708
- contentType,
1709
- encoding: "utf-8"
1710
- };
1711
- }
1712
- break;
1713
- }
1714
- case "write": {
1715
- mkdirSync(dirname(targetPath), { recursive: true });
1716
- const writeContent = typeof content === "string" ? content : content?.data ?? "";
1717
- const encoding = typeof content === "string" ? void 0 : content?.encoding;
1718
- if (encoding === "binary") {
1719
- writeFileSync(targetPath, Buffer.from(writeContent, "base64"));
1720
- } else {
1721
- writeFileSync(targetPath, writeContent, "utf-8");
1722
- }
1723
- data = true;
1724
- break;
1725
- }
1726
- case "delete": {
1727
- unlinkSync(targetPath);
1728
- data = true;
1729
- break;
1730
- }
1731
- default:
1732
- throw new Error(`Unsupported operation for mount: ${operation}`);
1733
- }
1734
- emit({ type: "resource_response", requestId, resourceId: mountId, operation, data });
1735
- } catch (err) {
1736
- emit({
1737
- type: "resource_response",
1738
- requestId,
1739
- resourceId: mountId,
1740
- operation,
1741
- error: err instanceof Error ? err.message : String(err)
1742
- });
1743
- }
1744
- }
1745
-
1746
- // runner/src/ai-credential-refresh.ts
1747
- var PROACTIVE_AI_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
1748
- function createAiCredentialRefreshScheduler(deps) {
1749
- const now = deps.now ?? (() => Date.now());
1750
- const setTimeoutImpl = deps.setTimeoutImpl ?? ((handler, ms) => {
1751
- const handle = setTimeout(handler, ms);
1752
- return handle;
1753
- });
1754
- const clearTimeoutImpl = deps.clearTimeoutImpl ?? ((handle) => {
1755
- clearTimeout(handle);
1756
- });
1757
- const marginMs = deps.marginMs ?? PROACTIVE_AI_REFRESH_MARGIN_MS;
1758
- let timer = null;
1759
- function cancel() {
1760
- if (timer !== null) {
1761
- clearTimeoutImpl(timer);
1762
- timer = null;
1763
- }
1764
- }
1765
- async function fire() {
1766
- timer = null;
1767
- let next;
1768
- try {
1769
- next = await deps.refresh();
1770
- } catch (err) {
1771
- deps.log(
1772
- `[ai-credential-refresh] refresh threw: ${err instanceof Error ? err.message : String(err)}; reactive 401 path remains as safety net`
1773
- );
1774
- return;
1775
- }
1776
- if (next === void 0) {
1777
- deps.log(
1778
- "[ai-credential-refresh] refresh returned no expiry; reactive 401 path remains as safety net"
1779
- );
1780
- return;
1781
- }
1782
- schedule(next);
1783
- }
1784
- function schedule(expiresAtIso) {
1785
- cancel();
1786
- if (!expiresAtIso) {
1787
- return;
1788
- }
1789
- const expiresAtMs = Date.parse(expiresAtIso);
1790
- if (!Number.isFinite(expiresAtMs)) return;
1791
- const delayMs = expiresAtMs - now() - marginMs;
1792
- if (delayMs <= 0) {
1793
- deps.log(
1794
- `[ai-credential-refresh] token within ${marginMs / 1e3}s of expiry; refreshing now`
1795
- );
1796
- timer = setTimeoutImpl(() => {
1797
- void fire();
1798
- }, 0);
1799
- } else {
1800
- deps.log(
1801
- `[ai-credential-refresh] scheduled in ${Math.round(delayMs / 1e3)}s (expiresAt=${expiresAtIso})`
1802
- );
1803
- timer = setTimeoutImpl(() => {
1804
- void fire();
1805
- }, delayMs);
1806
- }
1807
- timer.unref?.();
1808
- }
1809
- return {
1810
- schedule,
1811
- cancel,
1812
- isPending: () => timer !== null
1813
- };
1814
- }
1815
- function keywordSearch(capabilities, query, limit) {
1816
- const tokens = query.toLowerCase().split(/\s+/).filter((t) => t.length > 0);
1817
- if (tokens.length === 0) return capabilities.slice(0, limit);
1818
- const scored = capabilities.map((cap) => {
1819
- const haystack = [cap.name, cap.description, cap.promptFragment ?? ""].join(" ").toLowerCase();
1820
- let score = 0;
1821
- for (const token of tokens) {
1822
- if (haystack.includes(token)) score += 1;
1823
- }
1824
- return { cap, score };
1825
- }).filter((x) => x.score > 0).sort((a, b) => b.score - a.score);
1826
- return scored.slice(0, limit).map((x) => x.cap);
1827
- }
1828
- function builtinCapabilities(registry) {
1829
- const listFilterSchema = z5.strictObject({
1830
- side: z5.enum(["client", "agent"]).optional(),
1831
- originKind: z5.enum(["framework", "client", "agent", "flow", "skill", "mcp", "connector", "mount"]).optional(),
1832
- scope: z5.enum(["session", "turn"]).optional()
1833
- });
1834
- return [
1835
- defineCapability({
1836
- name: "__capabilities.list",
1837
- displayName: "List capabilities",
1838
- description: "List available capabilities in this session. Optionally filter by side, origin kind, or scope.",
1839
- side: "agent",
1840
- origin: { kind: "framework" },
1841
- kind: "query",
1842
- userInvokable: false,
1843
- input: listFilterSchema,
1844
- handler: async (args) => registry.list({
1845
- side: args.side,
1846
- origin: args.originKind,
1847
- scope: args.scope
1848
- })
1849
- }),
1850
- defineCapability({
1851
- name: "__capabilities.search",
1852
- displayName: "Search capabilities",
1853
- description: "Keyword search across capability names, descriptions, and prompt fragments. Returns the top matches by score (substring match count).",
1854
- side: "agent",
1855
- origin: { kind: "framework" },
1856
- kind: "query",
1857
- userInvokable: false,
1858
- input: z5.strictObject({
1859
- query: z5.string(),
1860
- limit: z5.number().int().positive().optional()
1861
- }),
1862
- handler: async ({ query, limit }) => keywordSearch(registry.list(), query, limit ?? 10)
1863
- }),
1864
- defineCapability({
1865
- name: "__capabilities.describe",
1866
- displayName: "Describe capability",
1867
- description: "Return the full Capability descriptor for a single capability by name.",
1868
- side: "agent",
1869
- origin: { kind: "framework" },
1870
- kind: "query",
1871
- userInvokable: false,
1872
- input: z5.strictObject({ name: z5.string() }),
1873
- handler: async ({ name }) => {
1874
- const cap = registry.resolve(name);
1875
- if (!cap) return null;
1876
- const wire = registry.list().find((c) => c.name === name);
1877
- return wire ?? null;
1878
- }
1879
- })
1880
- ];
1881
- }
1882
- function bootstrapCapabilityRegistry(registry) {
1883
- for (const cap of builtinCapabilities(registry)) {
1884
- registry.register(cap, "agent");
1885
- }
1886
- }
1887
- var DEFAULT_AUDIENCE = ["llm", "user"];
1888
- function audienceOf(cap) {
1889
- return cap.audience ?? DEFAULT_AUDIENCE;
1890
- }
1891
- function hasAudience(cap, target) {
1892
- return audienceOf(cap).includes(target);
1893
- }
1894
- var ORIGIN_PROMPT_ORDER = [
1895
- "framework",
1896
- "client",
1897
- "agent",
1898
- "flow",
1899
- "skill",
1900
- "mcp",
1901
- "connector",
1902
- "mount"
1903
- ];
1904
- var CapabilityRegistry = class {
1905
- byName = /* @__PURE__ */ new Map();
1906
- logger;
1907
- remoteInvoker;
1908
- constructor(logger) {
1909
- this.logger = logger ?? createLogger({ kind: "runner", subkind: "capability-registry" });
1910
- }
1911
- /**
1912
- * Install the platform-bound invoker used by {@link invokeRemote}.
1913
- *
1914
- * The runner wires this up after the transport is connected and the
1915
- * per-session pending-call map is in place. Idempotent — repeated calls
1916
- * replace the prior invoker.
1917
- *
1918
- * @category Runtime
1919
- * @since 3.0.0
1920
- */
1921
- setRemoteInvoker(invoker) {
1922
- this.remoteInvoker = invoker;
1923
- }
1924
- /**
1925
- * Invoke a `host.*` (or other peer-side) capability on the platform.
1926
- *
1927
- * Composes a `capability_invoked` event under the hood and resolves with
1928
- * the matching `capability_result` payload. Infrastructure failures
1929
- * (timeout, schema mismatch, transport disconnect) reject with `Error`;
1930
- * domain failures (e.g. `{ ok: false, code }` for credential mints) ride
1931
- * in the resolved payload.
1932
- *
1933
- * @category Runtime
1934
- * @since 3.0.0
1935
- */
1936
- async invokeRemote(name, input) {
1937
- if (!this.remoteInvoker) {
1938
- throw new Error(
1939
- `Capability '${name}' invoked remotely but no remote invoker is wired. Call CapabilityRegistry.setRemoteInvoker(...) before dispatch.`
1940
- );
1941
- }
1942
- return this.remoteInvoker(name, input, this.byName.get(name)?.callTimeoutMs);
1846
+ return this.remoteInvoker(name, input, this.byName.get(name)?.callTimeoutMs);
1943
1847
  }
1944
1848
  /**
1945
1849
  * Register a capability. Validates that the registering `source` is
@@ -2218,34 +2122,227 @@ function resolveCapabilityResult(pending, cmd) {
2218
2122
  } else {
2219
2123
  entry.resolve(result);
2220
2124
  }
2221
- return true;
2222
- }
2223
- function rejectCapabilityOnApprovalDeny(pending, callId, feedback) {
2224
- const entry = pending.get(callId);
2225
- if (!entry) return false;
2226
- clearTimeout(entry.timer);
2227
- pending.delete(callId);
2228
- entry.reject(new Error(feedback ?? "declined by user"));
2229
- return true;
2230
- }
2231
-
2232
- // runner/src/compaction/strategy.ts
2233
- var TokenThresholdStrategy = class {
2234
- thresholdPercent;
2235
- minCooldownMs;
2236
- constructor(opts) {
2237
- this.thresholdPercent = opts.thresholdPercent;
2238
- this.minCooldownMs = opts.minCooldownMs;
2125
+ return true;
2126
+ }
2127
+ function rejectCapabilityOnApprovalDeny(pending, callId, feedback) {
2128
+ const entry = pending.get(callId);
2129
+ if (!entry) return false;
2130
+ clearTimeout(entry.timer);
2131
+ pending.delete(callId);
2132
+ entry.reject(new Error(feedback ?? "declined by user"));
2133
+ return true;
2134
+ }
2135
+ async function handleResourceRequest(command, manager, emit) {
2136
+ const { requestId, connectorId, operation, path: path5, options, content } = command;
2137
+ try {
2138
+ let data;
2139
+ switch (operation) {
2140
+ case "list":
2141
+ data = await manager.list(
2142
+ connectorId,
2143
+ path5,
2144
+ options
2145
+ );
2146
+ break;
2147
+ case "read":
2148
+ data = await manager.read(connectorId, path5);
2149
+ break;
2150
+ case "write":
2151
+ await manager.write(connectorId, path5, content);
2152
+ data = true;
2153
+ break;
2154
+ case "delete":
2155
+ data = await manager.delete(connectorId, path5);
2156
+ break;
2157
+ case "search":
2158
+ data = await manager.search(
2159
+ connectorId,
2160
+ path5,
2161
+ options
2162
+ );
2163
+ break;
2164
+ }
2165
+ emit({ type: "resource_response", requestId, resourceId: connectorId, operation, data });
2166
+ } catch (err) {
2167
+ emit({
2168
+ type: "resource_response",
2169
+ requestId,
2170
+ resourceId: connectorId,
2171
+ operation,
2172
+ error: err instanceof Error ? err.message : String(err)
2173
+ });
2174
+ }
2175
+ }
2176
+ function getMimeType(filename) {
2177
+ return mime.getType(filename) ?? "application/octet-stream";
2178
+ }
2179
+ function isBinaryFile(filePath) {
2180
+ try {
2181
+ const fd = openSync(filePath, "r");
2182
+ const buf = Buffer.alloc(8192);
2183
+ const bytesRead = readSync(fd, buf, 0, 8192, 0);
2184
+ closeSync(fd);
2185
+ for (let i = 0; i < bytesRead; i++) {
2186
+ if (buf[i] === 0) return true;
2187
+ }
2188
+ return false;
2189
+ } catch {
2190
+ return false;
2191
+ }
2192
+ }
2193
+ function handleMountResourceRequest(command, manager, emit) {
2194
+ const { requestId, connectorId: mountId, operation, path: reqPath, content } = command;
2195
+ try {
2196
+ const entry = manager.get(mountId);
2197
+ const mountPath = entry.handle.mountPath;
2198
+ if (!mountPath) {
2199
+ throw new Error(`Connector "${mountId}" has no projected mount directory`);
2200
+ }
2201
+ const targetPath = reqPath ? resolve(mountPath, reqPath) : mountPath;
2202
+ const rel = relative(mountPath, targetPath);
2203
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
2204
+ throw new Error("Path traversal outside mount");
2205
+ }
2206
+ let data;
2207
+ switch (operation) {
2208
+ case "list": {
2209
+ const entries = readdirSync(targetPath, { withFileTypes: true });
2210
+ data = entries.filter((e) => !e.name.startsWith(".")).map((e) => {
2211
+ const entryPath = reqPath ? join(reqPath, e.name) : e.name;
2212
+ const result = {
2213
+ name: e.name,
2214
+ path: entryPath,
2215
+ type: e.isDirectory() ? "directory" : "file"
2216
+ };
2217
+ if (!e.isDirectory()) {
2218
+ try {
2219
+ result.size = statSync(join(targetPath, e.name)).size;
2220
+ } catch {
2221
+ }
2222
+ }
2223
+ return result;
2224
+ });
2225
+ break;
2226
+ }
2227
+ case "read": {
2228
+ const contentType = getMimeType(basename(targetPath));
2229
+ if (isBinaryFile(targetPath)) {
2230
+ const buf = readFileSync(targetPath);
2231
+ data = {
2232
+ data: buf.toString("base64"),
2233
+ contentType,
2234
+ encoding: "binary"
2235
+ };
2236
+ } else {
2237
+ data = {
2238
+ data: readFileSync(targetPath, "utf-8"),
2239
+ contentType,
2240
+ encoding: "utf-8"
2241
+ };
2242
+ }
2243
+ break;
2244
+ }
2245
+ case "write": {
2246
+ mkdirSync(dirname(targetPath), { recursive: true });
2247
+ const writeContent = typeof content === "string" ? content : content?.data ?? "";
2248
+ const encoding = typeof content === "string" ? void 0 : content?.encoding;
2249
+ if (encoding === "binary") {
2250
+ writeFileSync(targetPath, Buffer.from(writeContent, "base64"));
2251
+ } else {
2252
+ writeFileSync(targetPath, writeContent, "utf-8");
2253
+ }
2254
+ data = true;
2255
+ break;
2256
+ }
2257
+ case "delete": {
2258
+ unlinkSync(targetPath);
2259
+ data = true;
2260
+ break;
2261
+ }
2262
+ default:
2263
+ throw new Error(`Unsupported operation for mount: ${operation}`);
2264
+ }
2265
+ emit({ type: "resource_response", requestId, resourceId: mountId, operation, data });
2266
+ } catch (err) {
2267
+ emit({
2268
+ type: "resource_response",
2269
+ requestId,
2270
+ resourceId: mountId,
2271
+ operation,
2272
+ error: err instanceof Error ? err.message : String(err)
2273
+ });
2274
+ }
2275
+ }
2276
+
2277
+ // runner/src/ai-credential-refresh.ts
2278
+ var PROACTIVE_AI_REFRESH_MARGIN_MS = 5 * 60 * 1e3;
2279
+ function createAiCredentialRefreshScheduler(deps) {
2280
+ const now = deps.now ?? (() => Date.now());
2281
+ const setTimeoutImpl = deps.setTimeoutImpl ?? ((handler, ms) => {
2282
+ const handle = setTimeout(handler, ms);
2283
+ return handle;
2284
+ });
2285
+ const clearTimeoutImpl = deps.clearTimeoutImpl ?? ((handle) => {
2286
+ clearTimeout(handle);
2287
+ });
2288
+ const marginMs = deps.marginMs ?? PROACTIVE_AI_REFRESH_MARGIN_MS;
2289
+ let timer = null;
2290
+ function cancel() {
2291
+ if (timer !== null) {
2292
+ clearTimeoutImpl(timer);
2293
+ timer = null;
2294
+ }
2295
+ }
2296
+ async function fire() {
2297
+ timer = null;
2298
+ let next;
2299
+ try {
2300
+ next = await deps.refresh();
2301
+ } catch (err) {
2302
+ deps.log(
2303
+ `[ai-credential-refresh] refresh threw: ${err instanceof Error ? err.message : String(err)}; reactive 401 path remains as safety net`
2304
+ );
2305
+ return;
2306
+ }
2307
+ if (next === void 0) {
2308
+ deps.log(
2309
+ "[ai-credential-refresh] refresh returned no expiry; reactive 401 path remains as safety net"
2310
+ );
2311
+ return;
2312
+ }
2313
+ schedule(next);
2239
2314
  }
2240
- shouldCompact(context) {
2241
- const fillPercent = context.estimatedTokens / context.contextWindow * 100;
2242
- if (fillPercent < this.thresholdPercent) return false;
2243
- if (context.timeSinceLastCompaction !== null && context.timeSinceLastCompaction < this.minCooldownMs) {
2244
- return false;
2315
+ function schedule(expiresAtIso) {
2316
+ cancel();
2317
+ if (!expiresAtIso) {
2318
+ return;
2245
2319
  }
2246
- return true;
2320
+ const expiresAtMs = Date.parse(expiresAtIso);
2321
+ if (!Number.isFinite(expiresAtMs)) return;
2322
+ const delayMs = expiresAtMs - now() - marginMs;
2323
+ if (delayMs <= 0) {
2324
+ deps.log(
2325
+ `[ai-credential-refresh] token within ${marginMs / 1e3}s of expiry; refreshing now`
2326
+ );
2327
+ timer = setTimeoutImpl(() => {
2328
+ void fire();
2329
+ }, 0);
2330
+ } else {
2331
+ deps.log(
2332
+ `[ai-credential-refresh] scheduled in ${Math.round(delayMs / 1e3)}s (expiresAt=${expiresAtIso})`
2333
+ );
2334
+ timer = setTimeoutImpl(() => {
2335
+ void fire();
2336
+ }, delayMs);
2337
+ }
2338
+ timer.unref?.();
2247
2339
  }
2248
- };
2340
+ return {
2341
+ schedule,
2342
+ cancel,
2343
+ isPending: () => timer !== null
2344
+ };
2345
+ }
2249
2346
 
2250
2347
  // runner/src/compaction/prompt.ts
2251
2348
  var DEFAULT_COMPACTION_PROMPT = `You are being asked to compact this conversation into a structured summary.
@@ -2325,6 +2422,24 @@ ${opts.focus}`);
2325
2422
  return parts.join("");
2326
2423
  }
2327
2424
 
2425
+ // runner/src/compaction/strategy.ts
2426
+ var TokenThresholdStrategy = class {
2427
+ thresholdPercent;
2428
+ minCooldownMs;
2429
+ constructor(opts) {
2430
+ this.thresholdPercent = opts.thresholdPercent;
2431
+ this.minCooldownMs = opts.minCooldownMs;
2432
+ }
2433
+ shouldCompact(context) {
2434
+ const fillPercent = context.estimatedTokens / context.contextWindow * 100;
2435
+ if (fillPercent < this.thresholdPercent) return false;
2436
+ if (context.timeSinceLastCompaction !== null && context.timeSinceLastCompaction < this.minCooldownMs) {
2437
+ return false;
2438
+ }
2439
+ return true;
2440
+ }
2441
+ };
2442
+
2328
2443
  // runner/src/compaction/orchestrator.ts
2329
2444
  var MIN_SUMMARY_CHARS = 200;
2330
2445
  var MAX_COMPRESSION_RATIO = 0.9;
@@ -2370,9 +2485,6 @@ var CompactionOrchestrator = class {
2370
2485
  minCooldownMs: this.config.minCooldownMs
2371
2486
  });
2372
2487
  }
2373
- get hasNewMessagesSinceCompaction() {
2374
- return this.messagesSinceCompaction > 0;
2375
- }
2376
2488
  trackMessage(seq) {
2377
2489
  this.currentSeq = seq;
2378
2490
  this.messagesSinceCompaction++;
@@ -2406,6 +2518,17 @@ var CompactionOrchestrator = class {
2406
2518
  const driverProvider = this.driver.driverInfo?.id ?? null;
2407
2519
  const coversFromSeq = this.lastCompactionSeq ?? 0;
2408
2520
  const capabilitySignatureAtCompaction = this.opts.getCurrentCapabilitySignature?.() ?? null;
2521
+ const attemptBase = {
2522
+ trigger,
2523
+ model: driverModel,
2524
+ provider: driverProvider,
2525
+ inputTokens: tokensBefore,
2526
+ coversFromSeq,
2527
+ coversToSeq: this.currentSeq,
2528
+ driverSessionIdAtCompaction: driverSessionId,
2529
+ modelAtCompaction: driverModel,
2530
+ capabilitySignatureAtCompaction
2531
+ };
2409
2532
  this.opts.onCompactionStart?.(trigger);
2410
2533
  this.opts.onLog?.(`[compaction] Starting (trigger=${trigger}, tokens=${tokensBefore})`);
2411
2534
  const prompt = buildCompactionPrompt({
@@ -2414,6 +2537,33 @@ var CompactionOrchestrator = class {
2414
2537
  mcpDirectives: this.opts.mcpDirectives,
2415
2538
  focus
2416
2539
  });
2540
+ const { summaryText, promptError } = await this._runCompactionPrompt(prompt);
2541
+ if (promptError !== null) {
2542
+ this._emitThrownFailure(attemptBase, promptError);
2543
+ return null;
2544
+ }
2545
+ const actualUsage = this.driver.getTokenUsage();
2546
+ const tokensAfter = actualUsage?.outputTokens ?? Math.ceil(summaryText.length / 4);
2547
+ const validation = validateCompactionOutput(summaryText, tokensBefore, tokensAfter);
2548
+ if (!validation.ok) {
2549
+ this._emitValidationFailure(attemptBase, validation.errorCode, tokensAfter);
2550
+ return null;
2551
+ }
2552
+ return await this._completeCompaction(
2553
+ attemptBase,
2554
+ trigger,
2555
+ summaryText,
2556
+ tokensBefore,
2557
+ tokensAfter,
2558
+ coversFromSeq
2559
+ );
2560
+ }
2561
+ /**
2562
+ * Drive the compaction `prompt()` call, capturing summary text from the
2563
+ * driver's raw bridge-level events and any thrown error. The listener is
2564
+ * always removed in `finally`.
2565
+ */
2566
+ async _runCompactionPrompt(prompt) {
2417
2567
  let summaryText = "";
2418
2568
  const captureHandler = (raw) => {
2419
2569
  const event = raw;
@@ -2444,59 +2594,55 @@ var CompactionOrchestrator = class {
2444
2594
  } finally {
2445
2595
  this.driver.removeListener("agent-event", captureHandler);
2446
2596
  }
2447
- if (promptError !== null) {
2448
- const classified = classifyClaudeSdkError(
2449
- promptError instanceof Error ? promptError.message : String(promptError)
2450
- );
2451
- const errorCode = classified.category === "rate_limit" ? "rate_limit" : classified.category === "auth" ? "auth" : classified.category === "network" ? "timeout" : classified.category === "validation" ? "parse_error" : classified.category;
2452
- this.opts.onLog?.(`[compaction] Failed: ${classified.message} (${errorCode})`);
2453
- this.opts.onCompactionAttempt?.({
2454
- type: "compaction_attempt",
2455
- status: "failed",
2456
- errorCode,
2457
- trigger,
2458
- summary: "",
2459
- model: driverModel,
2460
- provider: driverProvider,
2461
- inputTokens: tokensBefore,
2462
- outputTokens: 0,
2463
- coversFromSeq,
2464
- coversToSeq: this.currentSeq,
2465
- driverSessionIdAtCompaction: driverSessionId,
2466
- modelAtCompaction: driverModel,
2467
- capabilitySignatureAtCompaction,
2468
- validatedAt: null
2469
- });
2470
- this.opts.onCompactionEnd?.();
2471
- return null;
2472
- }
2473
- const actualUsage = this.driver.getTokenUsage();
2474
- const tokensAfter = actualUsage?.outputTokens ?? Math.ceil(summaryText.length / 4);
2475
- const validation = validateCompactionOutput(summaryText, tokensBefore, tokensAfter);
2476
- if (!validation.ok) {
2477
- this.opts.onLog?.(
2478
- `[compaction] Output failed validation (${validation.errorCode}); recording failed attempt`
2479
- );
2480
- this.opts.onCompactionAttempt?.({
2481
- type: "compaction_attempt",
2482
- status: "failed",
2483
- errorCode: validation.errorCode,
2484
- trigger,
2485
- summary: "",
2486
- model: driverModel,
2487
- provider: driverProvider,
2488
- inputTokens: tokensBefore,
2489
- outputTokens: tokensAfter,
2490
- coversFromSeq,
2491
- coversToSeq: this.currentSeq,
2492
- driverSessionIdAtCompaction: driverSessionId,
2493
- modelAtCompaction: driverModel,
2494
- capabilitySignatureAtCompaction,
2495
- validatedAt: null
2496
- });
2497
- this.opts.onCompactionEnd?.();
2498
- return null;
2499
- }
2597
+ return { summaryText, promptError };
2598
+ }
2599
+ /**
2600
+ * Record a `failed` attempt for a thrown `prompt()` error: classify the
2601
+ * error, map it to an error code, log, emit the attempt event, and signal
2602
+ * compaction end.
2603
+ */
2604
+ _emitThrownFailure(attemptBase, promptError) {
2605
+ const classified = classifyClaudeSdkError(
2606
+ promptError instanceof Error ? promptError.message : String(promptError)
2607
+ );
2608
+ const errorCode = classified.category === "rate_limit" ? "rate_limit" : classified.category === "auth" ? "auth" : classified.category === "network" ? "timeout" : classified.category === "validation" ? "parse_error" : classified.category;
2609
+ this.opts.onLog?.(`[compaction] Failed: ${classified.message} (${errorCode})`);
2610
+ this.opts.onCompactionAttempt?.({
2611
+ type: "compaction_attempt",
2612
+ status: "failed",
2613
+ errorCode,
2614
+ summary: "",
2615
+ outputTokens: 0,
2616
+ validatedAt: null,
2617
+ ...attemptBase
2618
+ });
2619
+ this.opts.onCompactionEnd?.();
2620
+ }
2621
+ /**
2622
+ * Record a `failed` attempt for output that passed `prompt()` but failed
2623
+ * structural validation. Logs, emits the attempt event, and signals
2624
+ * compaction end.
2625
+ */
2626
+ _emitValidationFailure(attemptBase, errorCode, tokensAfter) {
2627
+ this.opts.onLog?.(
2628
+ `[compaction] Output failed validation (${errorCode}); recording failed attempt`
2629
+ );
2630
+ this.opts.onCompactionAttempt?.({
2631
+ type: "compaction_attempt",
2632
+ status: "failed",
2633
+ errorCode,
2634
+ summary: "",
2635
+ outputTokens: tokensAfter,
2636
+ validatedAt: null,
2637
+ ...attemptBase
2638
+ });
2639
+ this.opts.onCompactionEnd?.();
2640
+ }
2641
+ /**
2642
+ * Success path: emit the attempt + snapshot events, update internal state,
2643
+ * and (unless hibernating) reset the driver and inject the snapshot.
2644
+ */
2645
+ async _completeCompaction(attemptBase, trigger, summaryText, tokensBefore, tokensAfter, coversFromSeq) {
2500
2646
  const validatedAt = (/* @__PURE__ */ new Date()).toISOString();
2501
2647
  const snapshot = {
2502
2648
  type: "snapshot",
@@ -2511,18 +2657,10 @@ var CompactionOrchestrator = class {
2511
2657
  type: "compaction_attempt",
2512
2658
  status: "success",
2513
2659
  errorCode: null,
2514
- trigger,
2515
2660
  summary: summaryText,
2516
- model: driverModel,
2517
- provider: driverProvider,
2518
- inputTokens: tokensBefore,
2519
2661
  outputTokens: tokensAfter,
2520
- coversFromSeq,
2521
- coversToSeq: this.currentSeq,
2522
- driverSessionIdAtCompaction: driverSessionId,
2523
- modelAtCompaction: driverModel,
2524
- capabilitySignatureAtCompaction,
2525
- validatedAt
2662
+ validatedAt,
2663
+ ...attemptBase
2526
2664
  });
2527
2665
  this.opts.onSnapshot?.(snapshot);
2528
2666
  this.lastCompactionSeq = this.currentSeq + 1;
@@ -2545,11 +2683,6 @@ The conversation has been compacted. The above snapshot contains the full contex
2545
2683
  );
2546
2684
  return snapshot;
2547
2685
  }
2548
- initFromSnapshot(lastSnapshotSeq, lastSnapshotTime) {
2549
- this.lastCompactionSeq = lastSnapshotSeq;
2550
- this.lastCompactionTime = lastSnapshotTime;
2551
- this.messagesSinceCompaction = 0;
2552
- }
2553
2686
  };
2554
2687
  function rewriteFileChangedPath(event, cwd, mounts) {
2555
2688
  if (event.type !== "file_changed") return event;
@@ -2782,6 +2915,54 @@ function buildRunnerCapabilities(handlers) {
2782
2915
  })
2783
2916
  ];
2784
2917
  }
2918
+ var CLAUDE_CODE_CREDENTIALS_KEY = "CLAUDE_CODE_CREDENTIALS_JSON";
2919
+ async function writeClaudeCodeCredentialsFile(home, json) {
2920
+ const claudeDir = join(home, ".claude");
2921
+ await mkdir(claudeDir, { recursive: true, mode: 448 });
2922
+ const finalPath = join(claudeDir, ".credentials.json");
2923
+ const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
2924
+ await writeFile(tmpPath, json, { mode: 384 });
2925
+ await rename(tmpPath, finalPath);
2926
+ return finalPath;
2927
+ }
2928
+ async function mergeAndRewriteCredentialsFile(home, accessToken, expiresAt) {
2929
+ const credPath = join(home, ".claude", ".credentials.json");
2930
+ let merged = {};
2931
+ if (existsSync(credPath)) {
2932
+ try {
2933
+ merged = JSON.parse(readFileSync(credPath, "utf-8"));
2934
+ } catch {
2935
+ merged = {};
2936
+ }
2937
+ }
2938
+ const oauthBlock = merged.claudeAiOauth ?? {};
2939
+ oauthBlock.accessToken = accessToken;
2940
+ if (expiresAt) oauthBlock.expiresAt = expiresAt;
2941
+ merged.claudeAiOauth = oauthBlock;
2942
+ merged.accessToken = accessToken;
2943
+ return writeClaudeCodeCredentialsFile(home, JSON.stringify(merged));
2944
+ }
2945
+ function extractClaudeAiOauthExpiresAt(json) {
2946
+ let parsed;
2947
+ try {
2948
+ parsed = JSON.parse(json);
2949
+ } catch {
2950
+ return null;
2951
+ }
2952
+ if (!parsed || typeof parsed !== "object") return null;
2953
+ const obj = parsed;
2954
+ const oauth = obj.claudeAiOauth;
2955
+ const raw = oauth?.expiresAt ?? obj.expiresAt;
2956
+ if (raw == null) return null;
2957
+ if (typeof raw === "string") {
2958
+ const t = Date.parse(raw);
2959
+ return Number.isFinite(t) ? new Date(t).toISOString() : null;
2960
+ }
2961
+ if (typeof raw === "number" && Number.isFinite(raw)) {
2962
+ return new Date(raw).toISOString();
2963
+ }
2964
+ return null;
2965
+ }
2785
2966
 
2786
2967
  // runner/src/session-resource-handlers.ts
2787
2968
  function asDeclaration(declaration) {
@@ -2852,54 +3033,6 @@ function buildSessionResourceHandlers(deps) {
2852
3033
  }
2853
3034
  };
2854
3035
  }
2855
- var CLAUDE_CODE_CREDENTIALS_KEY = "CLAUDE_CODE_CREDENTIALS_JSON";
2856
- async function writeClaudeCodeCredentialsFile(home, json) {
2857
- const claudeDir = join(home, ".claude");
2858
- await mkdir(claudeDir, { recursive: true, mode: 448 });
2859
- const finalPath = join(claudeDir, ".credentials.json");
2860
- const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
2861
- await writeFile(tmpPath, json, { mode: 384 });
2862
- await rename(tmpPath, finalPath);
2863
- return finalPath;
2864
- }
2865
- async function mergeAndRewriteCredentialsFile(home, accessToken, expiresAt) {
2866
- const credPath = join(home, ".claude", ".credentials.json");
2867
- let merged = {};
2868
- if (existsSync(credPath)) {
2869
- try {
2870
- merged = JSON.parse(readFileSync(credPath, "utf-8"));
2871
- } catch {
2872
- merged = {};
2873
- }
2874
- }
2875
- const oauthBlock = merged.claudeAiOauth ?? {};
2876
- oauthBlock.accessToken = accessToken;
2877
- if (expiresAt) oauthBlock.expiresAt = expiresAt;
2878
- merged.claudeAiOauth = oauthBlock;
2879
- merged.accessToken = accessToken;
2880
- return writeClaudeCodeCredentialsFile(home, JSON.stringify(merged));
2881
- }
2882
- function extractClaudeAiOauthExpiresAt(json) {
2883
- let parsed;
2884
- try {
2885
- parsed = JSON.parse(json);
2886
- } catch {
2887
- return null;
2888
- }
2889
- if (!parsed || typeof parsed !== "object") return null;
2890
- const obj = parsed;
2891
- const oauth = obj.claudeAiOauth;
2892
- const raw = oauth?.expiresAt ?? obj.expiresAt;
2893
- if (raw == null) return null;
2894
- if (typeof raw === "string") {
2895
- const t = Date.parse(raw);
2896
- return Number.isFinite(t) ? new Date(t).toISOString() : null;
2897
- }
2898
- if (typeof raw === "number" && Number.isFinite(raw)) {
2899
- return new Date(raw).toISOString();
2900
- }
2901
- return null;
2902
- }
2903
3036
 
2904
3037
  // runner/src/session-stimulus.ts
2905
3038
  var DEFAULT_COALESCE_MS = 50;
@@ -3086,6 +3219,17 @@ function ensureGitConfigInclude(managedGitconfigPath) {
3086
3219
  `;
3087
3220
  writeFileSync(gitconfigPath, block, { mode: 420 });
3088
3221
  }
3222
+ function setupManagedGitconfig(projectDir, serverLog) {
3223
+ const managedGitconfigPath = join(projectDir, ".skaile", "managed-gitconfig");
3224
+ process.env.GIT_CONFIG_GLOBAL = managedGitconfigPath;
3225
+ try {
3226
+ ensureGitConfigInclude(managedGitconfigPath);
3227
+ } catch (err) {
3228
+ serverLog.warn("failed to write ~/.gitconfig include \u2014 relying on env only", {
3229
+ error: err instanceof Error ? err.message : String(err)
3230
+ });
3231
+ }
3232
+ }
3089
3233
  async function startAgentServer(opts) {
3090
3234
  const port = opts.port ?? 8080;
3091
3235
  const sessionId = opts.sessionId ?? process.env.SKAILE_SESSION_ID ?? "session-unknown";
@@ -3105,15 +3249,7 @@ async function startAgentServer(opts) {
3105
3249
  const log = (line) => serverLog.info(line);
3106
3250
  const profile = wsConfig.agent_config?.default;
3107
3251
  const maxTurns = wsConfig.agent?.permissions?.max_turns ?? profile?.max_turns;
3108
- const managedGitconfigPath = join(opts.projectDir, ".skaile", "managed-gitconfig");
3109
- process.env.GIT_CONFIG_GLOBAL = managedGitconfigPath;
3110
- try {
3111
- ensureGitConfigInclude(managedGitconfigPath);
3112
- } catch (err) {
3113
- serverLog.warn("failed to write ~/.gitconfig include \u2014 relying on env only", {
3114
- error: err instanceof Error ? err.message : String(err)
3115
- });
3116
- }
3252
+ setupManagedGitconfig(opts.projectDir, serverLog);
3117
3253
  const agentDir = opts.agentDir ?? resolveAgentDir(opts.projectDir);
3118
3254
  if (agentDir) {
3119
3255
  serverLog.info("agent dir resolved", { agentDir });
@@ -3235,475 +3371,1182 @@ async function startAgentServer(opts) {
3235
3371
  timeoutMs
3236
3372
  });
3237
3373
  }
3238
- function buildCapabilityHooks() {
3239
- return {
3240
- sessionId,
3241
- composeTools: () => capabilityRegistry.composeLLMTools(),
3242
- resolve: (name) => {
3243
- const cap = capabilityRegistry.resolve(name);
3244
- if (!cap) return null;
3245
- return capabilityRegistry.list().find((c) => c.name === name) ?? null;
3374
+ function buildCapabilityHooks() {
3375
+ return {
3376
+ sessionId,
3377
+ composeTools: () => capabilityRegistry.composeLLMTools(),
3378
+ resolve: (name) => {
3379
+ const cap = capabilityRegistry.resolve(name);
3380
+ if (!cap) return null;
3381
+ return capabilityRegistry.list().find((c) => c.name === name) ?? null;
3382
+ },
3383
+ invoke: (name, input) => capabilityRegistry.invoke(name, input, { sessionId }),
3384
+ emitRender: (event) => sendEvent(event),
3385
+ emitText: (text) => sendEvent({
3386
+ type: "text",
3387
+ content: text
3388
+ })
3389
+ };
3390
+ }
3391
+ function registerSessionComposition(session) {
3392
+ if (session.composition?.discoverableEntries.length) {
3393
+ registerCompositionCapabilities(capabilityRegistry, session.composition.discoverableEntries);
3394
+ log(
3395
+ `[serve] registered ${session.composition.discoverableEntries.length} composition capabilities`
3396
+ );
3397
+ }
3398
+ }
3399
+ const secretsMode = wsConfig.secrets?.provider ?? "env";
3400
+ wsConfig.secrets?.timeoutMs ?? 3e4;
3401
+ function buildSessionConfig(secretProvider2, apiKey) {
3402
+ return {
3403
+ projectDir: opts.projectDir,
3404
+ agentDir,
3405
+ driver: opts.driver,
3406
+ model: opts.model,
3407
+ provider: opts.provider,
3408
+ promptsDir: opts.promptsDir,
3409
+ sessionId,
3410
+ resumeSessionId,
3411
+ maxTurns,
3412
+ wsConfig,
3413
+ externalPromptFile: process.env.SKAILE_SYSTEM_PROMPT_FILE,
3414
+ secretProvider: secretProvider2,
3415
+ onAuthError,
3416
+ ...apiKey ? { apiKey } : {},
3417
+ // Protocol v2 `<CAPABILITIES>` block — joins each registered capability's
3418
+ // `promptFragment` (framework first, then client, then agent-side
3419
+ // surfaces). Built lazily inside `buildSessionConfig` so newly registered
3420
+ // capabilities (e.g. via mid-session `capability_register` commands) are
3421
+ // reflected on the next session rebuild.
3422
+ additionalPromptSections: (() => {
3423
+ const sections = [];
3424
+ const capSection = buildCapabilitiesPromptSection(capabilityRegistry);
3425
+ if (capSection) sections.push(capSection);
3426
+ return sections.length > 0 ? sections : void 0;
3427
+ })(),
3428
+ watch: {
3429
+ onSyncStatus: (mountId, status) => {
3430
+ sendEvent({
3431
+ type: "sync_status",
3432
+ mountId,
3433
+ ...status,
3434
+ phase: status.phase
3435
+ });
3436
+ if (status.phase === "stubs_ready") {
3437
+ log(
3438
+ `[serve] ${mountId}: file tree ready (${status.totalFiles} files, ${status.stubFiles} stubs)`
3439
+ );
3440
+ } else if (status.phase === "initial_sync_complete") {
3441
+ log(
3442
+ `[serve] ${mountId}: sync complete (${status.downloadedFiles}/${status.totalFiles} downloaded)`
3443
+ );
3444
+ }
3445
+ },
3446
+ onFileChanged: (connectorId, event) => {
3447
+ sendEvent({
3448
+ type: "file_changed",
3449
+ path: event.path,
3450
+ action: event.action,
3451
+ mountId: connectorId
3452
+ });
3453
+ },
3454
+ // Forward connector lifecycle transitions to the platform. This is the
3455
+ // channel deferred (rclone-backed) filesystem mounts report progress on
3456
+ // after session_init_ack — a wedged remote shows as "still syncing".
3457
+ onConnectorStatus: (event) => {
3458
+ sendEvent(event);
3459
+ }
3460
+ },
3461
+ // Bring rclone-backed filesystem mounts up in the background so a slow
3462
+ // remote can't blow the platform's session_init_ack deadline and trigger
3463
+ // the container restart loop.
3464
+ deferFilesystemMounts: true,
3465
+ onLog: (line) => log(`[serve] ${line}`),
3466
+ capabilities: buildCapabilityHooks(),
3467
+ // Hand the registry to the session builder so external (declarative /
3468
+ // recipe-backed) MCP servers are spawned by the RUNNER and their tools
3469
+ // registered as `mcp`-origin capabilities — works on every driver, no
3470
+ // dependence on the claude-agent-sdk `mcpServers` query option.
3471
+ capabilityRegistry,
3472
+ // v3: pre-minted credentials replace the tokenMediator for initial mints.
3473
+ // Refresh routes through `host.refresh_credential` (invoked from the
3474
+ // bridge driver's onAuthError callback). The legacy `tokenMediator`
3475
+ // wire is gone.
3476
+ ...preMintedSecrets ? { preMintedSecrets } : {},
3477
+ ...aiProviderConfigId ? { aiProviderConfigId } : {}
3478
+ };
3479
+ }
3480
+ let resolveSessionReady;
3481
+ let rejectSessionReady;
3482
+ const sessionReady = new Promise((resolve3, reject) => {
3483
+ resolveSessionReady = resolve3;
3484
+ rejectSessionReady = reject;
3485
+ });
3486
+ let sessionConfig;
3487
+ let agentSession;
3488
+ let secretProvider;
3489
+ let runtimeIdentity;
3490
+ let preMintedSecrets;
3491
+ stageMaterializedSkillsForSession();
3492
+ function stageMaterializedSkillsForSession() {
3493
+ try {
3494
+ const { skillsDir } = resolveDriverPaths({
3495
+ driver: opts.driver ?? wsConfig.agent_config?.default?.driver ?? "claude-sdk"
3496
+ });
3497
+ const strict = process.env.SKAILE_STRICT_YAML === "true";
3498
+ const declared = strict ? (wsConfig.dependencies ?? []).flatMap((dep) => {
3499
+ try {
3500
+ const ref = parseAssetRef(dep);
3501
+ return ref.kind === "skill" ? [ref.name] : [];
3502
+ } catch {
3503
+ return [];
3504
+ }
3505
+ }) : [];
3506
+ const stagedSkills = stageMaterializedSkills(opts.projectDir, skillsDir, {
3507
+ strict,
3508
+ declared
3509
+ });
3510
+ if (stagedSkills.length > 0) {
3511
+ log(
3512
+ `[serve] staged ${stagedSkills.length} materialized skill(s): ${stagedSkills.join(", ")}`
3513
+ );
3514
+ }
3515
+ } catch {
3516
+ }
3517
+ }
3518
+ if (secretsMode === "provisioned") {
3519
+ log("[serve] secrets mode: provisioned \u2014 waiting for provision_secrets command");
3520
+ } else {
3521
+ sessionConfig = buildSessionConfig();
3522
+ agentSession = await createAgentSession(sessionConfig);
3523
+ emitSystemPromptComposed(agentSession, sendEvent);
3524
+ registerSessionComposition(agentSession);
3525
+ resolveSessionReady();
3526
+ }
3527
+ let resourceManager = secretsMode === "env" ? agentSession.resourceManager : null;
3528
+ if (secretsMode === "env") {
3529
+ if (!agentSession.provider) {
3530
+ log(
3531
+ `[serve] WARNING: no API key found for ${agentSession.provider ?? "anthropic"} \u2014 agent calls will fail`
3532
+ );
3533
+ log(`[serve] Pass via env var (e.g. ANTHROPIC_API_KEY) or .skaile/settings.json`);
3534
+ }
3535
+ if (resourceManager) {
3536
+ const watched = resourceManager.watchedResources ?? [];
3537
+ if (watched.length > 0) {
3538
+ log(`[serve] watching ${watched.length} resource(s): ${watched.join(", ")}`);
3539
+ }
3540
+ }
3541
+ }
3542
+ if (secretsMode === "env" && agentSession.runtimeAssets) {
3543
+ emitRuntimeAssetWarnings(agentSession.runtimeAssets);
3544
+ }
3545
+ validateSkaileYamlDependencies();
3546
+ function emitRuntimeAssetWarnings(runtimeAssets) {
3547
+ for (const w of runtimeAssets.warnings) {
3548
+ const asset = w.code === "missing_driver" && w.declarationId ? `${w.declarationId} \u2192 ${w.name ?? "unknown"}` : w.name ?? w.code;
3549
+ log(`[serve] validation_warning: ${w.code}: ${w.message}`);
3550
+ sendEvent({
3551
+ type: "validation_warning",
3552
+ asset,
3553
+ reason: w.code,
3554
+ suggestion: w.message
3555
+ });
3556
+ }
3557
+ }
3558
+ function validateSkaileYamlDependencies() {
3559
+ try {
3560
+ const wsConfig2 = resolveSkWorkspaceConfig(opts.projectDir);
3561
+ for (const dep of wsConfig2.dependencies ?? []) {
3562
+ let ref;
3563
+ try {
3564
+ const parsed = parseAssetRef(dep);
3565
+ if (parsed.kind !== "skill") continue;
3566
+ ref = parsed.name;
3567
+ } catch {
3568
+ continue;
3569
+ }
3570
+ const skillDir = join(opts.projectDir, ".claude", "skills", ref);
3571
+ if (!existsSync(skillDir)) {
3572
+ log(`[serve] validation_warning: dependency "${dep}" not found \u2014 continuing without it`);
3573
+ sendEvent({
3574
+ type: "validation_warning",
3575
+ asset: dep,
3576
+ reason: "not_found",
3577
+ suggestion: "Sync the catalog and re-add this asset"
3578
+ });
3579
+ }
3580
+ }
3581
+ } catch {
3582
+ }
3583
+ }
3584
+ const normalizer = new EventNormalizer();
3585
+ function forwardNormalizedEvent(event) {
3586
+ if (compacting) return;
3587
+ const isTransient = event.type === "status" || event.type === "sync_status" || event.type === "resources_available" || event.type === "resource_response" || event.type === "commands_available";
3588
+ if (!isTransient) {
3589
+ eventSeq++;
3590
+ compactionOrchestrator?.trackMessage(eventSeq);
3591
+ }
3592
+ if (event.type === "finished") {
3593
+ if (!compacting && compactionOrchestrator?.shouldCompact()) {
3594
+ compactionOrchestrator.compact("threshold").catch((err) => {
3595
+ log(
3596
+ `[serve] auto-compaction failed: ${err instanceof Error ? err.message : String(err)}`
3597
+ );
3598
+ });
3599
+ }
3600
+ }
3601
+ if (!resourceManager) {
3602
+ sendEvent(event);
3603
+ return;
3604
+ }
3605
+ const mounts = resourceManager.listConnectors().filter((info) => typeof info.mountPath === "string").map((info) => ({
3606
+ id: info.id,
3607
+ mountPath: info.mountPath
3608
+ }));
3609
+ sendEvent(rewriteFileChangedPath(event, opts.projectDir, mounts));
3610
+ }
3611
+ let driverStarted = false;
3612
+ let driver = secretsMode === "env" ? agentSession.driver : void 0;
3613
+ let secretsProvisioned = secretsMode === "env";
3614
+ let eventSeq = 0;
3615
+ let compacting = false;
3616
+ let compactionOrchestrator = null;
3617
+ const compactionConfig = wsConfig.compaction;
3618
+ function rebuildCompactionOrchestrator() {
3619
+ if (!driver) return;
3620
+ compactionOrchestrator = new CompactionOrchestrator({
3621
+ driver,
3622
+ config: compactionConfig,
3623
+ onCompactionStart: (trigger) => {
3624
+ compacting = true;
3625
+ sendEvent({ type: "status", phase: "compacting" });
3626
+ log(`[serve] compaction started (trigger=${trigger})`);
3627
+ },
3628
+ onSnapshot: (snapshot) => {
3629
+ sendEvent(snapshot);
3630
+ },
3631
+ onCompactionAttempt: (event) => {
3632
+ sendEvent(event);
3633
+ },
3634
+ onCompactionEnd: () => {
3635
+ compacting = false;
3636
+ sendEvent({ type: "status", phase: "idle" });
3637
+ },
3638
+ onLog: (line) => log(`[serve] ${line}`),
3639
+ // Phase 3: stamp every CompactionAttemptEvent with the registry's
3640
+ // current signature so wake-time tier-1 selection has the input it
3641
+ // needs. The registry exists for the entire serve lifetime, so this
3642
+ // closure is safe across orchestrator rebuilds.
3643
+ getCurrentCapabilitySignature: () => computeCapabilitySignature(capabilityRegistry.list())
3644
+ });
3645
+ }
3646
+ if (secretsMode === "env" && driver) {
3647
+ rebuildCompactionOrchestrator();
3648
+ }
3649
+ const activeFlows = /* @__PURE__ */ new Map();
3650
+ let stimulusBus = null;
3651
+ const FLOW_TURN_TIMEOUT_MS = 4 * 60 * 60 * 1e3;
3652
+ function getOnlyActiveFlow() {
3653
+ const iter = activeFlows.values().next();
3654
+ return iter.done ? null : iter.value;
3655
+ }
3656
+ function getOrCreateStimulusBus() {
3657
+ if (stimulusBus) return stimulusBus;
3658
+ stimulusBus = createSessionStimulusBus({
3659
+ driveTurn: async (promptFragment, metas) => {
3660
+ const lastMeta = metas[metas.length - 1] ?? {};
3661
+ const runId = typeof lastMeta.runId === "string" ? lastMeta.runId : null;
3662
+ const entry = runId ? activeFlows.get(runId) : getOnlyActiveFlow();
3663
+ if (!entry) {
3664
+ serverLog.warn("driveTurn fired with no active flow", { runId });
3665
+ return;
3666
+ }
3667
+ const lastKind = lastMeta.kind;
3668
+ let stimulus;
3669
+ if (lastKind === "flow_started") {
3670
+ stimulus = { kind: "flow_started" };
3671
+ } else if (lastKind === "user_message" && typeof lastMeta.text === "string") {
3672
+ stimulus = {
3673
+ kind: "user_message",
3674
+ text: String(lastMeta.text),
3675
+ senderId: String(lastMeta.senderId ?? "user")
3676
+ };
3677
+ } else {
3678
+ stimulus = { kind: "state_changed" };
3679
+ }
3680
+ const fullPrompt = buildOrchestratorPrompt(
3681
+ entry.flowDef,
3682
+ entry.adapter.getExecution(entry.handle),
3683
+ stimulus
3684
+ );
3685
+ if (!driverStarted) {
3686
+ await driver.start();
3687
+ driverStarted = true;
3688
+ driver.on("agent-event", (event) => {
3689
+ const normalized = normalizer.normalize(event);
3690
+ for (const e of normalized) forwardNormalizedEvent(e);
3691
+ });
3692
+ driver.on("error", (err) => {
3693
+ log(`[serve] driver error: ${err.message}`);
3694
+ sendEvent({ type: "error", message: err.message, fatal: true });
3695
+ });
3696
+ }
3697
+ await awaitFlowAgentEnd(fullPrompt, FLOW_TURN_TIMEOUT_MS);
3246
3698
  },
3247
- invoke: (name, input) => capabilityRegistry.invoke(name, input, { sessionId }),
3248
- emitRender: (event) => sendEvent(event),
3249
- emitText: (text) => sendEvent({
3250
- type: "text",
3251
- content: text
3252
- })
3253
- };
3699
+ log: serverLog
3700
+ });
3701
+ return stimulusBus;
3254
3702
  }
3255
- function registerSessionComposition(session) {
3256
- if (session.composition?.discoverableEntries.length) {
3257
- registerCompositionCapabilities(capabilityRegistry, session.composition.discoverableEntries);
3258
- log(
3259
- `[serve] registered ${session.composition.discoverableEntries.length} composition capabilities`
3703
+ function awaitFlowAgentEnd(prompt, timeoutMs) {
3704
+ return new Promise((resolve3, reject) => {
3705
+ let settled = false;
3706
+ const onEvent = (event) => {
3707
+ if (event.type === "agent_end") done();
3708
+ else if (event.type === "error") {
3709
+ const msg = event.error ?? event.message ?? "Agent error";
3710
+ done(new Error(msg));
3711
+ }
3712
+ };
3713
+ const done = (err) => {
3714
+ if (settled) return;
3715
+ settled = true;
3716
+ clearTimeout(timer);
3717
+ driver.off?.(
3718
+ "agent-event",
3719
+ onEvent
3720
+ ) ?? driver.removeListener("agent-event", onEvent);
3721
+ if (err) reject(err);
3722
+ else resolve3();
3723
+ };
3724
+ const timer = setTimeout(
3725
+ () => done(new Error(`Flow turn timed out after ${timeoutMs / 1e3}s`)),
3726
+ timeoutMs
3260
3727
  );
3261
- }
3728
+ driver.on("agent-event", onEvent);
3729
+ driver.prompt(prompt).catch(done);
3730
+ });
3262
3731
  }
3263
- const secretsMode = wsConfig.secrets?.provider ?? "env";
3264
- wsConfig.secrets?.timeoutMs ?? 3e4;
3265
- function buildSessionConfig(secretProvider2, apiKey) {
3266
- return {
3267
- projectDir: opts.projectDir,
3268
- agentDir,
3269
- driver: opts.driver,
3270
- model: opts.model,
3271
- provider: opts.provider,
3272
- promptsDir: opts.promptsDir,
3273
- sessionId,
3274
- resumeSessionId,
3275
- maxTurns,
3276
- wsConfig,
3277
- externalPromptFile: process.env.SKAILE_SYSTEM_PROMPT_FILE,
3278
- secretProvider: secretProvider2,
3279
- onAuthError,
3280
- ...apiKey ? { apiKey } : {},
3281
- // Protocol v2 `<CAPABILITIES>` block — joins each registered capability's
3282
- // `promptFragment` (framework first, then client, then agent-side
3283
- // surfaces). Built lazily inside `buildSessionConfig` so newly registered
3284
- // capabilities (e.g. via mid-session `capability_register` commands) are
3285
- // reflected on the next session rebuild.
3286
- additionalPromptSections: (() => {
3287
- const sections = [];
3288
- const capSection = buildCapabilitiesPromptSection(capabilityRegistry);
3289
- if (capSection) sections.push(capSection);
3290
- return sections.length > 0 ? sections : void 0;
3291
- })(),
3292
- watch: {
3293
- onSyncStatus: (mountId, status) => {
3294
- sendEvent({
3295
- type: "sync_status",
3296
- mountId,
3297
- ...status,
3298
- phase: status.phase
3299
- });
3300
- if (status.phase === "stubs_ready") {
3301
- log(
3302
- `[serve] ${mountId}: file tree ready (${status.totalFiles} files, ${status.stubFiles} stubs)`
3303
- );
3304
- } else if (status.phase === "initial_sync_complete") {
3732
+ async function createActiveFlow(flowDef, seedOrExecution) {
3733
+ if (!resourceManager) {
3734
+ registerBuiltinConnectors();
3735
+ resourceManager = new ConnectorManager(opts.projectDir, {
3736
+ catalogEntries: getSessionCatalogEntries(agentSession)
3737
+ });
3738
+ log("[serve] created ConnectorManager for flow execution (no other connectors declared)");
3739
+ }
3740
+ const bus = getOrCreateStimulusBus();
3741
+ const baseOptions = {
3742
+ stimulusBus: bus,
3743
+ useBusForStimulus: true
3744
+ };
3745
+ const options = "execution" in seedOrExecution ? { flow: flowDef, execution: seedOrExecution.execution, ...baseOptions } : { flow: flowDef, seed: seedOrExecution.seed, ...baseOptions };
3746
+ const seedRunId = "execution" in seedOrExecution ? seedOrExecution.execution.runId : seedOrExecution.seed.runId;
3747
+ const connectorId = `flow:${seedRunId}`;
3748
+ const handle = await resourceManager.connect({
3749
+ id: connectorId,
3750
+ driver: "flow",
3751
+ access: "read-write",
3752
+ options
3753
+ });
3754
+ const adapter = resourceManager.get(connectorId).connector;
3755
+ const runId = adapter.getExecution(handle).runId;
3756
+ adapter.onStateChange(handle, (state) => {
3757
+ sendEvent({
3758
+ type: "state_changed",
3759
+ store: `flow:${state.runId}`,
3760
+ state
3761
+ });
3762
+ });
3763
+ if (agentSession?.driverType === "claude-sdk") {
3764
+ try {
3765
+ const server = await buildSdkConnectorTools(resourceManager, { z: z });
3766
+ if (server) {
3767
+ if (driverStarted) {
3768
+ const prevSessionId = driver.runtimeSessionId;
3305
3769
  log(
3306
- `[serve] ${mountId}: sync complete (${status.downloadedFiles}/${status.totalFiles} downloaded)`
3770
+ `[serve] restarting driver to register flow connector tools (resume=${prevSessionId ?? "none"})`
3307
3771
  );
3772
+ await agentSession.dispose();
3773
+ agentSession = await createAgentSession({
3774
+ ...sessionConfig,
3775
+ resumeSessionId: prevSessionId
3776
+ });
3777
+ emitSystemPromptComposed(agentSession, sendEvent);
3778
+ driver = agentSession.driver;
3779
+ normalizer.reset();
3780
+ driverStarted = false;
3781
+ rebuildCompactionOrchestrator();
3308
3782
  }
3309
- },
3310
- onFileChanged: (connectorId, event) => {
3311
- sendEvent({
3312
- type: "file_changed",
3313
- path: event.path,
3314
- action: event.action,
3315
- mountId: connectorId
3316
- });
3317
- },
3318
- // Forward connector lifecycle transitions to the platform. This is the
3319
- // channel deferred (rclone-backed) filesystem mounts report progress on
3320
- // after session_init_ack — a wedged remote shows as "still syncing".
3321
- onConnectorStatus: (event) => {
3322
- sendEvent(event);
3783
+ driver.config.mcpServers = { "skaile-connectors": server };
3784
+ log(`[serve] rebuilt MCP connector tools for flow ${connectorId}`);
3785
+ } else {
3786
+ log(`[serve] buildSdkConnectorTools returned null for flow ${connectorId}`);
3323
3787
  }
3324
- },
3325
- // Bring rclone-backed filesystem mounts up in the background so a slow
3326
- // remote can't blow the platform's session_init_ack deadline and trigger
3327
- // the container restart loop.
3328
- deferFilesystemMounts: true,
3329
- onLog: (line) => log(`[serve] ${line}`),
3330
- capabilities: buildCapabilityHooks(),
3331
- // Hand the registry to the session builder so external (declarative /
3332
- // recipe-backed) MCP servers are spawned by the RUNNER and their tools
3333
- // registered as `mcp`-origin capabilities — works on every driver, no
3334
- // dependence on the claude-agent-sdk `mcpServers` query option.
3335
- capabilityRegistry,
3336
- // v3: pre-minted credentials replace the tokenMediator for initial mints.
3337
- // Refresh routes through `host.refresh_credential` (invoked from the
3338
- // bridge driver's onAuthError callback). The legacy `tokenMediator`
3339
- // wire is gone.
3340
- ...preMintedSecrets ? { preMintedSecrets } : {},
3341
- ...aiProviderConfigId ? { aiProviderConfigId } : {}
3342
- };
3788
+ } catch (err) {
3789
+ const errMsg = err instanceof Error ? err.message : String(err);
3790
+ log(`[serve] failed to rebuild MCP tools for flow: ${errMsg}`);
3791
+ }
3792
+ }
3793
+ const entry = { runId, flowDef, adapter, handle, connectorId };
3794
+ activeFlows.set(runId, entry);
3795
+ return entry;
3343
3796
  }
3344
- let resolveSessionReady;
3345
- let rejectSessionReady;
3346
- const sessionReady = new Promise((resolve3, reject) => {
3347
- resolveSessionReady = resolve3;
3348
- rejectSessionReady = reject;
3349
- });
3350
- let sessionConfig;
3351
- let agentSession;
3352
- let secretProvider;
3353
- let runtimeIdentity;
3354
- let preMintedSecrets;
3797
+ const refreshFlagPath = join(opts.projectDir, ".skaile", "refresh-request");
3798
+ try {
3799
+ mkdirSync(join(opts.projectDir, ".skaile"), { recursive: true });
3800
+ if (!existsSync(refreshFlagPath)) {
3801
+ writeFileSync(refreshFlagPath, "", { mode: 432, flag: "a" });
3802
+ }
3803
+ } catch (err) {
3804
+ serverLog.warn("failed to pre-create refresh-flag \u2014 wake-mid-401 disabled", {
3805
+ error: err instanceof Error ? err.message : String(err)
3806
+ });
3807
+ }
3808
+ let lastRefreshAt = 0;
3809
+ const REFRESH_DEBOUNCE_MS = 2e3;
3810
+ let refreshFlagWatcher = null;
3355
3811
  try {
3356
- const { skillsDir } = resolveDriverPaths({
3357
- driver: opts.driver ?? wsConfig.agent_config?.default?.driver ?? "claude-sdk"
3812
+ refreshFlagWatcher = watch(refreshFlagPath, (event) => {
3813
+ if (event !== "change") return;
3814
+ const now = Date.now();
3815
+ if (now - lastRefreshAt < REFRESH_DEBOUNCE_MS) return;
3816
+ lastRefreshAt = now;
3817
+ void dispatchRefreshToExposedGitMounts(resourceManager, serverLog);
3818
+ });
3819
+ } catch (err) {
3820
+ serverLog.warn("fs.watch on refresh-flag failed \u2014 wake-mid-401 disabled", {
3821
+ error: err instanceof Error ? err.message : String(err)
3358
3822
  });
3359
- const strict = process.env.SKAILE_STRICT_YAML === "true";
3360
- const declared = strict ? (wsConfig.dependencies ?? []).flatMap((dep) => {
3823
+ }
3824
+ async function cleanup() {
3825
+ try {
3826
+ await Promise.race([
3827
+ sessionReady,
3828
+ new Promise((resolve3) => {
3829
+ const t = setTimeout(resolve3, 2e3);
3830
+ t.unref?.();
3831
+ })
3832
+ ]);
3833
+ } catch {
3834
+ }
3835
+ try {
3836
+ driver?.kill();
3837
+ } catch {
3838
+ }
3839
+ aiCredentialRefreshScheduler.cancel();
3840
+ for (const [callId, pending] of pendingCapabilityCalls) {
3841
+ clearTimeout(pending.timer);
3842
+ pending.reject(new Error(`Session terminated before capability_result (callId=${callId})`));
3843
+ }
3844
+ pendingCapabilityCalls.clear();
3845
+ try {
3846
+ refreshFlagWatcher?.close();
3847
+ } catch {
3848
+ }
3849
+ refreshFlagWatcher = null;
3850
+ if (agentSession) {
3361
3851
  try {
3362
- const ref = parseAssetRef(dep);
3363
- return ref.kind === "skill" ? [ref.name] : [];
3852
+ await agentSession.dispose();
3364
3853
  } catch {
3365
- return [];
3366
3854
  }
3367
- }) : [];
3368
- const stagedSkills = stageMaterializedSkills(opts.projectDir, skillsDir, { strict, declared });
3369
- if (stagedSkills.length > 0) {
3370
- log(
3371
- `[serve] staged ${stagedSkills.length} materialized skill(s): ${stagedSkills.join(", ")}`
3372
- );
3373
3855
  }
3374
- } catch {
3375
- }
3376
- if (secretsMode === "provisioned") {
3377
- log("[serve] secrets mode: provisioned \u2014 waiting for provision_secrets command");
3378
- } else {
3379
- sessionConfig = buildSessionConfig();
3380
- agentSession = await createAgentSession(sessionConfig);
3381
- emitSystemPromptComposed(agentSession, sendEvent);
3382
- registerSessionComposition(agentSession);
3383
- resolveSessionReady();
3384
3856
  }
3385
- let resourceManager = secretsMode === "env" ? agentSession.resourceManager : null;
3386
- if (secretsMode === "env") {
3387
- if (!agentSession.provider) {
3388
- log(
3389
- `[serve] WARNING: no API key found for ${agentSession.provider ?? "anthropic"} \u2014 agent calls will fail`
3390
- );
3391
- log(`[serve] Pass via env var (e.g. ANTHROPIC_API_KEY) or .skaile/settings.json`);
3857
+ async function awaitSessionReadyOrFail() {
3858
+ try {
3859
+ await sessionReady;
3860
+ return true;
3861
+ } catch (err) {
3862
+ sendEvent({
3863
+ type: "error",
3864
+ message: `Session not available: ${err.message}`,
3865
+ fatal: true
3866
+ });
3867
+ return false;
3392
3868
  }
3393
- if (resourceManager) {
3394
- const watched = resourceManager.watchedResources ?? [];
3395
- if (watched.length > 0) {
3396
- log(`[serve] watching ${watched.length} resource(s): ${watched.join(", ")}`);
3869
+ }
3870
+ function registerInitClientCapabilities(incoming) {
3871
+ for (const wire of incoming) {
3872
+ if (wire.audience && !wire.audience.includes("llm") && !wire.audience.includes("user")) {
3873
+ continue;
3397
3874
  }
3875
+ const stub = {
3876
+ ...wire,
3877
+ inputZod: { parse: (v) => v },
3878
+ outputZod: void 0,
3879
+ handler: buildClientCapabilityHandler2(wire)
3880
+ };
3881
+ capabilityRegistry.register(stub, "client");
3398
3882
  }
3399
3883
  }
3400
- if (secretsMode === "env" && agentSession.runtimeAssets) {
3401
- for (const w of agentSession.runtimeAssets.warnings) {
3402
- const asset = w.code === "missing_driver" && w.declarationId ? `${w.declarationId} \u2192 ${w.name ?? "unknown"}` : w.name ?? w.code;
3403
- log(`[serve] validation_warning: ${w.code}: ${w.message}`);
3884
+ async function provisionInitSecrets(secrets) {
3885
+ let initialAiCredentialExpiresAt = null;
3886
+ if (secrets) {
3887
+ const ccdValue = secrets[CLAUDE_CODE_CREDENTIALS_KEY];
3888
+ if (ccdValue) {
3889
+ const home = process.env.HOME ?? homedir();
3890
+ if (home) {
3891
+ try {
3892
+ await writeClaudeCodeCredentialsFile(home, ccdValue);
3893
+ log(
3894
+ `[serve] wrote Claude Code credentials file to ${home}/.claude/.credentials.json (mode 0600)`
3895
+ );
3896
+ } catch (err) {
3897
+ const errMsg = err instanceof Error ? err.message : String(err);
3898
+ log(`[serve] failed to write Claude Code credentials file: ${errMsg}`);
3899
+ }
3900
+ }
3901
+ initialAiCredentialExpiresAt = extractClaudeAiOauthExpiresAt(ccdValue);
3902
+ }
3903
+ const otherSecrets = Object.fromEntries(
3904
+ Object.entries(secrets).filter(([k]) => k !== CLAUDE_CODE_CREDENTIALS_KEY)
3905
+ );
3906
+ const provider = new InMemorySecretProvider();
3907
+ provider.provision(otherSecrets);
3908
+ secretProvider = provider;
3909
+ secretsProvisioned = true;
3910
+ }
3911
+ return initialAiCredentialExpiresAt;
3912
+ }
3913
+ async function buildSessionFromInit(secrets) {
3914
+ const effectiveProvider = opts.provider ?? "anthropic";
3915
+ const envKey = providerEnvKey(effectiveProvider);
3916
+ const apiKeyFromSecrets = secrets?.[envKey];
3917
+ sessionConfig = buildSessionConfig(secretProvider, apiKeyFromSecrets);
3918
+ if (!agentSession) {
3919
+ agentSession = await createAgentSession(sessionConfig);
3920
+ emitSystemPromptComposed(agentSession, sendEvent);
3921
+ resourceManager = agentSession.resourceManager;
3922
+ driver = agentSession.driver;
3923
+ rebuildCompactionOrchestrator();
3924
+ registerSessionComposition(agentSession);
3404
3925
  sendEvent({
3405
- type: "validation_warning",
3406
- asset,
3407
- reason: w.code,
3408
- suggestion: w.message
3926
+ type: "resources_available",
3927
+ ...splitConnectorsForWire(resourceManager)
3928
+ });
3929
+ agentSession.startWatching();
3930
+ capabilityRegistry.setRemoteInvoker(remoteCapabilityInvoker);
3931
+ const runnerCaps = buildRunnerCapabilities({
3932
+ // Resource hot-plug handlers, wired to the live session's
3933
+ // ConnectorManager + PreMintedSecretProvider via getters because
3934
+ // both are lazily (re)assigned in this scope.
3935
+ ...buildSessionResourceHandlers({
3936
+ getResourceManager: () => resourceManager,
3937
+ getPreMintedSecrets: () => preMintedSecrets,
3938
+ emitResourcesAvailable: () => {
3939
+ sendEvent({
3940
+ type: "resources_available",
3941
+ ...splitConnectorsForWire(resourceManager)
3942
+ });
3943
+ },
3944
+ log
3945
+ })
3946
+ // setLogLevel left undefined — the cap's built-in fallback calls
3947
+ // getLogStore()?.setLevel(...) directly. setState / lifecycle
3948
+ // remain stubs (not resource hot-plug; out of Sprint 2 scope).
3409
3949
  });
3950
+ for (const cap of runnerCaps) {
3951
+ capabilityRegistry.register(cap, "agent");
3952
+ }
3953
+ log("[serve] session built from session_init envelope");
3410
3954
  }
3411
3955
  }
3412
- try {
3413
- const wsConfig2 = resolveSkWorkspaceConfig(opts.projectDir);
3414
- for (const dep of wsConfig2.dependencies ?? []) {
3415
- let ref;
3416
- try {
3417
- const parsed = parseAssetRef(dep);
3418
- if (parsed.kind !== "skill") continue;
3419
- ref = parsed.name;
3420
- } catch {
3421
- continue;
3422
- }
3423
- const skillDir = join(opts.projectDir, ".claude", "skills", ref);
3424
- if (!existsSync(skillDir)) {
3425
- log(`[serve] validation_warning: dependency "${dep}" not found \u2014 continuing without it`);
3426
- sendEvent({
3427
- type: "validation_warning",
3428
- asset: dep,
3429
- reason: "not_found",
3430
- suggestion: "Sync the catalog and re-add this asset"
3431
- });
3432
- }
3956
+ async function recreateSessionForInit(pendingResumeSessionId) {
3957
+ await agentSession.dispose();
3958
+ agentSession = await createAgentSession({
3959
+ ...sessionConfig,
3960
+ ...aiProviderConfigId ? { aiProviderConfigId } : {},
3961
+ ...pendingResumeSessionId ? { resumeSessionId: pendingResumeSessionId } : {}
3962
+ });
3963
+ emitSystemPromptComposed(agentSession, sendEvent);
3964
+ driver = agentSession.driver;
3965
+ normalizer.reset();
3966
+ driverStarted = false;
3967
+ rebuildCompactionOrchestrator();
3968
+ }
3969
+ async function applyInitSharedState(sharedState) {
3970
+ if (!ensureResourceManagerForSharedState()) {
3971
+ sendEvent({ type: "configured" });
3972
+ return false;
3433
3973
  }
3434
- } catch {
3974
+ let storesRegistered = 0;
3975
+ for (const stateConfig of sharedState) {
3976
+ if (await registerSharedStateStore(stateConfig)) storesRegistered++;
3977
+ }
3978
+ await rebuildMcpToolsForNewStores(storesRegistered);
3979
+ return true;
3435
3980
  }
3436
- const normalizer = new EventNormalizer();
3437
- function forwardNormalizedEvent(event) {
3438
- if (compacting) return;
3439
- const isTransient = event.type === "status" || event.type === "sync_status" || event.type === "resources_available" || event.type === "resource_response" || event.type === "commands_available";
3440
- if (!isTransient) {
3441
- eventSeq++;
3442
- compactionOrchestrator?.trackMessage(eventSeq);
3981
+ function ensureResourceManagerForSharedState() {
3982
+ if (resourceManager) return true;
3983
+ try {
3984
+ registerBuiltinConnectors();
3985
+ resourceManager = new ConnectorManager(opts.projectDir, {
3986
+ catalogEntries: getSessionCatalogEntries(agentSession)
3987
+ });
3988
+ log("[serve] created ConnectorManager for sharedState (no connectors were declared)");
3989
+ return true;
3990
+ } catch (err) {
3991
+ const errMsg = err instanceof Error ? err.message : String(err);
3992
+ log(`[serve] failed to create ConnectorManager: ${errMsg}`);
3993
+ return false;
3443
3994
  }
3444
- if (event.type === "finished") {
3445
- if (!compacting && compactionOrchestrator?.shouldCompact()) {
3446
- compactionOrchestrator.compact("threshold").catch((err) => {
3995
+ }
3996
+ async function registerSharedStateStore(stateConfig) {
3997
+ try {
3998
+ if (resourceManager.has(stateConfig.id)) {
3999
+ try {
4000
+ const snapshot = await resourceManager.executeOp(stateConfig.id, "get", {});
4001
+ const state = snapshot ? JSON.parse(snapshot) : {};
4002
+ sendEvent({
4003
+ type: "state_changed",
4004
+ store: stateConfig.id,
4005
+ state
4006
+ });
3447
4007
  log(
3448
- `[serve] auto-compaction failed: ${err instanceof Error ? err.message : String(err)}`
4008
+ `[serve] sharedState ${stateConfig.id} already registered \u2014 re-emitted current state`
4009
+ );
4010
+ } catch {
4011
+ log(
4012
+ `[serve] sharedState ${stateConfig.id} already registered \u2014 skipping (could not read state)`
3449
4013
  );
4014
+ }
4015
+ return false;
4016
+ }
4017
+ const handle = await resourceManager.connect({
4018
+ id: stateConfig.id,
4019
+ driver: stateConfig.adapter,
4020
+ access: "read-write",
4021
+ options: {
4022
+ ...stateConfig.definition ?? {},
4023
+ initial: stateConfig.initialState ?? {}
4024
+ }
4025
+ });
4026
+ const entry = resourceManager.get(stateConfig.id);
4027
+ if (typeof entry.connector.onStoreChange === "function") {
4028
+ entry.connector.onStoreChange(handle, (snapshot) => {
4029
+ sendEvent({
4030
+ type: "state_changed",
4031
+ store: stateConfig.id,
4032
+ state: snapshot
4033
+ });
3450
4034
  });
3451
4035
  }
4036
+ log(`[serve] sharedState registered: ${stateConfig.id} (${stateConfig.adapter})`);
4037
+ sendEvent({
4038
+ type: "state_changed",
4039
+ store: stateConfig.id,
4040
+ state: stateConfig.initialState ?? {}
4041
+ });
4042
+ return true;
4043
+ } catch (err) {
4044
+ const errMsg = err instanceof Error ? err.message : String(err);
4045
+ log(`[serve] sharedState registration failed for ${stateConfig.id}: ${errMsg}`);
4046
+ sendEvent({
4047
+ type: "error",
4048
+ message: `sharedState ${stateConfig.id}: ${errMsg}`,
4049
+ fatal: false
4050
+ });
4051
+ return false;
3452
4052
  }
3453
- if (!resourceManager) {
3454
- sendEvent(event);
4053
+ }
4054
+ async function rebuildMcpToolsForNewStores(storesRegistered) {
4055
+ if (!(storesRegistered > 0 && !driverStarted && agentSession?.driverType === "claude-sdk")) {
3455
4056
  return;
3456
4057
  }
3457
- const mounts = resourceManager.listConnectors().filter((info) => typeof info.mountPath === "string").map((info) => ({
3458
- id: info.id,
3459
- mountPath: info.mountPath
3460
- }));
3461
- sendEvent(rewriteFileChangedPath(event, opts.projectDir, mounts));
3462
- }
3463
- let driverStarted = false;
3464
- let driver = secretsMode === "env" ? agentSession.driver : void 0;
3465
- let secretsProvisioned = secretsMode === "env";
3466
- let eventSeq = 0;
3467
- let compacting = false;
3468
- let compactionOrchestrator = null;
3469
- const compactionConfig = wsConfig.compaction;
3470
- function rebuildCompactionOrchestrator() {
3471
- if (!driver) return;
3472
- compactionOrchestrator = new CompactionOrchestrator({
3473
- driver,
3474
- config: compactionConfig,
3475
- onCompactionStart: (trigger) => {
3476
- compacting = true;
3477
- sendEvent({ type: "status", phase: "compacting" });
3478
- log(`[serve] compaction started (trigger=${trigger})`);
3479
- },
3480
- onSnapshot: (snapshot) => {
3481
- sendEvent(snapshot);
3482
- },
3483
- onCompactionAttempt: (event) => {
3484
- sendEvent(event);
3485
- },
3486
- onCompactionEnd: () => {
3487
- compacting = false;
3488
- sendEvent({ type: "status", phase: "idle" });
3489
- },
3490
- onLog: (line) => log(`[serve] ${line}`),
3491
- // Phase 3: stamp every CompactionAttemptEvent with the registry's
3492
- // current signature so wake-time tier-1 selection has the input it
3493
- // needs. The registry exists for the entire serve lifetime, so this
3494
- // closure is safe across orchestrator rebuilds.
3495
- getCurrentCapabilitySignature: () => computeCapabilitySignature(capabilityRegistry.list())
3496
- });
3497
- }
3498
- if (secretsMode === "env" && driver) {
3499
- rebuildCompactionOrchestrator();
4058
+ try {
4059
+ const server = await buildSdkConnectorTools(resourceManager, { z: z });
4060
+ if (server) {
4061
+ driver.config.mcpServers = { "skaile-connectors": server };
4062
+ log(`[serve] rebuilt MCP connector tools (${storesRegistered} new store(s))`);
4063
+ } else {
4064
+ log(`[serve] buildSdkConnectorTools returned null`);
4065
+ }
4066
+ } catch (err) {
4067
+ const errMsg = err instanceof Error ? err.message : String(err);
4068
+ log(`[serve] failed to rebuild MCP tools: ${errMsg}`);
4069
+ }
3500
4070
  }
3501
- const activeFlows = /* @__PURE__ */ new Map();
3502
- let stimulusBus = null;
3503
- const FLOW_TURN_TIMEOUT_MS = 4 * 60 * 60 * 1e3;
3504
- function getOnlyActiveFlow() {
3505
- const iter = activeFlows.values().next();
3506
- return iter.done ? null : iter.value;
4071
+ async function applyInitAiResources(aiResources) {
4072
+ const catalogActions = aiResources.filter((a) => a.action);
4073
+ if (catalogActions.length === 0) return;
4074
+ log(`[serve] aiResources catalog actions: ${catalogActions.length}`);
4075
+ try {
4076
+ const skillsDir = join(sessionConfig.projectDir ?? process.cwd(), ".claude", "skills");
4077
+ const assetsRoot = process.env.SKAILE_ASSETS_ROOT ?? findAiAssetsRoot(sessionConfig.projectDir ?? process.cwd()) ?? "/app/ai-assets";
4078
+ for (const res of catalogActions) {
4079
+ applyCatalogAction(res, { assetsRoot, skillsDir });
4080
+ }
4081
+ await restartDriverForSkillChanges(catalogActions);
4082
+ } catch (err) {
4083
+ const errMsg = err instanceof Error ? err.message : String(err);
4084
+ log(`[serve] aiResources handling failed: ${errMsg}`);
4085
+ sendEvent({
4086
+ type: "error",
4087
+ message: `aiResources: ${errMsg}`,
4088
+ fatal: false
4089
+ });
4090
+ }
3507
4091
  }
3508
- function getOrCreateStimulusBus() {
3509
- if (stimulusBus) return stimulusBus;
3510
- stimulusBus = createSessionStimulusBus({
3511
- driveTurn: async (promptFragment, metas) => {
3512
- const lastMeta = metas[metas.length - 1] ?? {};
3513
- const runId = typeof lastMeta.runId === "string" ? lastMeta.runId : null;
3514
- const entry = runId ? activeFlows.get(runId) : getOnlyActiveFlow();
3515
- if (!entry) {
3516
- serverLog.warn("driveTurn fired with no active flow", { runId });
3517
- return;
3518
- }
3519
- const lastKind = lastMeta.kind;
3520
- let stimulus;
3521
- if (lastKind === "flow_started") {
3522
- stimulus = { kind: "flow_started" };
3523
- } else if (lastKind === "user_message" && typeof lastMeta.text === "string") {
3524
- stimulus = {
3525
- kind: "user_message",
3526
- text: String(lastMeta.text),
3527
- senderId: String(lastMeta.senderId ?? "user")
3528
- };
3529
- } else {
3530
- stimulus = { kind: "state_changed" };
3531
- }
3532
- const fullPrompt = buildOrchestratorPrompt(
3533
- entry.flowDef,
3534
- entry.adapter.getExecution(entry.handle),
3535
- stimulus
4092
+ function applyCatalogAction(res, paths) {
4093
+ if (res.action === "add" && res.entry) {
4094
+ const entry = res.entry;
4095
+ const result = deployCatalogEntry(entry, {
4096
+ assetsRoot: paths.assetsRoot,
4097
+ skillsDir: paths.skillsDir,
4098
+ context: res.context
4099
+ });
4100
+ if (result.success) {
4101
+ log(`[serve] deployed skill: ${entry.name ?? res.name}`);
4102
+ } else {
4103
+ log(
4104
+ `[serve] failed to deploy skill ${entry.name ?? res.name}: ${result.error}`
3536
4105
  );
3537
- if (!driverStarted) {
3538
- await driver.start();
3539
- driverStarted = true;
3540
- driver.on("agent-event", (event) => {
3541
- const normalized = normalizer.normalize(event);
3542
- for (const e of normalized) forwardNormalizedEvent(e);
4106
+ }
4107
+ } else if (res.action === "remove") {
4108
+ const removed = undeployCatalogEntry(res.name, paths.skillsDir);
4109
+ log(`[serve] ${removed ? "removed" : "not found"} skill: ${res.name}`);
4110
+ }
4111
+ }
4112
+ async function restartDriverForSkillChanges(catalogActions) {
4113
+ if (!driverStarted) {
4114
+ log("[serve] aiResources applied before driver start \u2014 skills discovered on first prompt");
4115
+ return;
4116
+ }
4117
+ const prevSessionId = driver.runtimeSessionId;
4118
+ log(`[serve] restarting driver to pick up skill changes (resume=${prevSessionId ?? "none"})`);
4119
+ await agentSession.dispose();
4120
+ agentSession = await createAgentSession({
4121
+ ...sessionConfig,
4122
+ resumeSessionId: prevSessionId
4123
+ });
4124
+ emitSystemPromptComposed(agentSession, sendEvent);
4125
+ driver = agentSession.driver;
4126
+ normalizer.reset();
4127
+ driverStarted = false;
4128
+ rebuildCompactionOrchestrator();
4129
+ sendEvent({
4130
+ type: "skills_changed",
4131
+ skills: catalogActions.map((a) => ({
4132
+ name: a.entry?.name ?? a.name,
4133
+ action: a.action ?? "add"
4134
+ }))
4135
+ });
4136
+ }
4137
+ async function handleSessionInit(cmd) {
4138
+ if (cmd.protocolVersion.major !== PROTOCOL_VERSION.major) {
4139
+ log(
4140
+ `[serve] incompatible protocol: agent=${PROTOCOL_VERSION.major} platform=${cmd.protocolVersion.major}`
4141
+ );
4142
+ sendEvent({
4143
+ type: "incompatible_protocol",
4144
+ agentVersion: PROTOCOL_VERSION,
4145
+ platformVersion: cmd.protocolVersion
4146
+ });
4147
+ await cleanup();
4148
+ disposeLogStore();
4149
+ return;
4150
+ }
4151
+ if (runtimeIdentity && runtimeIdentity.containerId !== cmd.identity.containerId) {
4152
+ log(
4153
+ `[serve] session_init_mismatch: expected=${runtimeIdentity.containerId} received=${cmd.identity.containerId}`
4154
+ );
4155
+ sendEvent({
4156
+ type: "session_init_mismatch",
4157
+ expectedContainerId: runtimeIdentity.containerId,
4158
+ receivedContainerId: cmd.identity.containerId
4159
+ });
4160
+ await cleanup();
4161
+ disposeLogStore();
4162
+ return;
4163
+ }
4164
+ runtimeIdentity = cmd.identity;
4165
+ registerInitClientCapabilities(cmd.capabilities ?? []);
4166
+ sendEvent({ type: "protocol_info", version: PROTOCOL_VERSION });
4167
+ preMintedSecrets = new PreMintedSecretProvider(cmd.credentials);
4168
+ const initialAiCredentialExpiresAt = await provisionInitSecrets(cmd.secrets);
4169
+ if (cmd.resolvedConfig.aiProviderConfigId) {
4170
+ aiProviderConfigId = cmd.resolvedConfig.aiProviderConfigId;
4171
+ log(`[serve] stashed aiProviderConfigId=${aiProviderConfigId}`);
4172
+ }
4173
+ if (aiProviderConfigId && initialAiCredentialExpiresAt) {
4174
+ aiCredentialRefreshScheduler.schedule(initialAiCredentialExpiresAt);
4175
+ }
4176
+ sendEvent({
4177
+ type: "session_init_ack",
4178
+ capabilitySignature: computeCapabilitySignature(capabilityRegistry.list())
4179
+ });
4180
+ const buildSession = async () => {
4181
+ await buildSessionFromInit(cmd.secrets);
4182
+ const cfg = {
4183
+ aiResources: cmd.resolvedConfig.aiResources,
4184
+ subagents: cmd.resolvedConfig.subagents,
4185
+ sharedState: cmd.sharedState,
4186
+ resumeSessionId: cmd.resume?.sessionId,
4187
+ expectedCapabilitySignature: cmd.resume?.expectedCapabilitySignature,
4188
+ aiProviderConfigId: cmd.resolvedConfig.aiProviderConfigId
4189
+ };
4190
+ log(
4191
+ `[serve] configure cfg keys=${Object.keys(cfg).join(",")} sharedState=${cfg.sharedState ? cfg.sharedState.length : "undefined"} resourceManager=${resourceManager ? "set" : "null"}`
4192
+ );
4193
+ let pendingResumeSessionId;
4194
+ const cfgResumeSessionId = cfg.resumeSessionId;
4195
+ const cfgExpectedSig = cfg.expectedCapabilitySignature;
4196
+ if (cfgResumeSessionId) {
4197
+ const currentSig = computeCapabilitySignature(capabilityRegistry.list());
4198
+ if (cfgExpectedSig && cfgExpectedSig !== currentSig) {
4199
+ log(
4200
+ `[serve] tier-1 resume rejected: signature mismatch (expected=${cfgExpectedSig.slice(0, 12)} current=${currentSig.slice(0, 12)})`
4201
+ );
4202
+ sendEvent({
4203
+ type: "resume_failed",
4204
+ resumeSessionId: cfgResumeSessionId,
4205
+ reason: "signature_mismatch"
3543
4206
  });
3544
- driver.on("error", (err) => {
3545
- log(`[serve] driver error: ${err.message}`);
3546
- sendEvent({ type: "error", message: err.message, fatal: true });
4207
+ } else {
4208
+ log(`[serve] tier-1 resume attempted: resumeSessionId=${cfgResumeSessionId}`);
4209
+ sendEvent({
4210
+ type: "resume_attempted",
4211
+ resumeSessionId: cfgResumeSessionId,
4212
+ capabilitySignature: currentSig
3547
4213
  });
4214
+ pendingResumeSessionId = cfgResumeSessionId;
3548
4215
  }
3549
- await awaitFlowAgentEnd(fullPrompt, FLOW_TURN_TIMEOUT_MS);
3550
- },
3551
- log: serverLog
4216
+ }
4217
+ if (pendingResumeSessionId || cfg.subagents) {
4218
+ await recreateSessionForInit(pendingResumeSessionId);
4219
+ }
4220
+ if (cfg.sharedState) {
4221
+ const handled = await applyInitSharedState(cfg.sharedState);
4222
+ if (!handled) throw new Error("session_init: shared-state setup failed");
4223
+ }
4224
+ if (cfg.aiResources) {
4225
+ await applyInitAiResources(cfg.aiResources);
4226
+ }
4227
+ resolveSessionReady();
4228
+ };
4229
+ void buildSession().catch((err) => {
4230
+ const errMsg = err instanceof Error ? err.message : String(err);
4231
+ log(`[serve] session_init failed to build session: ${errMsg}`);
4232
+ sendEvent({ type: "error", message: errMsg, fatal: true });
4233
+ rejectSessionReady(err instanceof Error ? err : new Error(errMsg));
3552
4234
  });
3553
- return stimulusBus;
3554
4235
  }
3555
- function awaitFlowAgentEnd(prompt, timeoutMs) {
3556
- return new Promise((resolve3, reject) => {
3557
- let settled = false;
3558
- const onEvent = (event) => {
3559
- if (event.type === "agent_end") done();
3560
- else if (event.type === "error") {
3561
- const msg = event.error ?? event.message ?? "Agent error";
3562
- done(new Error(msg));
4236
+ async function handlePrompt(cmd) {
4237
+ if (!await awaitSessionReadyOrFail()) return;
4238
+ if (!secretsProvisioned) {
4239
+ log("[serve] prompt received before secrets provisioned \u2014 rejecting");
4240
+ sendEvent({
4241
+ type: "error",
4242
+ message: "Secrets not yet provisioned \u2014 send provision_secrets first",
4243
+ fatal: false
4244
+ });
4245
+ return;
4246
+ }
4247
+ log(`[serve] prompt: ${cmd.prompt.slice(0, 80)}...`);
4248
+ const activeFlow = getOnlyActiveFlow();
4249
+ if (activeFlow) {
4250
+ sendEvent({ type: "status", phase: "thinking" });
4251
+ try {
4252
+ const bus = getOrCreateStimulusBus();
4253
+ const userStimulus = {
4254
+ kind: "user_message",
4255
+ text: cmd.prompt,
4256
+ senderId: "user"
4257
+ };
4258
+ await bus.signal(activeFlow.connectorId, {
4259
+ promptFragment: renderStimulusPrompt(userStimulus),
4260
+ meta: {
4261
+ kind: "user_message",
4262
+ runId: activeFlow.runId,
4263
+ text: cmd.prompt,
4264
+ senderId: "user"
4265
+ }
4266
+ });
4267
+ } catch (err) {
4268
+ const errMsg = err instanceof Error ? err.message : String(err);
4269
+ log(`[serve] flow user-message turn failed: ${errMsg}`);
4270
+ sendEvent({ type: "error", message: `Flow turn failed: ${errMsg}`, fatal: false });
4271
+ }
4272
+ return;
4273
+ }
4274
+ if (!driverStarted) {
4275
+ try {
4276
+ await driver.start();
4277
+ driverStarted = true;
4278
+ } catch (err) {
4279
+ const errMsg = err instanceof Error ? err.message : String(err);
4280
+ log(`[serve] driver start failed: ${errMsg}`);
4281
+ sendEvent({ type: "error", message: `Driver start failed: ${errMsg}`, fatal: true });
4282
+ return;
4283
+ }
4284
+ driver.on("agent-event", (event) => {
4285
+ const normalized = normalizer.normalize(event);
4286
+ for (const e of normalized) {
4287
+ forwardNormalizedEvent(e);
3563
4288
  }
3564
- };
3565
- const done = (err) => {
3566
- if (settled) return;
3567
- settled = true;
3568
- clearTimeout(timer);
3569
- driver.off?.(
3570
- "agent-event",
3571
- onEvent
3572
- ) ?? driver.removeListener("agent-event", onEvent);
3573
- if (err) reject(err);
3574
- else resolve3();
3575
- };
3576
- const timer = setTimeout(
3577
- () => done(new Error(`Flow turn timed out after ${timeoutMs / 1e3}s`)),
3578
- timeoutMs
3579
- );
3580
- driver.on("agent-event", onEvent);
3581
- driver.prompt(prompt).catch(done);
3582
- });
4289
+ });
4290
+ driver.on("error", (err) => {
4291
+ log(`[serve] driver error: ${err.message}`);
4292
+ sendEvent({ type: "error", message: err.message, fatal: true });
4293
+ });
4294
+ }
4295
+ sendEvent({ type: "status", phase: "thinking" });
4296
+ try {
4297
+ await driver.prompt(cmd.prompt);
4298
+ } catch (err) {
4299
+ const errMsg = err instanceof Error ? err.message : String(err);
4300
+ log(`[serve] prompt failed: ${errMsg}`);
4301
+ sendEvent({ type: "error", message: `Prompt failed: ${errMsg}`, fatal: true });
4302
+ }
3583
4303
  }
3584
- async function createActiveFlow(flowDef, seedOrExecution) {
4304
+ async function handleReply(cmd) {
4305
+ if (!await awaitSessionReadyOrFail()) return;
4306
+ log(`[serve] reply: ${cmd.answer.slice(0, 80)}...`);
4307
+ try {
4308
+ await driver.prompt(cmd.answer);
4309
+ } catch (err) {
4310
+ sendEvent({
4311
+ type: "error",
4312
+ message: `Reply failed: ${err instanceof Error ? err.message : String(err)}`,
4313
+ fatal: false
4314
+ });
4315
+ }
4316
+ }
4317
+ async function handleCancel() {
4318
+ if (!await awaitSessionReadyOrFail()) return;
4319
+ log("[serve] cancel");
4320
+ try {
4321
+ await driver.abort();
4322
+ } catch (err) {
4323
+ log(`[serve] cancel failed: ${err}`);
4324
+ }
4325
+ }
4326
+ async function handleResourceRequestCommand(cmd) {
4327
+ if (!await awaitSessionReadyOrFail()) return;
4328
+ if (resourceManager?.has(cmd.connectorId)) {
4329
+ handleMountResourceRequest(cmd, resourceManager, sendEvent);
4330
+ return;
4331
+ }
3585
4332
  if (!resourceManager) {
3586
- registerBuiltinConnectors();
3587
- resourceManager = new ConnectorManager(opts.projectDir, {
3588
- catalogEntries: getSessionCatalogEntries(agentSession)
4333
+ sendEvent({
4334
+ type: "resource_response",
4335
+ requestId: cmd.requestId,
4336
+ resourceId: cmd.connectorId,
4337
+ operation: cmd.operation,
4338
+ error: "No resources available \u2014 no mounts or connectors configured"
3589
4339
  });
3590
- log("[serve] created ConnectorManager for flow execution (no other connectors declared)");
4340
+ return;
3591
4341
  }
3592
- const bus = getOrCreateStimulusBus();
3593
- const baseOptions = {
3594
- stimulusBus: bus,
3595
- useBusForStimulus: true
3596
- };
3597
- const options = "execution" in seedOrExecution ? { flow: flowDef, execution: seedOrExecution.execution, ...baseOptions } : { flow: flowDef, seed: seedOrExecution.seed, ...baseOptions };
3598
- const seedRunId = "execution" in seedOrExecution ? seedOrExecution.execution.runId : seedOrExecution.seed.runId;
3599
- const connectorId = `flow:${seedRunId}`;
3600
- const handle = await resourceManager.connect({
3601
- id: connectorId,
3602
- driver: "flow",
3603
- access: "read-write",
3604
- options
3605
- });
3606
- const adapter = resourceManager.get(connectorId).connector;
3607
- const runId = adapter.getExecution(handle).runId;
3608
- adapter.onStateChange(handle, (state) => {
4342
+ void handleResourceRequest(cmd, resourceManager, sendEvent);
4343
+ }
4344
+ async function handleConnectorMutate(cmd) {
4345
+ try {
4346
+ await sessionReady;
4347
+ } catch (err) {
3609
4348
  sendEvent({
3610
- type: "state_changed",
3611
- store: `flow:${state.runId}`,
3612
- state
4349
+ type: "error",
4350
+ message: `Session not available: ${err.message}`,
4351
+ fatal: true
3613
4352
  });
3614
- });
3615
- if (agentSession?.driverType === "claude-sdk") {
3616
- try {
3617
- const server = await buildSdkConnectorTools(resourceManager, { z: z });
3618
- if (server) {
3619
- if (driverStarted) {
3620
- const prevSessionId = driver.runtimeSessionId;
3621
- log(
3622
- `[serve] restarting driver to register flow connector tools (resume=${prevSessionId ?? "none"})`
3623
- );
3624
- await agentSession.dispose();
3625
- agentSession = await createAgentSession({
3626
- ...sessionConfig,
3627
- resumeSessionId: prevSessionId
4353
+ return;
4354
+ }
4355
+ try {
4356
+ if (!resourceManager) throw new Error("connector_mutate before resources ready");
4357
+ const targetEntry = resourceManager.has?.(cmd.id) ? resourceManager.get(cmd.id) : null;
4358
+ const isExistingFlow = targetEntry?.connector?.name === "flow";
4359
+ const isBareFlowId = cmd.id === "flow";
4360
+ if (cmd.op === "start") {
4361
+ if (isExistingFlow || isBareFlowId) {
4362
+ const payload = cmd.payload ?? {};
4363
+ const flowDef = payload.flow;
4364
+ const seed = payload.seed;
4365
+ if (flowDef?.id && seed?.runId && seed?.startedBy) {
4366
+ if (activeFlows.size > 0) {
4367
+ serverLog.warn("connector_mutate flow.start: another flow already active", {
4368
+ activeRunIds: Array.from(activeFlows.keys())
4369
+ });
4370
+ return;
4371
+ }
4372
+ const entry = await createActiveFlow(flowDef, { seed });
4373
+ serverLog.info("connector_mutate flow.start", {
4374
+ runId: entry.runId,
4375
+ flowId: flowDef.id
3628
4376
  });
3629
- emitSystemPromptComposed(agentSession, sendEvent);
3630
- driver = agentSession.driver;
3631
- normalizer.reset();
3632
- driverStarted = false;
3633
- rebuildCompactionOrchestrator();
4377
+ return;
3634
4378
  }
3635
- driver.config.mcpServers = { "skaile-connectors": server };
3636
- log(`[serve] rebuilt MCP connector tools for flow ${connectorId}`);
3637
- } else {
3638
- log(`[serve] buildSdkConnectorTools returned null for flow ${connectorId}`);
3639
4379
  }
3640
- } catch (err) {
3641
- const errMsg = err instanceof Error ? err.message : String(err);
3642
- log(`[serve] failed to rebuild MCP tools for flow: ${errMsg}`);
3643
4380
  }
4381
+ let dispatchId = cmd.id;
4382
+ if (!targetEntry && isBareFlowId) {
4383
+ const onlyActive = getOnlyActiveFlow();
4384
+ if (onlyActive) {
4385
+ dispatchId = onlyActive.connectorId;
4386
+ }
4387
+ }
4388
+ await resourceManager.executeOp(dispatchId, cmd.op, cmd.payload ?? {});
4389
+ } catch (err) {
4390
+ serverLog.warn("connector_mutate failed", {
4391
+ id: cmd.id,
4392
+ op: cmd.op,
4393
+ error: err instanceof Error ? err.message : String(err)
4394
+ });
3644
4395
  }
3645
- const entry = { runId, flowDef, adapter, handle, connectorId };
3646
- activeFlows.set(runId, entry);
3647
- return entry;
3648
4396
  }
3649
- const refreshFlagPath = join(opts.projectDir, ".skaile", "refresh-request");
3650
- try {
3651
- mkdirSync(join(opts.projectDir, ".skaile"), { recursive: true });
3652
- if (!existsSync(refreshFlagPath)) {
3653
- writeFileSync(refreshFlagPath, "", { mode: 432, flag: "a" });
4397
+ async function handleConnectorQuery(cmd) {
4398
+ try {
4399
+ await sessionReady;
4400
+ } catch (err) {
4401
+ sendEvent({
4402
+ type: "error",
4403
+ message: `Session not available: ${err.message}`,
4404
+ fatal: true
4405
+ });
4406
+ return;
3654
4407
  }
3655
- } catch (err) {
3656
- serverLog.warn("failed to pre-create refresh-flag \u2014 wake-mid-401 disabled", {
3657
- error: err instanceof Error ? err.message : String(err)
4408
+ let ok = true;
4409
+ let result;
4410
+ let error;
4411
+ try {
4412
+ if (!resourceManager) throw new Error("connector_query before resources ready");
4413
+ let dispatchId = cmd.id;
4414
+ if (!resourceManager.has?.(cmd.id) && cmd.id === "flow") {
4415
+ const onlyActive = getOnlyActiveFlow();
4416
+ if (onlyActive) {
4417
+ dispatchId = onlyActive.connectorId;
4418
+ }
4419
+ }
4420
+ result = await resourceManager.executeOp(dispatchId, cmd.op, cmd.payload ?? {});
4421
+ } catch (err) {
4422
+ ok = false;
4423
+ error = err instanceof Error ? err.message : String(err);
4424
+ }
4425
+ sendEvent({
4426
+ type: "connector_query_response",
4427
+ requestId: cmd.requestId,
4428
+ ok,
4429
+ ...result !== void 0 ? { result } : {},
4430
+ ...error !== void 0 ? { error } : {}
3658
4431
  });
3659
4432
  }
3660
- let lastRefreshAt = 0;
3661
- const REFRESH_DEBOUNCE_MS = 2e3;
3662
- let refreshFlagWatcher = null;
3663
- try {
3664
- refreshFlagWatcher = watch(refreshFlagPath, (event) => {
3665
- if (event !== "change") return;
3666
- const now = Date.now();
3667
- if (now - lastRefreshAt < REFRESH_DEBOUNCE_MS) return;
3668
- lastRefreshAt = now;
3669
- void dispatchRefreshToExposedGitMounts(resourceManager, serverLog);
3670
- });
3671
- } catch (err) {
3672
- serverLog.warn("fs.watch on refresh-flag failed \u2014 wake-mid-401 disabled", {
3673
- error: err instanceof Error ? err.message : String(err)
3674
- });
4433
+ async function computeDebugResult(cmd) {
4434
+ switch (cmd.query) {
4435
+ case "tools": {
4436
+ const mcpServers = Object.keys(driver?.config?.mcpServers ?? {});
4437
+ const allowedTools = driver?.config?.allowedTools ?? null;
4438
+ return { mcpServers, allowedTools, driverStarted };
4439
+ }
4440
+ case "connectors": {
4441
+ return resourceManager?.listConnectors?.() ?? [];
4442
+ }
4443
+ case "mcp": {
4444
+ const servers = Object.entries(driver?.config?.mcpServers ?? {}).map(([name]) => ({
4445
+ name
4446
+ }));
4447
+ return { servers };
4448
+ }
4449
+ case "config": {
4450
+ const liveModel = driver?.getModel() ?? agentSession?.model;
4451
+ return {
4452
+ driver: agentSession?.driverType,
4453
+ model: liveModel,
4454
+ provider: agentSession?.provider,
4455
+ maxTurns,
4456
+ secretsMode,
4457
+ driverStarted,
4458
+ projectDir: opts.projectDir,
4459
+ versions: getFrameworkVersions()
4460
+ };
4461
+ }
4462
+ case "state": {
4463
+ const storeId = cmd.args?.store;
4464
+ if (storeId) {
4465
+ try {
4466
+ const snapshot = await resourceManager?.executeOp(storeId, "get", {});
4467
+ return snapshot ? JSON.parse(snapshot) : null;
4468
+ } catch (err) {
4469
+ return { error: err instanceof Error ? err.message : String(err) };
4470
+ }
4471
+ }
4472
+ const all = resourceManager?.listConnectors?.() ?? [];
4473
+ return { stores: all.map((c) => c.id) };
4474
+ }
4475
+ case "commands": {
4476
+ const commands = driver?.getSlashCommands?.() ?? [];
4477
+ return { commands, driverType: agentSession?.driverType };
4478
+ }
4479
+ default:
4480
+ return { error: `Unknown debug query: ${cmd.query}` };
4481
+ }
3675
4482
  }
3676
- async function cleanup() {
4483
+ async function handleDebug(cmd) {
4484
+ if (!await awaitSessionReadyOrFail()) return;
4485
+ log(`[serve] debug query=${cmd.query}`);
4486
+ let result;
3677
4487
  try {
3678
- await Promise.race([
3679
- sessionReady,
3680
- new Promise((resolve3) => {
3681
- const t = setTimeout(resolve3, 2e3);
3682
- t.unref?.();
3683
- })
3684
- ]);
3685
- } catch {
4488
+ result = await computeDebugResult(cmd);
4489
+ } catch (err) {
4490
+ result = { error: err instanceof Error ? err.message : String(err) };
3686
4491
  }
3687
- try {
3688
- driver?.kill();
3689
- } catch {
4492
+ sendEvent({ type: "debug_result", query: cmd.query, data: result });
4493
+ }
4494
+ async function handleShutdown() {
4495
+ log("[serve] shutdown requested");
4496
+ await cleanup();
4497
+ if (transport instanceof WebSocketServerTransport) {
4498
+ await transport.close();
4499
+ }
4500
+ disposeLogStore();
4501
+ process.exit(0);
4502
+ }
4503
+ function handleCapabilityResult(cmd) {
4504
+ const matched = resolveCapabilityResult(pendingCapabilityCalls, cmd);
4505
+ if (!matched) {
4506
+ log(
4507
+ `[serve] capability_result for unknown callId=${cmd.callId} (timed out or already resolved); dropping`
4508
+ );
3690
4509
  }
3691
- aiCredentialRefreshScheduler.cancel();
3692
- for (const [callId, pending] of pendingCapabilityCalls) {
3693
- clearTimeout(pending.timer);
3694
- pending.reject(new Error(`Session terminated before capability_result (callId=${callId})`));
4510
+ }
4511
+ function handleCapabilityRegister(cmd) {
4512
+ for (const raw of cmd.capabilities) {
4513
+ const wire = raw;
4514
+ capabilityRegistry.register(
4515
+ {
4516
+ ...wire,
4517
+ inputZod: { parse: (v) => v },
4518
+ outputZod: void 0,
4519
+ handler: buildClientCapabilityHandler2(wire)
4520
+ },
4521
+ "client"
4522
+ );
3695
4523
  }
3696
- pendingCapabilityCalls.clear();
3697
- try {
3698
- refreshFlagWatcher?.close();
3699
- } catch {
4524
+ }
4525
+ function handleCapabilityDeregister(cmd) {
4526
+ for (const name of cmd.names) {
4527
+ capabilityRegistry.deregister(name);
3700
4528
  }
3701
- refreshFlagWatcher = null;
3702
- if (agentSession) {
3703
- try {
3704
- await agentSession.dispose();
3705
- } catch {
4529
+ }
4530
+ function handleCapabilityApprove(cmd) {
4531
+ if (cmd.decision === "rejected") {
4532
+ const matched = rejectCapabilityOnApprovalDeny(
4533
+ pendingCapabilityCalls,
4534
+ cmd.callId,
4535
+ cmd.feedback
4536
+ );
4537
+ if (!matched) {
4538
+ log(
4539
+ `[serve] capability_approve(rejected) for unknown callId=${cmd.callId} (already resolved or timed out); dropping`
4540
+ );
4541
+ } else {
4542
+ log(
4543
+ `[serve] capability_approve(rejected) callId=${cmd.callId} decidedBy=${cmd.decidedBy}${cmd.feedback ? ` feedback=${JSON.stringify(cmd.feedback)}` : ""}`
4544
+ );
3706
4545
  }
4546
+ } else {
4547
+ log(
4548
+ `[serve] capability_approve(approved) callId=${cmd.callId} decidedBy=${cmd.decidedBy} (awaiting capability_result)`
4549
+ );
3707
4550
  }
3708
4551
  }
3709
4552
  async function handleCommand(cmd) {
@@ -3719,492 +4562,23 @@ async function startAgentServer(opts) {
3719
4562
  return;
3720
4563
  }
3721
4564
  switch (cmd.type) {
3722
- case "session_init": {
3723
- if (cmd.protocolVersion.major !== PROTOCOL_VERSION.major) {
3724
- log(
3725
- `[serve] incompatible protocol: agent=${PROTOCOL_VERSION.major} platform=${cmd.protocolVersion.major}`
3726
- );
3727
- sendEvent({
3728
- type: "incompatible_protocol",
3729
- agentVersion: PROTOCOL_VERSION,
3730
- platformVersion: cmd.protocolVersion
3731
- });
3732
- await cleanup();
3733
- disposeLogStore();
3734
- return;
3735
- }
3736
- if (runtimeIdentity && runtimeIdentity.containerId !== cmd.identity.containerId) {
3737
- log(
3738
- `[serve] session_init_mismatch: expected=${runtimeIdentity.containerId} received=${cmd.identity.containerId}`
3739
- );
3740
- sendEvent({
3741
- type: "session_init_mismatch",
3742
- expectedContainerId: runtimeIdentity.containerId,
3743
- receivedContainerId: cmd.identity.containerId
3744
- });
3745
- await cleanup();
3746
- disposeLogStore();
3747
- return;
3748
- }
3749
- runtimeIdentity = cmd.identity;
3750
- const incoming = cmd.capabilities ?? [];
3751
- for (const wire of incoming) {
3752
- if (wire.audience && !wire.audience.includes("llm") && !wire.audience.includes("user")) {
3753
- continue;
3754
- }
3755
- const stub = {
3756
- ...wire,
3757
- inputZod: { parse: (v) => v },
3758
- outputZod: void 0,
3759
- handler: buildClientCapabilityHandler2(wire)
3760
- };
3761
- capabilityRegistry.register(stub, "client");
3762
- }
3763
- sendEvent({ type: "protocol_info", version: PROTOCOL_VERSION });
3764
- preMintedSecrets = new PreMintedSecretProvider(cmd.credentials);
3765
- let initialAiCredentialExpiresAt = null;
3766
- if (cmd.secrets) {
3767
- const ccdValue = cmd.secrets[CLAUDE_CODE_CREDENTIALS_KEY];
3768
- if (ccdValue) {
3769
- const home = process.env.HOME ?? homedir();
3770
- if (home) {
3771
- try {
3772
- await writeClaudeCodeCredentialsFile(home, ccdValue);
3773
- log(
3774
- `[serve] wrote Claude Code credentials file to ${home}/.claude/.credentials.json (mode 0600)`
3775
- );
3776
- } catch (err) {
3777
- const errMsg = err instanceof Error ? err.message : String(err);
3778
- log(`[serve] failed to write Claude Code credentials file: ${errMsg}`);
3779
- }
3780
- }
3781
- initialAiCredentialExpiresAt = extractClaudeAiOauthExpiresAt(ccdValue);
3782
- }
3783
- const otherSecrets = Object.fromEntries(
3784
- Object.entries(cmd.secrets).filter(([k]) => k !== CLAUDE_CODE_CREDENTIALS_KEY)
3785
- );
3786
- const provider = new InMemorySecretProvider();
3787
- provider.provision(otherSecrets);
3788
- secretProvider = provider;
3789
- secretsProvisioned = true;
3790
- }
3791
- try {
3792
- const effectiveProvider = opts.provider ?? "anthropic";
3793
- const envKey = providerEnvKey(effectiveProvider);
3794
- const apiKeyFromSecrets = cmd.secrets?.[envKey];
3795
- sessionConfig = buildSessionConfig(secretProvider, apiKeyFromSecrets);
3796
- if (!agentSession) {
3797
- agentSession = await createAgentSession(sessionConfig);
3798
- emitSystemPromptComposed(agentSession, sendEvent);
3799
- resourceManager = agentSession.resourceManager;
3800
- driver = agentSession.driver;
3801
- rebuildCompactionOrchestrator();
3802
- registerSessionComposition(agentSession);
3803
- sendEvent({
3804
- type: "resources_available",
3805
- ...splitConnectorsForWire(resourceManager)
3806
- });
3807
- agentSession.startWatching();
3808
- capabilityRegistry.setRemoteInvoker(remoteCapabilityInvoker);
3809
- const runnerCaps = buildRunnerCapabilities({
3810
- // Resource hot-plug handlers, wired to the live session's
3811
- // ConnectorManager + PreMintedSecretProvider via getters because
3812
- // both are lazily (re)assigned in this scope.
3813
- ...buildSessionResourceHandlers({
3814
- getResourceManager: () => resourceManager,
3815
- getPreMintedSecrets: () => preMintedSecrets,
3816
- emitResourcesAvailable: () => {
3817
- sendEvent({
3818
- type: "resources_available",
3819
- ...splitConnectorsForWire(resourceManager)
3820
- });
3821
- },
3822
- log
3823
- })
3824
- // setLogLevel left undefined — the cap's built-in fallback calls
3825
- // getLogStore()?.setLevel(...) directly. setState / lifecycle
3826
- // remain stubs (not resource hot-plug; out of Sprint 2 scope).
3827
- });
3828
- for (const cap of runnerCaps) {
3829
- capabilityRegistry.register(cap, "agent");
3830
- }
3831
- resolveSessionReady();
3832
- log("[serve] session built from session_init envelope");
3833
- }
3834
- } catch (err) {
3835
- const errMsg = err instanceof Error ? err.message : String(err);
3836
- log(`[serve] session_init failed to build session: ${errMsg}`);
3837
- sendEvent({ type: "error", message: errMsg, fatal: true });
3838
- rejectSessionReady(err instanceof Error ? err : new Error(errMsg));
3839
- }
3840
- if (cmd.resolvedConfig.aiProviderConfigId) {
3841
- aiProviderConfigId = cmd.resolvedConfig.aiProviderConfigId;
3842
- log(`[serve] stashed aiProviderConfigId=${aiProviderConfigId}`);
3843
- }
3844
- if (aiProviderConfigId && initialAiCredentialExpiresAt) {
3845
- aiCredentialRefreshScheduler.schedule(initialAiCredentialExpiresAt);
3846
- }
3847
- const cfg = {
3848
- aiResources: cmd.resolvedConfig.aiResources,
3849
- subagents: cmd.resolvedConfig.subagents,
3850
- sharedState: cmd.sharedState,
3851
- resumeSessionId: cmd.resume?.sessionId,
3852
- expectedCapabilitySignature: cmd.resume?.expectedCapabilitySignature,
3853
- aiProviderConfigId: cmd.resolvedConfig.aiProviderConfigId
3854
- };
3855
- log(
3856
- `[serve] configure cfg keys=${Object.keys(cfg).join(",")} sharedState=${cfg.sharedState ? cfg.sharedState.length : "undefined"} resourceManager=${resourceManager ? "set" : "null"}`
3857
- );
3858
- let pendingResumeSessionId;
3859
- const cfgResumeSessionId = cfg.resumeSessionId;
3860
- const cfgExpectedSig = cfg.expectedCapabilitySignature;
3861
- if (cfgResumeSessionId) {
3862
- const currentSig = computeCapabilitySignature(capabilityRegistry.list());
3863
- if (cfgExpectedSig && cfgExpectedSig !== currentSig) {
3864
- log(
3865
- `[serve] tier-1 resume rejected: signature mismatch (expected=${cfgExpectedSig.slice(0, 12)} current=${currentSig.slice(0, 12)})`
3866
- );
3867
- sendEvent({
3868
- type: "resume_failed",
3869
- resumeSessionId: cfgResumeSessionId,
3870
- reason: "signature_mismatch"
3871
- });
3872
- } else {
3873
- log(`[serve] tier-1 resume attempted: resumeSessionId=${cfgResumeSessionId}`);
3874
- sendEvent({
3875
- type: "resume_attempted",
3876
- resumeSessionId: cfgResumeSessionId,
3877
- capabilitySignature: currentSig
3878
- });
3879
- pendingResumeSessionId = cfgResumeSessionId;
3880
- }
3881
- }
3882
- if (pendingResumeSessionId || cfg.subagents) {
3883
- await agentSession.dispose();
3884
- agentSession = await createAgentSession({
3885
- ...sessionConfig,
3886
- ...aiProviderConfigId ? { aiProviderConfigId } : {},
3887
- ...pendingResumeSessionId ? { resumeSessionId: pendingResumeSessionId } : {}
3888
- });
3889
- emitSystemPromptComposed(agentSession, sendEvent);
3890
- driver = agentSession.driver;
3891
- normalizer.reset();
3892
- driverStarted = false;
3893
- rebuildCompactionOrchestrator();
3894
- }
3895
- if (cfg.sharedState) {
3896
- if (!resourceManager) {
3897
- try {
3898
- registerBuiltinConnectors();
3899
- resourceManager = new ConnectorManager(opts.projectDir, {
3900
- catalogEntries: getSessionCatalogEntries(agentSession)
3901
- });
3902
- log("[serve] created ConnectorManager for sharedState (no connectors were declared)");
3903
- } catch (err) {
3904
- const errMsg = err instanceof Error ? err.message : String(err);
3905
- log(`[serve] failed to create ConnectorManager: ${errMsg}`);
3906
- sendEvent({ type: "configured" });
3907
- break;
3908
- }
3909
- }
3910
- let storesRegistered = 0;
3911
- for (const stateConfig of cfg.sharedState) {
3912
- try {
3913
- if (resourceManager.has(stateConfig.id)) {
3914
- try {
3915
- const snapshot = await resourceManager.executeOp(stateConfig.id, "get", {});
3916
- const state = snapshot ? JSON.parse(snapshot) : {};
3917
- sendEvent({
3918
- type: "state_changed",
3919
- store: stateConfig.id,
3920
- state
3921
- });
3922
- log(
3923
- `[serve] sharedState ${stateConfig.id} already registered \u2014 re-emitted current state`
3924
- );
3925
- } catch {
3926
- log(
3927
- `[serve] sharedState ${stateConfig.id} already registered \u2014 skipping (could not read state)`
3928
- );
3929
- }
3930
- continue;
3931
- }
3932
- const handle = await resourceManager.connect({
3933
- id: stateConfig.id,
3934
- driver: stateConfig.adapter,
3935
- access: "read-write",
3936
- options: {
3937
- ...stateConfig.definition ?? {},
3938
- initial: stateConfig.initialState ?? {}
3939
- }
3940
- });
3941
- const entry = resourceManager.get(stateConfig.id);
3942
- if (typeof entry.connector.onStoreChange === "function") {
3943
- entry.connector.onStoreChange(handle, (snapshot) => {
3944
- sendEvent({
3945
- type: "state_changed",
3946
- store: stateConfig.id,
3947
- state: snapshot
3948
- });
3949
- });
3950
- }
3951
- log(`[serve] sharedState registered: ${stateConfig.id} (${stateConfig.adapter})`);
3952
- storesRegistered++;
3953
- sendEvent({
3954
- type: "state_changed",
3955
- store: stateConfig.id,
3956
- state: stateConfig.initialState ?? {}
3957
- });
3958
- } catch (err) {
3959
- const errMsg = err instanceof Error ? err.message : String(err);
3960
- log(`[serve] sharedState registration failed for ${stateConfig.id}: ${errMsg}`);
3961
- sendEvent({
3962
- type: "error",
3963
- message: `sharedState ${stateConfig.id}: ${errMsg}`,
3964
- fatal: false
3965
- });
3966
- }
3967
- }
3968
- if (storesRegistered > 0 && !driverStarted && agentSession?.driverType === "claude-sdk") {
3969
- try {
3970
- const server = await buildSdkConnectorTools(resourceManager, { z: z });
3971
- if (server) {
3972
- driver.config.mcpServers = { "skaile-connectors": server };
3973
- log(`[serve] rebuilt MCP connector tools (${storesRegistered} new store(s))`);
3974
- } else {
3975
- log(`[serve] buildSdkConnectorTools returned null`);
3976
- }
3977
- } catch (err) {
3978
- const errMsg = err instanceof Error ? err.message : String(err);
3979
- log(`[serve] failed to rebuild MCP tools: ${errMsg}`);
3980
- }
3981
- }
3982
- }
3983
- if (cfg.aiResources) {
3984
- const catalogActions = cfg.aiResources.filter((a) => a.action);
3985
- if (catalogActions.length > 0) {
3986
- log(`[serve] aiResources catalog actions: ${catalogActions.length}`);
3987
- try {
3988
- const skillsDir = join(
3989
- sessionConfig.projectDir ?? process.cwd(),
3990
- ".claude",
3991
- "skills"
3992
- );
3993
- const assetsRoot = process.env.SKAILE_ASSETS_ROOT ?? findAiAssetsRoot(sessionConfig.projectDir ?? process.cwd()) ?? "/app/ai-assets";
3994
- for (const res of catalogActions) {
3995
- if (res.action === "add" && res.entry) {
3996
- const entry = res.entry;
3997
- const result = deployCatalogEntry(entry, {
3998
- assetsRoot,
3999
- skillsDir,
4000
- context: res.context
4001
- });
4002
- if (result.success) {
4003
- log(`[serve] deployed skill: ${entry.name ?? res.name}`);
4004
- } else {
4005
- log(
4006
- `[serve] failed to deploy skill ${entry.name ?? res.name}: ${result.error}`
4007
- );
4008
- }
4009
- } else if (res.action === "remove") {
4010
- const removed = undeployCatalogEntry(res.name, skillsDir);
4011
- log(`[serve] ${removed ? "removed" : "not found"} skill: ${res.name}`);
4012
- }
4013
- }
4014
- if (driverStarted) {
4015
- const prevSessionId = driver.runtimeSessionId;
4016
- log(
4017
- `[serve] restarting driver to pick up skill changes (resume=${prevSessionId ?? "none"})`
4018
- );
4019
- await agentSession.dispose();
4020
- agentSession = await createAgentSession({
4021
- ...sessionConfig,
4022
- resumeSessionId: prevSessionId
4023
- });
4024
- emitSystemPromptComposed(agentSession, sendEvent);
4025
- driver = agentSession.driver;
4026
- normalizer.reset();
4027
- driverStarted = false;
4028
- rebuildCompactionOrchestrator();
4029
- sendEvent({
4030
- type: "skills_changed",
4031
- skills: catalogActions.map((a) => ({
4032
- name: a.entry?.name ?? a.name,
4033
- action: a.action ?? "add"
4034
- }))
4035
- });
4036
- } else {
4037
- log(
4038
- "[serve] aiResources applied before driver start \u2014 skills discovered on first prompt"
4039
- );
4040
- }
4041
- } catch (err) {
4042
- const errMsg = err instanceof Error ? err.message : String(err);
4043
- log(`[serve] aiResources handling failed: ${errMsg}`);
4044
- sendEvent({
4045
- type: "error",
4046
- message: `aiResources: ${errMsg}`,
4047
- fatal: false
4048
- });
4049
- }
4050
- }
4051
- }
4052
- sendEvent({
4053
- type: "session_init_ack",
4054
- capabilitySignature: computeCapabilitySignature(capabilityRegistry.list())
4055
- });
4565
+ case "session_init":
4566
+ await handleSessionInit(cmd);
4056
4567
  break;
4057
- }
4058
- case "prompt": {
4059
- try {
4060
- await sessionReady;
4061
- } catch (err) {
4062
- sendEvent({
4063
- type: "error",
4064
- message: `Session not available: ${err.message}`,
4065
- fatal: true
4066
- });
4067
- break;
4068
- }
4069
- if (!secretsProvisioned) {
4070
- log("[serve] prompt received before secrets provisioned \u2014 rejecting");
4071
- sendEvent({
4072
- type: "error",
4073
- message: "Secrets not yet provisioned \u2014 send provision_secrets first",
4074
- fatal: false
4075
- });
4076
- break;
4077
- }
4078
- log(`[serve] prompt: ${cmd.prompt.slice(0, 80)}...`);
4079
- const activeFlow = getOnlyActiveFlow();
4080
- if (activeFlow) {
4081
- sendEvent({ type: "status", phase: "thinking" });
4082
- try {
4083
- const bus = getOrCreateStimulusBus();
4084
- const userStimulus = {
4085
- kind: "user_message",
4086
- text: cmd.prompt,
4087
- senderId: "user"
4088
- };
4089
- await bus.signal(activeFlow.connectorId, {
4090
- promptFragment: renderStimulusPrompt(userStimulus),
4091
- meta: {
4092
- kind: "user_message",
4093
- runId: activeFlow.runId,
4094
- text: cmd.prompt,
4095
- senderId: "user"
4096
- }
4097
- });
4098
- } catch (err) {
4099
- const errMsg = err instanceof Error ? err.message : String(err);
4100
- log(`[serve] flow user-message turn failed: ${errMsg}`);
4101
- sendEvent({ type: "error", message: `Flow turn failed: ${errMsg}`, fatal: false });
4102
- }
4103
- break;
4104
- }
4105
- if (!driverStarted) {
4106
- try {
4107
- await driver.start();
4108
- driverStarted = true;
4109
- } catch (err) {
4110
- const errMsg = err instanceof Error ? err.message : String(err);
4111
- log(`[serve] driver start failed: ${errMsg}`);
4112
- sendEvent({ type: "error", message: `Driver start failed: ${errMsg}`, fatal: true });
4113
- break;
4114
- }
4115
- driver.on("agent-event", (event) => {
4116
- const normalized = normalizer.normalize(event);
4117
- for (const e of normalized) {
4118
- forwardNormalizedEvent(e);
4119
- }
4120
- });
4121
- driver.on("error", (err) => {
4122
- log(`[serve] driver error: ${err.message}`);
4123
- sendEvent({ type: "error", message: err.message, fatal: true });
4124
- });
4125
- }
4126
- sendEvent({ type: "status", phase: "thinking" });
4127
- try {
4128
- await driver.prompt(cmd.prompt);
4129
- } catch (err) {
4130
- const errMsg = err instanceof Error ? err.message : String(err);
4131
- log(`[serve] prompt failed: ${errMsg}`);
4132
- sendEvent({ type: "error", message: `Prompt failed: ${errMsg}`, fatal: true });
4133
- }
4568
+ case "prompt":
4569
+ await handlePrompt(cmd);
4134
4570
  break;
4135
- }
4136
- case "reply": {
4137
- try {
4138
- await sessionReady;
4139
- } catch (err) {
4140
- sendEvent({
4141
- type: "error",
4142
- message: `Session not available: ${err.message}`,
4143
- fatal: true
4144
- });
4145
- break;
4146
- }
4147
- log(`[serve] reply: ${cmd.answer.slice(0, 80)}...`);
4148
- try {
4149
- await driver.prompt(cmd.answer);
4150
- } catch (err) {
4151
- sendEvent({
4152
- type: "error",
4153
- message: `Reply failed: ${err instanceof Error ? err.message : String(err)}`,
4154
- fatal: false
4155
- });
4156
- }
4571
+ case "reply":
4572
+ await handleReply(cmd);
4157
4573
  break;
4158
- }
4159
- case "cancel": {
4160
- try {
4161
- await sessionReady;
4162
- } catch (err) {
4163
- sendEvent({
4164
- type: "error",
4165
- message: `Session not available: ${err.message}`,
4166
- fatal: true
4167
- });
4168
- break;
4169
- }
4170
- log("[serve] cancel");
4171
- try {
4172
- await driver.abort();
4173
- } catch (err) {
4174
- log(`[serve] cancel failed: ${err}`);
4175
- }
4574
+ case "cancel":
4575
+ await handleCancel();
4176
4576
  break;
4177
- }
4178
4577
  // v3: compact + lifecycle removed — reachable via runner.lifecycle capability
4179
4578
  // (the v3 `runner.lifecycle` cap takes `{ phase: 'hibernate' | 'close' | 'compact' }`).
4180
- case "resource_request": {
4181
- try {
4182
- await sessionReady;
4183
- } catch (err) {
4184
- sendEvent({
4185
- type: "error",
4186
- message: `Session not available: ${err.message}`,
4187
- fatal: true
4188
- });
4189
- break;
4190
- }
4191
- if (resourceManager?.has(cmd.connectorId)) {
4192
- handleMountResourceRequest(cmd, resourceManager, sendEvent);
4193
- break;
4194
- }
4195
- if (!resourceManager) {
4196
- sendEvent({
4197
- type: "resource_response",
4198
- requestId: cmd.requestId,
4199
- resourceId: cmd.connectorId,
4200
- operation: cmd.operation,
4201
- error: "No resources available \u2014 no mounts or connectors configured"
4202
- });
4203
- break;
4204
- }
4205
- void handleResourceRequest(cmd, resourceManager, sendEvent);
4579
+ case "resource_request":
4580
+ await handleResourceRequestCommand(cmd);
4206
4581
  break;
4207
- }
4208
4582
  // v3: state_action removed — reachable via the runner.set_state capability.
4209
4583
  // ── Generic connector dispatch (Protocol 2.2) ──────────────────────
4210
4584
  // The 6 typed flow commands (`start_flow`, `approve_flow_node`,
@@ -4213,152 +4587,15 @@ async function startAgentServer(opts) {
4213
4587
  // extraction. Hosts now drive flow execution exclusively through
4214
4588
  // `connector_mutate` against the `flow` connector. See
4215
4589
  // `MIGRATION-flow-connector.md` for the per-op mapping table.
4216
- case "connector_mutate": {
4217
- try {
4218
- if (!resourceManager) throw new Error("connector_mutate before resources ready");
4219
- const targetEntry = resourceManager.has?.(cmd.id) ? resourceManager.get(cmd.id) : null;
4220
- const isExistingFlow = targetEntry?.connector?.name === "flow";
4221
- const isBareFlowId = cmd.id === "flow";
4222
- if (cmd.op === "start") {
4223
- if (isExistingFlow || isBareFlowId) {
4224
- const payload = cmd.payload ?? {};
4225
- const flowDef = payload.flow;
4226
- const seed = payload.seed;
4227
- if (flowDef?.id && seed?.runId && seed?.startedBy) {
4228
- if (activeFlows.size > 0) {
4229
- serverLog.warn("connector_mutate flow.start: another flow already active", {
4230
- activeRunIds: Array.from(activeFlows.keys())
4231
- });
4232
- break;
4233
- }
4234
- const entry = await createActiveFlow(flowDef, { seed });
4235
- serverLog.info("connector_mutate flow.start", {
4236
- runId: entry.runId,
4237
- flowId: flowDef.id
4238
- });
4239
- break;
4240
- }
4241
- }
4242
- }
4243
- let dispatchId = cmd.id;
4244
- if (!targetEntry && isBareFlowId) {
4245
- const onlyActive = getOnlyActiveFlow();
4246
- if (onlyActive) {
4247
- dispatchId = onlyActive.connectorId;
4248
- }
4249
- }
4250
- await resourceManager.executeOp(dispatchId, cmd.op, cmd.payload ?? {});
4251
- } catch (err) {
4252
- serverLog.warn("connector_mutate failed", {
4253
- id: cmd.id,
4254
- op: cmd.op,
4255
- error: err instanceof Error ? err.message : String(err)
4256
- });
4257
- }
4590
+ case "connector_mutate":
4591
+ await handleConnectorMutate(cmd);
4258
4592
  break;
4259
- }
4260
- case "connector_query": {
4261
- let ok = true;
4262
- let result;
4263
- let error;
4264
- try {
4265
- if (!resourceManager) throw new Error("connector_query before resources ready");
4266
- let dispatchId = cmd.id;
4267
- if (!resourceManager.has?.(cmd.id) && cmd.id === "flow") {
4268
- const onlyActive = getOnlyActiveFlow();
4269
- if (onlyActive) {
4270
- dispatchId = onlyActive.connectorId;
4271
- }
4272
- }
4273
- result = await resourceManager.executeOp(dispatchId, cmd.op, cmd.payload ?? {});
4274
- } catch (err) {
4275
- ok = false;
4276
- error = err instanceof Error ? err.message : String(err);
4277
- }
4278
- sendEvent({
4279
- type: "connector_query_response",
4280
- requestId: cmd.requestId,
4281
- ok,
4282
- ...result !== void 0 ? { result } : {},
4283
- ...error !== void 0 ? { error } : {}
4284
- });
4593
+ case "connector_query":
4594
+ await handleConnectorQuery(cmd);
4285
4595
  break;
4286
- }
4287
- case "debug": {
4288
- try {
4289
- await sessionReady;
4290
- } catch (err) {
4291
- sendEvent({
4292
- type: "error",
4293
- message: `Session not available: ${err.message}`,
4294
- fatal: true
4295
- });
4296
- break;
4297
- }
4298
- log(`[serve] debug query=${cmd.query}`);
4299
- let result;
4300
- try {
4301
- switch (cmd.query) {
4302
- case "tools": {
4303
- const mcpServers = Object.keys(driver?.config?.mcpServers ?? {});
4304
- const allowedTools = driver?.config?.allowedTools ?? null;
4305
- result = { mcpServers, allowedTools, driverStarted };
4306
- break;
4307
- }
4308
- case "connectors": {
4309
- result = resourceManager?.listConnectors?.() ?? [];
4310
- break;
4311
- }
4312
- case "mcp": {
4313
- const servers = Object.entries(driver?.config?.mcpServers ?? {}).map(
4314
- ([name]) => ({ name })
4315
- );
4316
- result = { servers };
4317
- break;
4318
- }
4319
- case "config": {
4320
- const liveModel = driver?.getModel() ?? agentSession?.model;
4321
- result = {
4322
- driver: agentSession?.driverType,
4323
- model: liveModel,
4324
- provider: agentSession?.provider,
4325
- maxTurns,
4326
- secretsMode,
4327
- driverStarted,
4328
- projectDir: opts.projectDir,
4329
- versions: getFrameworkVersions()
4330
- };
4331
- break;
4332
- }
4333
- case "state": {
4334
- const storeId = cmd.args?.store;
4335
- if (storeId) {
4336
- try {
4337
- const snapshot = await resourceManager?.executeOp(storeId, "get", {});
4338
- result = snapshot ? JSON.parse(snapshot) : null;
4339
- } catch (err) {
4340
- result = { error: err instanceof Error ? err.message : String(err) };
4341
- }
4342
- } else {
4343
- const all = resourceManager?.listConnectors?.() ?? [];
4344
- result = { stores: all.map((c) => c.id) };
4345
- }
4346
- break;
4347
- }
4348
- case "commands": {
4349
- const commands = driver?.getSlashCommands?.() ?? [];
4350
- result = { commands, driverType: agentSession?.driverType };
4351
- break;
4352
- }
4353
- default:
4354
- result = { error: `Unknown debug query: ${cmd.query}` };
4355
- }
4356
- } catch (err) {
4357
- result = { error: err instanceof Error ? err.message : String(err) };
4358
- }
4359
- sendEvent({ type: "debug_result", query: cmd.query, data: result });
4596
+ case "debug":
4597
+ await handleDebug(cmd);
4360
4598
  break;
4361
- }
4362
4599
  // -----------------------------------------------------------------------
4363
4600
  // -----------------------------------------------------------------------
4364
4601
  // v3: add_resource, remove_resource, remount_mount, set_log_level,
@@ -4367,75 +4604,27 @@ async function startAgentServer(opts) {
4367
4604
  // capability invocations (audience: ['runtime']) — the platform calls
4368
4605
  // them through invokeRunnerCapability on the gateway. Handler bodies
4369
4606
  // live in the runner.* capability registrations.
4370
- case "shutdown": {
4371
- log("[serve] shutdown requested");
4372
- await cleanup();
4373
- if (transport instanceof WebSocketServerTransport) {
4374
- await transport.close();
4375
- }
4376
- disposeLogStore();
4377
- process.exit(0);
4607
+ case "shutdown":
4608
+ await handleShutdown();
4378
4609
  break;
4379
- }
4380
4610
  // Protocol v2 — capability round-trip results from the platform.
4381
- case "capability_result": {
4382
- const matched = resolveCapabilityResult(pendingCapabilityCalls, cmd);
4383
- if (!matched) {
4384
- log(
4385
- `[serve] capability_result for unknown callId=${cmd.callId} (timed out or already resolved); dropping`
4386
- );
4387
- }
4611
+ case "capability_result":
4612
+ handleCapabilityResult(cmd);
4388
4613
  break;
4389
- }
4390
4614
  // Protocol v2 — additional capability lifecycle commands. The platform
4391
4615
  // can register / deregister capabilities mid-session (e.g. when a user
4392
4616
  // installs an extension after `configure`). Approval routing is reserved
4393
4617
  // for a later phase; we accept the command shape for forward
4394
4618
  // compatibility and log it.
4395
- case "capability_register": {
4396
- for (const raw of cmd.capabilities) {
4397
- const wire = raw;
4398
- capabilityRegistry.register(
4399
- {
4400
- ...wire,
4401
- inputZod: { parse: (v) => v },
4402
- outputZod: void 0,
4403
- handler: buildClientCapabilityHandler2(wire)
4404
- },
4405
- "client"
4406
- );
4407
- }
4619
+ case "capability_register":
4620
+ handleCapabilityRegister(cmd);
4408
4621
  break;
4409
- }
4410
- case "capability_deregister": {
4411
- for (const name of cmd.names) {
4412
- capabilityRegistry.deregister(name);
4413
- }
4622
+ case "capability_deregister":
4623
+ handleCapabilityDeregister(cmd);
4414
4624
  break;
4415
- }
4416
- case "capability_approve": {
4417
- if (cmd.decision === "rejected") {
4418
- const matched = rejectCapabilityOnApprovalDeny(
4419
- pendingCapabilityCalls,
4420
- cmd.callId,
4421
- cmd.feedback
4422
- );
4423
- if (!matched) {
4424
- log(
4425
- `[serve] capability_approve(rejected) for unknown callId=${cmd.callId} (already resolved or timed out); dropping`
4426
- );
4427
- } else {
4428
- log(
4429
- `[serve] capability_approve(rejected) callId=${cmd.callId} decidedBy=${cmd.decidedBy}${cmd.feedback ? ` feedback=${JSON.stringify(cmd.feedback)}` : ""}`
4430
- );
4431
- }
4432
- } else {
4433
- log(
4434
- `[serve] capability_approve(approved) callId=${cmd.callId} decidedBy=${cmd.decidedBy} (awaiting capability_result)`
4435
- );
4436
- }
4625
+ case "capability_approve":
4626
+ handleCapabilityApprove(cmd);
4437
4627
  break;
4438
- }
4439
4628
  }
4440
4629
  }
4441
4630
  transport.onCommand((cmd) => {
@@ -4447,21 +4636,22 @@ async function startAgentServer(opts) {
4447
4636
  if (secretsMode === "env" && agentSession) {
4448
4637
  agentSession.startWatching();
4449
4638
  }
4639
+ function emitAvailabilityOnConnect() {
4640
+ if (resourceManager) {
4641
+ const wire = splitConnectorsForWire(resourceManager);
4642
+ log(
4643
+ `[serve] emitting resources_available: ${wire.mounts.length} mount(s), ${wire.connectors.length} connector(s)`
4644
+ );
4645
+ sendEvent({ type: "resources_available", ...wire });
4646
+ }
4647
+ const commands = driver?.getSlashCommands?.() ?? [];
4648
+ if (commands.length > 0) {
4649
+ log(`[serve] emitting commands_available: ${commands.length} command(s)`);
4650
+ sendEvent({ type: "commands_available", commands });
4651
+ }
4652
+ }
4450
4653
  if (transport instanceof WebSocketServerTransport) {
4451
- transport.onConnect(() => {
4452
- if (resourceManager) {
4453
- const wire = splitConnectorsForWire(resourceManager);
4454
- log(
4455
- `[serve] emitting resources_available: ${wire.mounts.length} mount(s), ${wire.connectors.length} connector(s)`
4456
- );
4457
- sendEvent({ type: "resources_available", ...wire });
4458
- }
4459
- const commands = driver?.getSlashCommands?.() ?? [];
4460
- if (commands.length > 0) {
4461
- log(`[serve] emitting commands_available: ${commands.length} command(s)`);
4462
- sendEvent({ type: "commands_available", commands });
4463
- }
4464
- });
4654
+ transport.onConnect(emitAvailabilityOnConnect);
4465
4655
  }
4466
4656
  if (secretsMode === "env") {
4467
4657
  log(
@@ -4471,53 +4661,56 @@ async function startAgentServer(opts) {
4471
4661
  log(`[serve] secrets=provisioned \u2014 session will initialize after provision_secrets`);
4472
4662
  }
4473
4663
  log(`[serve] project=${opts.projectDir}`);
4474
- process.on("uncaughtException", (err) => {
4475
- log(
4476
- `[serve] uncaughtException (non-fatal): ${err instanceof Error ? err.stack ?? err.message : String(err)}`
4477
- );
4478
- });
4479
- process.on("unhandledRejection", (reason) => {
4480
- log(
4481
- `[serve] unhandledRejection (non-fatal): ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}`
4482
- );
4483
- });
4484
- const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5e3;
4485
- let shuttingDown = false;
4486
- async function initiateShutdown(signal) {
4487
- if (shuttingDown) {
4488
- log(`[serve] ${signal} received again \u2014 forcing exit`);
4489
- process.exit(130);
4490
- }
4491
- shuttingDown = true;
4492
- log(`[serve] ${signal} received \u2014 shutting down (press Ctrl-C again to force)`);
4493
- const forceExitTimer = setTimeout(() => {
4494
- log(`[serve] graceful shutdown exceeded ${GRACEFUL_SHUTDOWN_TIMEOUT_MS}ms \u2014 forcing exit`);
4495
- process.exit(130);
4496
- }, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
4497
- forceExitTimer.unref?.();
4498
- try {
4499
- await cleanup();
4500
- } catch (err) {
4501
- log(`[serve] cleanup error: ${err.message}`);
4502
- }
4503
- try {
4504
- if (transport instanceof WebSocketServerTransport) {
4505
- await transport.close();
4664
+ installProcessHandlers();
4665
+ function installProcessHandlers() {
4666
+ process.on("uncaughtException", (err) => {
4667
+ log(
4668
+ `[serve] uncaughtException (non-fatal): ${err instanceof Error ? err.stack ?? err.message : String(err)}`
4669
+ );
4670
+ });
4671
+ process.on("unhandledRejection", (reason) => {
4672
+ log(
4673
+ `[serve] unhandledRejection (non-fatal): ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}`
4674
+ );
4675
+ });
4676
+ const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 5e3;
4677
+ let shuttingDown = false;
4678
+ async function initiateShutdown(signal) {
4679
+ if (shuttingDown) {
4680
+ log(`[serve] ${signal} received again \u2014 forcing exit`);
4681
+ process.exit(130);
4506
4682
  }
4507
- } catch (err) {
4508
- log(`[serve] transport close error: ${err.message}`);
4683
+ shuttingDown = true;
4684
+ log(`[serve] ${signal} received \u2014 shutting down (press Ctrl-C again to force)`);
4685
+ const forceExitTimer = setTimeout(() => {
4686
+ log(`[serve] graceful shutdown exceeded ${GRACEFUL_SHUTDOWN_TIMEOUT_MS}ms \u2014 forcing exit`);
4687
+ process.exit(130);
4688
+ }, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
4689
+ forceExitTimer.unref?.();
4690
+ try {
4691
+ await cleanup();
4692
+ } catch (err) {
4693
+ log(`[serve] cleanup error: ${err.message}`);
4694
+ }
4695
+ try {
4696
+ if (transport instanceof WebSocketServerTransport) {
4697
+ await transport.close();
4698
+ }
4699
+ } catch (err) {
4700
+ log(`[serve] transport close error: ${err.message}`);
4701
+ }
4702
+ clearTimeout(forceExitTimer);
4703
+ log("[serve] shutdown complete");
4704
+ disposeLogStore();
4705
+ process.exit(0);
4509
4706
  }
4510
- clearTimeout(forceExitTimer);
4511
- log("[serve] shutdown complete");
4512
- disposeLogStore();
4513
- process.exit(0);
4707
+ process.on("SIGTERM", () => {
4708
+ void initiateShutdown("SIGTERM");
4709
+ });
4710
+ process.on("SIGINT", () => {
4711
+ void initiateShutdown("SIGINT");
4712
+ });
4514
4713
  }
4515
- process.on("SIGTERM", () => {
4516
- void initiateShutdown("SIGTERM");
4517
- });
4518
- process.on("SIGINT", () => {
4519
- void initiateShutdown("SIGINT");
4520
- });
4521
4714
  }
4522
4715
  var SESSION_DIR = ".skaile";
4523
4716
  var SESSIONS_SUBDIR = "sessions";
@@ -4633,5 +4826,5 @@ function touchSession(state) {
4633
4826
  }
4634
4827
 
4635
4828
  export { CLAUDE_CODE_CREDENTIALS_KEY, COMPILE_MANIFEST_FILENAME, CapabilityRegistry, DEFAULT_CAPABILITY_CALL_TIMEOUT_MS, DEFAULT_COALESCE_MS, MarkdownStreamer, PreInitRingSink, agentDefinitionExists, bootstrapCapabilityRegistry, bootstrapRunnerLogStore, buildAgentResources, buildClientCapabilityHandler, buildContextSection, buildEnvironmentSection, builtinCapabilities, clearPreInitRingSink, clearSession, compileComposition, computeCapabilitySignature, createAgentSession, createSessionStimulusBus, defineCapability, deleteSession, dispatchRunnerCapabilityInvocation, emitSystemPromptComposed, ensureGitConfigInclude, extractClaudeAiOauthExpiresAt, getPreInitRingSink, handleMountResourceRequest, handleResourceRequest, installPreInitRingSink, listSessions, loadAgentManifest, loadCompileManifest, loadCompileManifestFromDir, loadSession, loadSessionById, newSession, registerCompositionCapabilities, rejectCapabilityOnApprovalDeny, resetRunnerLogStore, resolveAgentComposition, resolveAgentMixins, resolveBinding, resolveCapabilityCallTimeoutMs, resolveCapabilityResult, resolveComposition, resolveMixin, runAgentChat, saveSession, setCurrentSession, startAgentServer, touchSession, writeClaudeCodeCredentialsFile };
4636
- //# sourceMappingURL=chunk-TDZGENVP.js.map
4637
- //# sourceMappingURL=chunk-TDZGENVP.js.map
4829
+ //# sourceMappingURL=chunk-XOAF5QAE.js.map
4830
+ //# sourceMappingURL=chunk-XOAF5QAE.js.map