jishushell 0.6.18 → 0.7.3

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 (343) hide show
  1. package/apps/anythingllm-container.yaml +1 -0
  2. package/apps/browserless-chromium-container.yaml +1 -0
  3. package/apps/filebrowser-container.yaml +1 -0
  4. package/apps/hermes-container.yaml +1 -7
  5. package/apps/immich-container-lite.yaml +337 -0
  6. package/apps/immich-container.yaml +371 -0
  7. package/apps/jishu-kb-container.yaml +26 -21
  8. package/apps/ollama-binary.yaml +1 -0
  9. package/apps/ollama-cpu-container.yaml +1 -0
  10. package/apps/ollama-with-hollama-binary.yaml +2 -0
  11. package/apps/openclaw-binary.yaml +4 -8
  12. package/apps/openclaw-container.yaml +1 -7
  13. package/apps/openclaw-with-ollama-container.yaml +1 -0
  14. package/apps/openclaw-with-searxng-container.yaml +20 -0
  15. package/apps/searxng-container.yaml +20 -0
  16. package/apps/weknora-container.yaml +5 -0
  17. package/dependencies/jishushell-panel-0.7.3.tgz +0 -0
  18. package/dist/cli/core.js +1 -1
  19. package/dist/cli/core.js.map +1 -1
  20. package/dist/cli/doctor.js +96 -0
  21. package/dist/cli/doctor.js.map +1 -1
  22. package/dist/config.d.ts +9 -1
  23. package/dist/config.js +72 -2
  24. package/dist/config.js.map +1 -1
  25. package/dist/install.js +60 -19
  26. package/dist/install.js.map +1 -1
  27. package/dist/routes/admin.d.ts +2 -0
  28. package/dist/routes/admin.js +72 -0
  29. package/dist/routes/admin.js.map +1 -0
  30. package/dist/routes/docker.d.ts +2 -0
  31. package/dist/routes/docker.js +58 -0
  32. package/dist/routes/docker.js.map +1 -0
  33. package/dist/routes/file-mounts.js +5 -8
  34. package/dist/routes/file-mounts.js.map +1 -1
  35. package/dist/routes/instances.d.ts +0 -14
  36. package/dist/routes/instances.js +44 -1184
  37. package/dist/routes/instances.js.map +1 -1
  38. package/dist/server.d.ts +6 -0
  39. package/dist/server.js +53 -20
  40. package/dist/server.js.map +1 -1
  41. package/dist/services/app-common/catalog-service.js +15 -5
  42. package/dist/services/app-common/catalog-service.js.map +1 -1
  43. package/dist/services/app-common/delete-service.js +5 -0
  44. package/dist/services/app-common/delete-service.js.map +1 -1
  45. package/dist/services/app-common/instance-store.js +3 -0
  46. package/dist/services/app-common/instance-store.js.map +1 -1
  47. package/dist/services/app-common/lifecycle-service.js +12 -4
  48. package/dist/services/app-common/lifecycle-service.js.map +1 -1
  49. package/dist/services/app-common/ownership.d.ts +3 -0
  50. package/dist/services/app-common/ownership.js +11 -0
  51. package/dist/services/app-common/ownership.js.map +1 -0
  52. package/dist/services/app-common/runtime-facts.js +2 -0
  53. package/dist/services/app-common/runtime-facts.js.map +1 -1
  54. package/dist/services/app-common/spec-materializer.d.ts +0 -1
  55. package/dist/services/app-common/spec-materializer.js +21 -87
  56. package/dist/services/app-common/spec-materializer.js.map +1 -1
  57. package/dist/services/app-common/status-refresh.js +25 -13
  58. package/dist/services/app-common/status-refresh.js.map +1 -1
  59. package/dist/services/app-modules/browserless/routes.js +5 -3
  60. package/dist/services/app-modules/browserless/routes.js.map +1 -1
  61. package/dist/services/capabilities/contract.d.ts +1 -2
  62. package/dist/services/capabilities/contract.js +0 -10
  63. package/dist/services/capabilities/contract.js.map +1 -1
  64. package/dist/services/capabilities/endpoint-validator.js +0 -1
  65. package/dist/services/capabilities/endpoint-validator.js.map +1 -1
  66. package/dist/services/capabilities/health.js +1 -1
  67. package/dist/services/capabilities/health.js.map +1 -1
  68. package/dist/services/capability-proxy/http.d.ts +7 -0
  69. package/dist/services/capability-proxy/http.js +555 -0
  70. package/dist/services/capability-proxy/http.js.map +1 -0
  71. package/dist/services/capability-proxy/terminal.d.ts +4 -0
  72. package/dist/services/capability-proxy/terminal.js +179 -0
  73. package/dist/services/capability-proxy/terminal.js.map +1 -0
  74. package/dist/services/connections/admin.js +19 -9
  75. package/dist/services/connections/admin.js.map +1 -1
  76. package/dist/services/connections/apply.d.ts +3 -9
  77. package/dist/services/connections/apply.js +0 -29
  78. package/dist/services/connections/apply.js.map +1 -1
  79. package/dist/services/connections/transactor.js +2 -2
  80. package/dist/services/connections/transactor.js.map +1 -1
  81. package/dist/services/files/bootstrap.d.ts +7 -0
  82. package/dist/services/files/bootstrap.js +16 -0
  83. package/dist/services/files/bootstrap.js.map +1 -0
  84. package/dist/services/files/photos/upload-page.d.ts +2 -0
  85. package/dist/services/files/photos/upload-page.js +248 -0
  86. package/dist/services/files/photos/upload-page.js.map +1 -0
  87. package/dist/services/files/photos/upload-store.d.ts +74 -0
  88. package/dist/services/files/photos/upload-store.js +432 -0
  89. package/dist/services/files/photos/upload-store.js.map +1 -0
  90. package/dist/services/http/proxy-utils.d.ts +7 -0
  91. package/dist/services/http/proxy-utils.js +29 -0
  92. package/dist/services/http/proxy-utils.js.map +1 -0
  93. package/dist/services/http/request-utils.d.ts +3 -0
  94. package/dist/services/http/request-utils.js +23 -0
  95. package/dist/services/http/request-utils.js.map +1 -0
  96. package/dist/services/instances/manager.d.ts +6 -5
  97. package/dist/services/instances/manager.js +45 -51
  98. package/dist/services/instances/manager.js.map +1 -1
  99. package/dist/services/instances/pairing.d.ts +17 -0
  100. package/dist/services/instances/pairing.js +53 -0
  101. package/dist/services/instances/pairing.js.map +1 -0
  102. package/dist/services/instances/status.d.ts +2 -0
  103. package/dist/services/instances/status.js +11 -0
  104. package/dist/services/instances/status.js.map +1 -0
  105. package/dist/services/integrations/hermes/integration.d.ts +1 -1
  106. package/dist/services/integrations/hermes/integration.js +7 -8
  107. package/dist/services/integrations/hermes/integration.js.map +1 -1
  108. package/dist/services/integrations/immich/client.d.ts +93 -0
  109. package/dist/services/integrations/immich/client.js +458 -0
  110. package/dist/services/integrations/immich/client.js.map +1 -0
  111. package/dist/services/integrations/immich/config.d.ts +15 -0
  112. package/dist/services/integrations/immich/config.js +178 -0
  113. package/dist/services/integrations/immich/config.js.map +1 -0
  114. package/dist/services/integrations/immich/discovery.d.ts +9 -0
  115. package/dist/services/integrations/immich/discovery.js +101 -0
  116. package/dist/services/integrations/immich/discovery.js.map +1 -0
  117. package/dist/services/integrations/immich/gallery-renderer.d.ts +5 -0
  118. package/dist/services/integrations/immich/gallery-renderer.js +150 -0
  119. package/dist/services/integrations/immich/gallery-renderer.js.map +1 -0
  120. package/dist/services/integrations/immich/immich-shim.d.ts +11 -0
  121. package/dist/services/integrations/immich/immich-shim.js +439 -0
  122. package/dist/services/integrations/immich/immich-shim.js.map +1 -0
  123. package/dist/services/integrations/immich/integration.d.ts +18 -0
  124. package/dist/services/integrations/immich/integration.js +64 -0
  125. package/dist/services/integrations/immich/integration.js.map +1 -0
  126. package/dist/services/integrations/immich/photo-library.d.ts +4 -0
  127. package/dist/services/integrations/immich/photo-library.js +63 -0
  128. package/dist/services/integrations/immich/photo-library.js.map +1 -0
  129. package/dist/services/integrations/immich/review-executor.d.ts +3 -0
  130. package/dist/services/integrations/immich/review-executor.js +41 -0
  131. package/dist/services/integrations/immich/review-executor.js.map +1 -0
  132. package/dist/services/integrations/immich/review-session-service.d.ts +27 -0
  133. package/dist/services/integrations/immich/review-session-service.js +206 -0
  134. package/dist/services/integrations/immich/review-session-service.js.map +1 -0
  135. package/dist/services/integrations/immich/review-store.d.ts +47 -0
  136. package/dist/services/integrations/immich/review-store.js +347 -0
  137. package/dist/services/integrations/immich/review-store.js.map +1 -0
  138. package/dist/services/integrations/immich/routes.d.ts +7 -0
  139. package/dist/services/integrations/immich/routes.js +363 -0
  140. package/dist/services/integrations/immich/routes.js.map +1 -0
  141. package/dist/services/integrations/immich/types.d.ts +186 -0
  142. package/dist/services/integrations/immich/types.js +2 -0
  143. package/dist/services/integrations/immich/types.js.map +1 -0
  144. package/dist/services/integrations/index.d.ts +1 -0
  145. package/dist/services/integrations/index.js +1 -0
  146. package/dist/services/integrations/index.js.map +1 -1
  147. package/dist/services/integrations/installable/installers/integration.js +113 -7
  148. package/dist/services/integrations/installable/installers/integration.js.map +1 -1
  149. package/dist/services/integrations/jishukb/integration.d.ts +3 -1
  150. package/dist/services/integrations/jishukb/integration.js +121 -10
  151. package/dist/services/integrations/jishukb/integration.js.map +1 -1
  152. package/dist/services/integrations/openclaw/integration.d.ts +21 -7
  153. package/dist/services/integrations/openclaw/integration.js +385 -158
  154. package/dist/services/integrations/openclaw/integration.js.map +1 -1
  155. package/dist/services/integrations/openclaw/jishukb-native-mcp.d.ts +58 -0
  156. package/dist/services/integrations/openclaw/jishukb-native-mcp.js +373 -0
  157. package/dist/services/integrations/openclaw/jishukb-native-mcp.js.map +1 -0
  158. package/dist/services/integrations/openclaw/jishukb-shim.d.ts +8 -4
  159. package/dist/services/integrations/openclaw/jishukb-shim.js +624 -17
  160. package/dist/services/integrations/openclaw/jishukb-shim.js.map +1 -1
  161. package/dist/services/integrations/openclaw/mcporter.d.ts +13 -0
  162. package/dist/services/integrations/openclaw/mcporter.js +31 -0
  163. package/dist/services/integrations/openclaw/mcporter.js.map +1 -1
  164. package/dist/services/integrations/openclaw/native-mcp.d.ts +48 -0
  165. package/dist/services/integrations/openclaw/native-mcp.js +125 -0
  166. package/dist/services/integrations/openclaw/native-mcp.js.map +1 -0
  167. package/dist/services/integrations/openclaw/routes.js +4 -1
  168. package/dist/services/integrations/openclaw/routes.js.map +1 -1
  169. package/dist/services/integrations/types.d.ts +5 -17
  170. package/dist/services/repair/runtime-repair.js +68 -1
  171. package/dist/services/repair/runtime-repair.js.map +1 -1
  172. package/dist/services/runtime/docker-network.d.ts +8 -0
  173. package/dist/services/runtime/docker-network.js +123 -0
  174. package/dist/services/runtime/docker-network.js.map +1 -0
  175. package/dist/services/runtime/driver-registry.d.ts +4 -0
  176. package/dist/services/runtime/driver-registry.js.map +1 -1
  177. package/dist/services/runtime/drivers/nomad.d.ts +1 -0
  178. package/dist/services/runtime/drivers/nomad.js +35 -5
  179. package/dist/services/runtime/drivers/nomad.js.map +1 -1
  180. package/dist/services/runtime/service-manager.d.ts +2 -0
  181. package/dist/services/runtime/service-manager.js +18 -0
  182. package/dist/services/runtime/service-manager.js.map +1 -0
  183. package/dist/services/runtime/types.d.ts +1 -0
  184. package/dist/services/runtime/workload-compiler.js +29 -4
  185. package/dist/services/runtime/workload-compiler.js.map +1 -1
  186. package/dist/services/setup/setup-manager.js +29 -73
  187. package/dist/services/setup/setup-manager.js.map +1 -1
  188. package/dist/services/system/runtime-ownership.d.ts +36 -0
  189. package/dist/services/system/runtime-ownership.js +250 -0
  190. package/dist/services/system/runtime-ownership.js.map +1 -0
  191. package/dist/services/system/system-reconciler.js +53 -0
  192. package/dist/services/system/system-reconciler.js.map +1 -1
  193. package/dist/types.d.ts +19 -3
  194. package/dist/utils/path-safety.js +1 -1
  195. package/dist/utils/service-user.d.ts +13 -0
  196. package/dist/utils/service-user.js +129 -0
  197. package/dist/utils/service-user.js.map +1 -0
  198. package/install/jishu-install.sh +0 -1
  199. package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
  200. package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
  201. package/node_modules/brace-expansion/dist/esm/index.js +24 -14
  202. package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
  203. package/node_modules/brace-expansion/package.json +2 -2
  204. package/node_modules/fast-uri/index.js +1 -1
  205. package/node_modules/fast-uri/package.json +1 -1
  206. package/node_modules/fast-uri/test/security.test.js +28 -0
  207. package/node_modules/fastify/SECURITY.md +1 -1
  208. package/node_modules/fastify/SPONSORS.md +6 -4
  209. package/node_modules/fastify/docs/Guides/Database.md +0 -28
  210. package/node_modules/fastify/docs/Guides/Ecosystem.md +13 -2
  211. package/node_modules/fastify/docs/Guides/Serverless.md +2 -2
  212. package/node_modules/fastify/docs/Guides/Write-Plugin.md +1 -1
  213. package/node_modules/fastify/docs/Reference/Encapsulation.md +27 -26
  214. package/node_modules/fastify/docs/Reference/Errors.md +10 -4
  215. package/node_modules/fastify/docs/Reference/HTTP2.md +10 -10
  216. package/node_modules/fastify/docs/Reference/Hooks.md +4 -4
  217. package/node_modules/fastify/docs/Reference/Index.md +14 -16
  218. package/node_modules/fastify/docs/Reference/LTS.md +12 -13
  219. package/node_modules/fastify/docs/Reference/Lifecycle.md +9 -8
  220. package/node_modules/fastify/docs/Reference/Logging.md +44 -39
  221. package/node_modules/fastify/docs/Reference/Middleware.md +21 -25
  222. package/node_modules/fastify/docs/Reference/Principles.md +2 -2
  223. package/node_modules/fastify/docs/Reference/Reply.md +6 -1
  224. package/node_modules/fastify/docs/Reference/Request.md +27 -16
  225. package/node_modules/fastify/docs/Reference/Routes.md +5 -2
  226. package/node_modules/fastify/docs/Reference/Server.md +31 -3
  227. package/node_modules/fastify/docs/Reference/Type-Providers.md +29 -5
  228. package/node_modules/fastify/docs/Reference/Validation-and-Serialization.md +15 -2
  229. package/node_modules/fastify/docs/Reference/Warnings.md +7 -6
  230. package/node_modules/fastify/eslint.config.js +7 -2
  231. package/node_modules/fastify/fastify.d.ts +8 -3
  232. package/node_modules/fastify/fastify.js +43 -14
  233. package/node_modules/fastify/lib/content-type-parser.js +13 -1
  234. package/node_modules/fastify/lib/decorate.js +11 -3
  235. package/node_modules/fastify/lib/error-handler.js +4 -3
  236. package/node_modules/fastify/lib/error-serializer.js +59 -59
  237. package/node_modules/fastify/lib/errors.js +16 -1
  238. package/node_modules/fastify/lib/four-oh-four.js +14 -9
  239. package/node_modules/fastify/lib/handle-request.js +11 -5
  240. package/node_modules/fastify/lib/plugin-override.js +2 -1
  241. package/node_modules/fastify/lib/plugin-utils.js +5 -5
  242. package/node_modules/fastify/lib/reply.js +63 -8
  243. package/node_modules/fastify/lib/request.js +14 -4
  244. package/node_modules/fastify/lib/route.js +20 -6
  245. package/node_modules/fastify/lib/schema-controller.js +1 -1
  246. package/node_modules/fastify/lib/schemas.js +37 -30
  247. package/node_modules/fastify/lib/symbols.js +3 -1
  248. package/node_modules/fastify/lib/validation.js +1 -13
  249. package/node_modules/fastify/lib/warnings.js +3 -3
  250. package/node_modules/fastify/package.json +13 -15
  251. package/node_modules/fastify/scripts/validate-ecosystem-links.js +1 -0
  252. package/node_modules/fastify/test/bundler/esbuild/package.json +1 -1
  253. package/node_modules/fastify/test/close-pipelining.test.js +1 -2
  254. package/node_modules/fastify/test/custom-http-server.test.js +38 -0
  255. package/node_modules/fastify/test/decorator-instance-properties.test.js +63 -0
  256. package/node_modules/fastify/test/diagnostics-channel/async-error-handler.test.js +74 -0
  257. package/node_modules/fastify/test/hooks.test.js +23 -0
  258. package/node_modules/fastify/test/http-methods/get.test.js +1 -1
  259. package/node_modules/fastify/test/http2/plain.test.js +135 -0
  260. package/node_modules/fastify/test/http2/secure-with-fallback.test.js +1 -1
  261. package/node_modules/fastify/test/https/https.test.js +1 -2
  262. package/node_modules/fastify/test/internals/errors.test.js +31 -1
  263. package/node_modules/fastify/test/internals/plugin.test.js +3 -1
  264. package/node_modules/fastify/test/internals/request.test.js +27 -3
  265. package/node_modules/fastify/test/internals/schema-controller-perf.test.js +33 -0
  266. package/node_modules/fastify/test/logger/logging.test.js +18 -1
  267. package/node_modules/fastify/test/logger/options.test.js +38 -1
  268. package/node_modules/fastify/test/reply-error.test.js +1 -1
  269. package/node_modules/fastify/test/reply-trailers.test.js +70 -0
  270. package/node_modules/fastify/test/request-media-type.test.js +105 -0
  271. package/node_modules/fastify/test/route-prefix.test.js +34 -0
  272. package/node_modules/fastify/test/router-options.test.js +222 -11
  273. package/node_modules/fastify/test/schema-serialization.test.js +108 -0
  274. package/node_modules/fastify/test/schema-validation.test.js +24 -0
  275. package/node_modules/fastify/test/scripts/validate-ecosystem-links.test.js +40 -57
  276. package/node_modules/fastify/test/throw.test.js +14 -0
  277. package/node_modules/fastify/test/trust-proxy.test.js +21 -0
  278. package/node_modules/fastify/test/types/content-type-parser.tst.ts +70 -0
  279. package/node_modules/fastify/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  280. package/node_modules/fastify/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  281. package/node_modules/fastify/test/types/errors.tst.ts +91 -0
  282. package/node_modules/fastify/test/types/fastify.tst.ts +351 -0
  283. package/node_modules/fastify/test/types/hooks.tst.ts +578 -0
  284. package/node_modules/fastify/test/types/instance.tst.ts +597 -0
  285. package/node_modules/fastify/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  286. package/node_modules/fastify/test/types/plugin.tst.ts +96 -0
  287. package/node_modules/fastify/test/types/register.tst.ts +245 -0
  288. package/node_modules/fastify/test/types/reply.tst.ts +297 -0
  289. package/node_modules/fastify/test/types/request.tst.ts +199 -0
  290. package/node_modules/fastify/test/types/route.tst.ts +576 -0
  291. package/node_modules/fastify/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  292. package/node_modules/fastify/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  293. package/node_modules/fastify/test/types/tsconfig.json +9 -0
  294. package/node_modules/fastify/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  295. package/node_modules/fastify/test/types/using.tst.ts +14 -0
  296. package/node_modules/fastify/types/errors.d.ts +3 -0
  297. package/node_modules/fastify/types/request.d.ts +23 -2
  298. package/node_modules/jishushell-panel/output/public/assets/{ApiKeyField-NKcbHjNz.js → ApiKeyField-Ce5d1xna.js} +1 -1
  299. package/node_modules/jishushell-panel/output/public/assets/{Dashboard-Da1fL38t.js → Dashboard-BXame3yg.js} +1 -1
  300. package/node_modules/jishushell-panel/output/public/assets/{HermesChatPanel-DZvmYsoh.js → HermesChatPanel-BHZtPCJd.js} +1 -1
  301. package/node_modules/jishushell-panel/output/public/assets/{HermesConfigForm-BLUWlKwm.js → HermesConfigForm-CB3GbNX9.js} +1 -1
  302. package/node_modules/jishushell-panel/output/public/assets/{InitPassword-BAKsshzk.js → InitPassword-Boab9F6g.js} +1 -1
  303. package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-DrIWCqo-.js +14 -0
  304. package/node_modules/jishushell-panel/output/public/assets/{Login-DHeOmwI8.js → Login-CzpOkNau.js} +1 -1
  305. package/node_modules/jishushell-panel/output/public/assets/NewInstance-CANXyCcL.js +1 -0
  306. package/node_modules/jishushell-panel/output/public/assets/{ProviderRecommendations-H0ByEYF0.js → ProviderRecommendations-BABo9VOC.js} +1 -1
  307. package/node_modules/jishushell-panel/output/public/assets/Settings-CKp5XxFh.js +1 -0
  308. package/node_modules/jishushell-panel/output/public/assets/Setup-C7xVDPow.js +1 -0
  309. package/node_modules/jishushell-panel/output/public/assets/{WeixinLoginPanel-D-T6BxkQ.js → WeixinLoginPanel-B765Xz4C.js} +1 -1
  310. package/node_modules/jishushell-panel/output/public/assets/{index-ERt6_ngA.js → index-Bs6DSbiR.js} +6 -6
  311. package/node_modules/jishushell-panel/output/public/assets/{registry-DF93EzIb.js → registry-sWIZsIEF.js} +2 -2
  312. package/node_modules/jishushell-panel/output/public/assets/{usePolling-DeoThIQn.js → usePolling-D4IDOQd_.js} +1 -1
  313. package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-Df8aUdv8.js +1 -0
  314. package/node_modules/jishushell-panel/output/public/assets/{vendor-react-Cc84NArf.js → vendor-react-0L0rjmYG.js} +3 -3
  315. package/node_modules/jishushell-panel/output/public/index.html +3 -3
  316. package/node_modules/jishushell-panel/package.json +1 -1
  317. package/node_modules/semver/classes/range.js +6 -2
  318. package/node_modules/semver/package.json +1 -1
  319. package/package.json +4 -4
  320. package/scripts/check-app-spec.mjs +4 -12
  321. package/scripts/check-architecture-boundaries.mjs +178 -0
  322. package/scripts/pack-gui-and-send-pi.sh +5 -3
  323. package/dependencies/jishushell-panel-0.6.18.tgz +0 -0
  324. package/dist/services/connections/suggestions.d.ts +0 -27
  325. package/dist/services/connections/suggestions.js +0 -124
  326. package/dist/services/connections/suggestions.js.map +0 -1
  327. package/node_modules/fastify/test/types/content-type-parser.test-d.ts +0 -72
  328. package/node_modules/fastify/test/types/errors.test-d.ts +0 -90
  329. package/node_modules/fastify/test/types/fastify.test-d.ts +0 -352
  330. package/node_modules/fastify/test/types/hooks.test-d.ts +0 -550
  331. package/node_modules/fastify/test/types/import.ts +0 -2
  332. package/node_modules/fastify/test/types/instance.test-d.ts +0 -588
  333. package/node_modules/fastify/test/types/plugin.test-d.ts +0 -97
  334. package/node_modules/fastify/test/types/register.test-d.ts +0 -237
  335. package/node_modules/fastify/test/types/reply.test-d.ts +0 -254
  336. package/node_modules/fastify/test/types/request.test-d.ts +0 -188
  337. package/node_modules/fastify/test/types/route.test-d.ts +0 -553
  338. package/node_modules/fastify/test/types/using.test-d.ts +0 -17
  339. package/node_modules/jishushell-panel/output/public/assets/InstanceDetail-Dgyc_TX5.js +0 -14
  340. package/node_modules/jishushell-panel/output/public/assets/NewInstance-CIy0cYtp.js +0 -1
  341. package/node_modules/jishushell-panel/output/public/assets/Settings-DAT-UMfP.js +0 -1
  342. package/node_modules/jishushell-panel/output/public/assets/Setup-g3uckFYR.js +0 -1
  343. package/node_modules/jishushell-panel/output/public/assets/vendor-i18n-CS8DFbkQ.js +0 -1
@@ -1,839 +1,33 @@
1
- import { getServiceManagerType } from "../config.js";
2
1
  import { assertNotLocked } from "../services/backup/backup-manager.js";
3
2
  import * as appService from "../services/app-common/service.js";
4
3
  import * as appLifecycle from "../services/app-common/lifecycle-service.js";
5
- import { cleanupInstance as cleanupProxyInstance, getLastProxyError, invalidateConfigCache, } from "../services/llm-proxy/instance-proxy.js";
4
+ import { cleanupInstance as cleanupProxyInstance, getLastProxyError, } from "../services/llm-proxy/instance-proxy.js";
6
5
  import { createInstance, isInstanceAdminError, validateId, } from "../services/instances/admin.js";
7
6
  import { cloneInstance, isInstanceCloneError } from "../services/instances/clone.js";
8
7
  import { getConnectionStatus, getConnectionSummary, isConnectionAdminError, replaceConnections, } from "../services/connections/admin.js";
9
8
  import { getAppConfigMeta, isAppConfigAdminError, readAppConfig, writeAppConfig, } from "../services/instances/config-admin.js";
10
- import { augmentInstanceMetadata, getInstanceCapabilities, resolvePrimaryIntegrationKind, } from "../services/runtime/instance.js";
11
- import { getIntegration, hasIntegration } from "../services/integrations/index.js";
9
+ import { augmentInstanceMetadata, resolvePrimaryIntegrationKind, } from "../services/runtime/instance.js";
10
+ import { getIntegration } from "../services/integrations/index.js";
12
11
  import { invalidateExecutionOwner } from "../services/app-common/execution-owner.js";
13
12
  import { markRuntimeRepairRestartApplied } from "../services/repair/runtime-repair.js";
14
- import { getCapabilityHtmlRewriter } from "../services/capability-proxy/html-rewriters/index.js";
15
- import { createHash } from "node:crypto";
16
- import { assertTerminalSessionOwner, getTerminalSession, getTerminalSessionEvents, sendTerminalSessionInput, startTerminalSession, stopTerminalSession, subscribeTerminalSession, } from "../services/app-common/terminal-session-manager.js";
17
- import { refreshInstanceStatus } from "../services/app-common/status-refresh.js";
18
- import { writeSecretFile } from "../utils/fs.js";
19
- import { Readable } from "node:stream";
20
- export { validateId };
21
- // Hop-by-hop headers that must not be forwarded by a proxy (RFC 2616 §13.5.1).
22
- // Exported for integration-owned route modules that implement their own HTTP proxies.
23
- export const HOP_BY_HOP = new Set([
24
- "connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
25
- "te", "trailer", "transfer-encoding", "upgrade",
26
- ]);
27
- /**
28
- * Strip the panel session cookie (`jishushell_session`) from a cookie header
29
- * value, preserving any other cookies for upstream forwarding.
30
- */
31
- function stripPanelSessionCookie(value) {
32
- if (value === undefined)
33
- return undefined;
34
- const cookie = Array.isArray(value) ? value.join("; ") : value;
35
- const preserved = cookie
36
- .split(";")
37
- .map((part) => part.trim())
38
- .filter((part) => part && !/^jishushell_session=/i.test(part));
39
- return preserved.length ? preserved.join("; ") : undefined;
40
- }
41
- function readHeaderValue(value) {
42
- const header = Array.isArray(value) ? value[0] : value;
43
- return typeof header === "string" && header.trim() ? header.trim() : null;
44
- }
45
- function parseHttpOrigin(value) {
46
- if (!value)
47
- return null;
48
- try {
49
- const parsed = new URL(value);
50
- return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.origin : null;
51
- }
52
- catch {
53
- return null;
54
- }
55
- }
56
- export function inferRequestOrigin(request) {
57
- // Only trust browser-sent Origin/Referer for auto-allowlisting. Host and
58
- // X-Forwarded-* are proxy metadata and should not become persisted origin
59
- // policy by themselves.
60
- return (parseHttpOrigin(readHeaderValue(request?.headers?.origin))
61
- ?? parseHttpOrigin(readHeaderValue(request?.headers?.referer)));
62
- }
63
- function capabilityProxyPath(instanceId, capability) {
64
- return `/api/instances/${encodeURIComponent(instanceId)}/provides/${encodeURIComponent(capability)}`;
65
- }
66
- function joinProxyPath(basePath, suffix) {
67
- const normalizedBase = basePath.replace(/\/+$/, "");
68
- const normalizedSuffix = suffix.replace(/^\/+/, "");
69
- if (!normalizedSuffix)
70
- return normalizedBase;
71
- return `${normalizedBase}/${normalizedSuffix}`;
72
- }
73
- function canonicalCapabilityProxyBase(basePath, capabilityPath) {
74
- const normalizedCapabilityPath = typeof capabilityPath === "string" ? capabilityPath.trim() : "";
75
- const needsTrailingSlash = !normalizedCapabilityPath || normalizedCapabilityPath.endsWith("/");
76
- return needsTrailingSlash ? `${basePath}/` : basePath;
77
- }
78
- function rewriteCapabilityLocation(basePath, canonicalBasePath, pathname, search = "", hash = "") {
79
- const rewrittenPath = pathname === "/"
80
- ? canonicalBasePath
81
- : joinProxyPath(basePath, pathname);
82
- return `${rewrittenPath}${search}${hash}`;
83
- }
84
- export function joinUpstreamPath(basePath, suffix) {
85
- const normalizedBase = typeof basePath === "string" && basePath.trim()
86
- ? (basePath.startsWith("/") ? basePath : `/${basePath}`)
87
- : "/";
88
- const normalizedSuffix = suffix.replace(/^\/+/, "");
89
- if (!normalizedSuffix)
90
- return normalizedBase;
91
- return `${normalizedBase.replace(/\/+$/, "")}/${normalizedSuffix}`;
92
- }
93
- function shouldRewriteProxyResponse(contentType) {
94
- const value = (contentType ?? "").toLowerCase();
95
- return value.includes("text/html") || value.includes("text/css");
96
- }
97
- function capabilityProxyCleanupToken(parts) {
98
- const hash = createHash("sha256");
99
- for (const part of parts) {
100
- hash.update(part ?? "");
101
- hash.update("\0");
102
- }
103
- return hash.digest("hex").slice(0, 32);
104
- }
105
- function hasCookieValue(cookieHeader, name, expectedValue) {
106
- return cookieHeader
107
- .split(";")
108
- .map((part) => part.trim())
109
- .some((part) => {
110
- const eq = part.indexOf("=");
111
- if (eq < 0)
112
- return false;
113
- return part.slice(0, eq) === name && part.slice(eq + 1) === expectedValue;
114
- });
115
- }
116
- export async function resolveHttpCapability(instanceId, capabilityName) {
117
- const capability = appService
118
- .getProvidedCapabilitiesForApp(instanceId)
119
- .find((entry) => entry.capability === capabilityName);
120
- if (!capability)
121
- throw new Error(`Capability '${capabilityName}' not found`);
122
- if (capability.visibility === "internal") {
123
- throw new Error(`Capability '${capabilityName}' is not externally accessible`);
124
- }
125
- if (capability.protocol !== "http" && capability.protocol !== "https") {
126
- throw new Error(`Capability '${capabilityName}' does not use HTTP(S)`);
127
- }
128
- const runtimePort = appService.resolveRuntimeCapabilityPort(instanceId, capabilityName);
129
- if (typeof runtimePort !== "number" || runtimePort < 1) {
130
- throw new Error(`Capability '${capabilityName}' has no resolved port`);
131
- }
132
- const upstreamHost = await appService.getHostForAppPort(instanceId, runtimePort);
133
- const proxyBase = capabilityProxyPath(instanceId, capabilityName);
134
- const canonicalProxyBase = canonicalCapabilityProxyBase(proxyBase, capability.path);
135
- return {
136
- capability,
137
- targetBaseUrl: `${capability.protocol}://${appService.urlHost(upstreamHost)}:${runtimePort}`,
138
- proxyUrl: canonicalProxyBase,
139
- };
140
- }
141
- /**
142
- * Inject a tiny <script> that monkey-patches `fetch`, `XMLHttpRequest.open`,
143
- * and `Element.prototype.{setAttribute,src,href}` so that same-origin absolute
144
- * paths (e.g. `/api/v1/...`, `/static/...`) are transparently rewritten to go
145
- * through the capability proxy prefix.
146
- *
147
- * The alternative — rewriting every JS bundle byte-for-byte — is fragile and
148
- * expensive; a runtime shim at document load is the standard approach used by
149
- * reverse-proxy front-ends (cf. Cloudflare Access, oauth2-proxy, etc.).
150
- */
151
- function capabilityProxyBootstrap(proxyBasePath) {
152
- const prefix = JSON.stringify(proxyBasePath.replace(/\/+$/, ""));
153
- const workerPrelude = JSON.stringify([
154
- "(function(){",
155
- `var P=${prefix};`,
156
- "function px(path){",
157
- "if(!path||path.charAt(0)!=='/'||path.charAt(1)==='/'||path.indexOf(P)===0||path.indexOf('/api/instances/')===0)return path;",
158
- "return P+path;",
159
- "}",
160
- "function str(u){return typeof u==='string'?u:(u&&typeof u.href==='string'?u.href:null);}",
161
- "function rwWs(u){",
162
- "var s0=str(u);if(!s0)return u;",
163
- "var H=typeof __capProxyHost==='string'?__capProxyHost:self.location.host;",
164
- "var L=typeof __capProxyProtocol==='string'?__capProxyProtocol:self.location.protocol;",
165
- "var B=(L==='https:'?'https:':'http:')+'//'+H+'/';",
166
- "try{var p=new URL(s0,B);",
167
- "if(p.host===H&&(p.protocol==='ws:'||p.protocol==='wss:'||p.protocol===L)){",
168
- "var pp=px(p.pathname);",
169
- "if(pp!==p.pathname){var s=L==='https:'?'wss:':'ws:';return s+'//'+H+pp+p.search+p.hash;}",
170
- "}}catch(_e){}",
171
- "return u;",
172
- "}",
173
- "var _WS=self.WebSocket;",
174
- "if(typeof _WS==='function')self.WebSocket=function(url,protocols){url=rwWs(url);return protocols!==undefined?new _WS(url,protocols):new _WS(url);};",
175
- "if(typeof _WS==='function'){self.WebSocket.prototype=_WS.prototype;self.WebSocket.CONNECTING=_WS.CONNECTING;self.WebSocket.OPEN=_WS.OPEN;self.WebSocket.CLOSING=_WS.CLOSING;self.WebSocket.CLOSED=_WS.CLOSED;}",
176
- "})();",
177
- ].join(""));
178
- return [
179
- "<script>(function(){",
180
- `var P=${prefix};`,
181
- "var O=window.location.origin;",
182
- // --- Service Worker neutralization ---
183
- // Embedded SPAs like OpenWebUI register a service worker that aggressively
184
- // caches HTML and JS chunks at the proxy origin. After we update the proxy
185
- // bootstrap, the SW would keep serving the old (unpatched) HTML, so socket.io
186
- // never gets the WebSocket monkey-patch and the splash screen spins forever.
187
- // Unregister any existing SW, purge its caches, and block future
188
- // registrations for the lifetime of the iframe.
189
- "try{if(navigator.serviceWorker){",
190
- "if(navigator.serviceWorker.getRegistrations){",
191
- "navigator.serviceWorker.getRegistrations().then(function(rs){rs.forEach(function(r){try{r.unregister()}catch(_e){}})}).catch(function(){});",
192
- "}",
193
- "navigator.serviceWorker.register=function(){return Promise.reject(new Error('service worker disabled in capability proxy'))};",
194
- "}}catch(_e){}",
195
- "try{if(window.caches&&caches.keys){caches.keys().then(function(ks){ks.forEach(function(k){try{caches.delete(k)}catch(_e){}})}).catch(function(){});}}catch(_e){}",
196
- // Only rewrite paths that do NOT already start with the proxy prefix and
197
- // that are simple absolute paths (start with `/` but not `//`).
198
- "function px(path){",
199
- "if(!path||path.charAt(0)!=='/'||path.charAt(1)==='/'||path.indexOf(P)===0||path.indexOf('/api/instances/')===0)return path;",
200
- "return P+path;",
201
- "}",
202
- "function str(u){return typeof u==='string'?u:(u&&typeof u.href==='string'?u.href:null);}",
203
- "function rw(u){",
204
- "var s0=str(u);if(!s0)return u;",
205
- "var direct=px(s0);",
206
- "if(direct!==s0)return direct;",
207
- "try{var p=new URL(s0,window.location.href);",
208
- "if(p.origin===O){var pp=px(p.pathname);if(pp!==p.pathname)return pp+p.search+p.hash;}",
209
- "}catch(_e){}",
210
- "return u;",
211
- "}",
212
- "function rwWs(u){",
213
- "var s0=str(u);if(!s0)return u;",
214
- "try{var p=new URL(s0,window.location.href);",
215
- "if(p.host===window.location.host&&(p.protocol==='ws:'||p.protocol==='wss:'||p.protocol===window.location.protocol)){",
216
- "var pp=px(p.pathname);",
217
- "if(pp!==p.pathname){var s=window.location.protocol==='https:'?'wss:':'ws:';return s+'//'+window.location.host+pp+p.search+p.hash;}",
218
- "}}catch(_e){}",
219
- "return u;",
220
- "}",
221
- // --- fetch() ---
222
- "var _f=window.fetch;",
223
- "window.fetch=function(r,o){",
224
- "if(typeof r==='string'||(r&&typeof r.href==='string'))r=rw(r);",
225
- "else if(r instanceof Request){",
226
- "var nr=rw(r.url);if(nr!==r.url)r=new Request(nr,r);}",
227
- "return _f.call(this,r,o);};",
228
- // --- XMLHttpRequest.open() ---
229
- "var _xo=XMLHttpRequest.prototype.open;",
230
- "XMLHttpRequest.prototype.open=function(m,u){",
231
- "arguments[1]=rw(u);",
232
- "return _xo.apply(this,arguments);};",
233
- // --- history.pushState / replaceState ---
234
- // SPA routers (SvelteKit, React Router, etc.) navigate via pushState.
235
- // Without this, pushing "/" lands on the panel's own SPA.
236
- "var _ps=history.pushState;",
237
- "var _rs=history.replaceState;",
238
- "history.pushState=function(s,t,u){",
239
- "if(typeof u==='string')u=rw(u);",
240
- "return _ps.call(this,s,t,u);};",
241
- "history.replaceState=function(s,t,u){",
242
- "if(typeof u==='string')u=rw(u);",
243
- "return _rs.call(this,s,t,u);};",
244
- // --- location.assign / location.replace ---
245
- "var _la=location.assign.bind(location);",
246
- "var _lr=location.replace.bind(location);",
247
- "location.assign=function(u){return _la(rw(u));};",
248
- "location.replace=function(u){return _lr(rw(u));};",
249
- // --- frame-busting defense ---
250
- // Embedded SPAs (e.g. WeKnora) frequently do
251
- // window.top.location.href = '/login'
252
- // when they see a 401, intending to log the user out. Inside our
253
- // capability proxy iframe `top` is the panel's main window — that
254
- // tears the user away from the instance detail page entirely.
255
- // Redirect `top`/`parent` to the iframe's own window so the
256
- // navigation stays inside the embed. Safe because the iframe IS
257
- // same-origin as the panel (our reverse proxy serves it from the
258
- // panel's host); cross-origin access would throw and fail closed.
259
- "try{",
260
- "Object.defineProperty(window,'top',{configurable:true,get:function(){return window;}});",
261
- "Object.defineProperty(window,'parent',{configurable:true,get:function(){return window;}});",
262
- "}catch(_e){}",
263
- // --- dynamic property assignment: img.src = '/static/...' ---
264
- "function patchProp(tag,prop){",
265
- "var d=Object.getOwnPropertyDescriptor(tag.prototype,prop);",
266
- "if(!d||!d.set)return;",
267
- "var orig=d.set;",
268
- "Object.defineProperty(tag.prototype,prop,{",
269
- "set:function(v){return orig.call(this,rw(v));},",
270
- "get:d.get,configurable:true,enumerable:true});",
271
- "}",
272
- "patchProp(HTMLImageElement,'src');",
273
- "patchProp(HTMLScriptElement,'src');",
274
- "patchProp(HTMLLinkElement,'href');",
275
- "patchProp(HTMLSourceElement,'src');",
276
- // --- Worker monkey-patch ---
277
- // Some SPA clients create socket.io/WebSocket connections from workers.
278
- // Patch same-origin workers so the same proxy-prefixing rule applies there.
279
- "var _Worker=window.Worker;",
280
- `var _workerPrelude=${workerPrelude};`,
281
- "function shouldWrapWorker(url){",
282
- "try{var p=new URL(String(url),window.location.href);return p.protocol==='blob:'||p.protocol==='data:'||p.origin===O;}catch(_e){return false;}",
283
- "}",
284
- "function wrapWorker(url,options){",
285
- "if(typeof _Worker!=='function'||!shouldWrapWorker(url))return url;",
286
- "try{var resolved=new URL(String(url),window.location.href).href;",
287
- "var isModule=!!(options&&options.type==='module');",
288
- "var workerEnv='var __capProxyHost='+JSON.stringify(window.location.host)+';var __capProxyProtocol='+JSON.stringify(window.location.protocol)+';';",
289
- "var source=isModule?workerEnv+_workerPrelude+'\\nimport '+JSON.stringify(resolved)+';':workerEnv+_workerPrelude+'\\nimportScripts('+JSON.stringify(resolved)+');';",
290
- "return URL.createObjectURL(new Blob([source],{type:'text/javascript'}));",
291
- "}catch(_e){return url;}",
292
- "}",
293
- "if(typeof _Worker==='function'){",
294
- "window.Worker=function(url,options){var wrapped=wrapWorker(url,options);return options!==undefined?new _Worker(wrapped,options):new _Worker(wrapped);};",
295
- "window.Worker.prototype=_Worker.prototype;",
296
- "}",
297
- // --- WebSocket ---
298
- // SPAs like OpenWebUI use socket.io over WebSocket. The socket.io client
299
- // builds ws:// URLs from window.location and the configured path; the URL
300
- // ends up pointing at the panel root (e.g. ws://panel:8090/ws/socket.io)
301
- // instead of the capability proxy. Without this patch the WS upgrade
302
- // request either gets destroyed (no route) or hits the wrong backend.
303
- "var _WS=window.WebSocket;",
304
- "if(typeof _WS==='function'){",
305
- "window.WebSocket=function(url,protocols){",
306
- "url=rwWs(url);",
307
- "return protocols!==undefined?new _WS(url,protocols):new _WS(url);",
308
- "};",
309
- "window.WebSocket.prototype=_WS.prototype;",
310
- "window.WebSocket.CONNECTING=_WS.CONNECTING;",
311
- "window.WebSocket.OPEN=_WS.OPEN;",
312
- "window.WebSocket.CLOSING=_WS.CLOSING;",
313
- "window.WebSocket.CLOSED=_WS.CLOSED;",
314
- "}",
315
- // --- EventSource ---
316
- // Some frameworks use SSE (Server-Sent Events) for real-time updates.
317
- "var _ES=window.EventSource;",
318
- "if(typeof _ES==='function'){",
319
- "window.EventSource=function(url,opts){",
320
- "if(typeof url==='string')url=rw(url);",
321
- "return new _ES(url,opts);",
322
- "};",
323
- "window.EventSource.prototype=_ES.prototype;",
324
- "window.EventSource.CONNECTING=_ES.CONNECTING;",
325
- "window.EventSource.OPEN=_ES.OPEN;",
326
- "window.EventSource.CLOSED=_ES.CLOSED;",
327
- "}",
328
- "})();</script>",
329
- ].join("");
330
- }
331
- function rewriteProxyTextBody(body, contentType, proxyBasePath, extraHeadHtml = "") {
332
- const value = (contentType ?? "").toLowerCase();
333
- const proxyBaseWithSlash = `${proxyBasePath.replace(/\/+$/, "")}/`;
334
- let rewritten = body;
335
- if (value.includes("text/html")) {
336
- // Rewrite asset URLs FIRST, then optionally inject a <base> tag.
337
- // Reversing the order would let the regex below match (and double-
338
- // prefix) the leading slash of the just-inserted `<base href="/api/...">`,
339
- // producing
340
- // <base href="/api/instances/X/provides/Y/api/instances/X/provides/Y/">
341
- // which then resolves every relative asset to a 404.
342
- rewritten = rewritten.replace(/((?:href|src|action|poster)=['"])\/(?!\/)/gi, `$1${proxyBaseWithSlash}`);
343
- // Rewrite dynamic import() paths inside inline <script> blocks so that
344
- // SvelteKit (and similar frameworks) resolve JS modules through the proxy.
345
- // Matches import("/_app/...") and import('/_app/...').
346
- rewritten = rewritten.replace(/\bimport\(\s*(['"])\/(?!\/)/g, `import($1${proxyBaseWithSlash}`);
347
- // Rewrite SvelteKit's client-side base path so that client-side routing
348
- // and subsequent chunk fetches go through the capability proxy path.
349
- // Older SvelteKit SSR output: __sveltekit_XXXXX = { base: "" };
350
- rewritten = rewritten.replace(/(__sveltekit_\w+\s*=\s*\{\s*base\s*:\s*)(["'])["']/, `$1$2${proxyBasePath.replace(/\/+$/, "")}$2`);
351
- // SvelteKit 2.x start() config: paths: { base: "", assets: "..." }
352
- rewritten = rewritten.replace(/(paths\s*:\s*\{\s*base\s*:\s*)(["'])["'](\s*,\s*assets\s*:)/, `$1$2${proxyBasePath.replace(/\/+$/, "")}$2$3`);
353
- if (!/<base\b/i.test(rewritten)) {
354
- if (/<head[^>]*>/i.test(rewritten)) {
355
- rewritten = rewritten.replace(/<head([^>]*)>/i, `<head$1><base href="${proxyBaseWithSlash}">`);
356
- }
357
- else {
358
- rewritten = `<base href="${proxyBaseWithSlash}">${rewritten}`;
359
- }
360
- }
361
- if (extraHeadHtml) {
362
- if (/<base\b/i.test(rewritten)) {
363
- rewritten = rewritten.replace(/<base\b[^>]*>/i, (match) => `${match}${extraHeadHtml}`);
364
- }
365
- else if (/<head[^>]*>/i.test(rewritten)) {
366
- rewritten = rewritten.replace(/<head([^>]*)>/i, `<head$1>${extraHeadHtml}`);
367
- }
368
- else {
369
- rewritten = `${extraHeadHtml}${rewritten}`;
370
- }
371
- }
372
- }
373
- if (value.includes("text/css")) {
374
- rewritten = rewritten.replace(/url\((['"]?)\/(?!\/)/gi, `url($1${proxyBaseWithSlash}`);
375
- }
376
- else if (value.includes("text/html")) {
377
- // HTML can contain inline scripts like new URL("/..."); only rewrite lowercase CSS url(...).
378
- rewritten = rewritten.replace(/url\((['"]?)\/(?!\/)/g, `url($1${proxyBaseWithSlash}`);
379
- }
380
- return rewritten;
381
- }
382
- function isReadableBody(value) {
383
- if (value instanceof Readable)
384
- return true;
385
- if (!value || typeof value !== "object")
386
- return false;
387
- const candidate = value;
388
- return (typeof candidate.pipe === "function" &&
389
- typeof candidate.on === "function" &&
390
- typeof candidate[Symbol.asyncIterator] === "function");
391
- }
392
- async function readProxyBodyStream(stream) {
393
- const chunks = [];
394
- for await (const chunk of stream) {
395
- if (typeof chunk === "string") {
396
- chunks.push(Buffer.from(chunk));
397
- }
398
- else if (chunk instanceof Uint8Array) {
399
- chunks.push(Buffer.from(chunk));
400
- }
401
- else {
402
- chunks.push(Buffer.from(String(chunk)));
403
- }
404
- }
405
- return Buffer.concat(chunks);
406
- }
407
- function bytesToArrayBuffer(bytes) {
408
- const out = new ArrayBuffer(bytes.byteLength);
409
- new Uint8Array(out).set(bytes);
410
- return out;
411
- }
412
- function isPlainRecord(value) {
413
- if (!value || typeof value !== "object")
414
- return false;
415
- const proto = Object.getPrototypeOf(value);
416
- return proto === Object.prototype || proto === null;
417
- }
418
- function encodeFormRecord(body) {
419
- const params = new URLSearchParams();
420
- for (const [key, value] of Object.entries(body)) {
421
- if (Array.isArray(value)) {
422
- for (const item of value)
423
- params.append(key, String(item));
424
- }
425
- else {
426
- params.append(key, String(value));
427
- }
428
- }
429
- return params.toString();
430
- }
431
- async function buildProxyRequestBody(req) {
432
- if (req.method === "GET" || req.method === "HEAD")
433
- return undefined;
434
- const body = req.body;
435
- if (body == null)
436
- return undefined;
437
- if (typeof body === "string") {
438
- return body;
439
- }
440
- if (body instanceof Uint8Array || Buffer.isBuffer(body))
441
- return bytesToArrayBuffer(body);
442
- if (isReadableBody(body)) {
443
- return bytesToArrayBuffer(await readProxyBodyStream(body));
444
- }
445
- const contentType = String(req.headers["content-type"] ?? "").toLowerCase();
446
- if (contentType.includes("application/x-www-form-urlencoded") && isPlainRecord(body)) {
447
- return encodeFormRecord(body);
448
- }
449
- if (contentType.includes("application/json") && isPlainRecord(body)) {
450
- return JSON.stringify(body);
451
- }
452
- if (isPlainRecord(body)) {
453
- return JSON.stringify(body);
454
- }
455
- return undefined;
456
- }
457
- function parseCommandLine(input) {
458
- const trimmed = input.trim();
459
- if (!trimmed)
460
- return [];
461
- const args = [];
462
- let current = "";
463
- let quote = null;
464
- let escaping = false;
465
- for (const char of trimmed) {
466
- if (escaping) {
467
- current += char;
468
- escaping = false;
469
- continue;
470
- }
471
- if (char === "\\") {
472
- escaping = true;
473
- continue;
474
- }
475
- if (quote) {
476
- if (char === quote) {
477
- quote = null;
478
- }
479
- else {
480
- current += char;
481
- }
482
- continue;
483
- }
484
- if (char === '"' || char === "'") {
485
- quote = char;
486
- continue;
487
- }
488
- if (/\s/.test(char)) {
489
- if (current) {
490
- args.push(current);
491
- current = "";
492
- }
493
- continue;
494
- }
495
- current += char;
496
- }
497
- if (escaping)
498
- current += "\\";
499
- if (quote)
500
- throw new Error("Command contains an unterminated quote");
501
- if (current)
502
- args.push(current);
503
- return args;
504
- }
505
- function isTerminalProvide(provide) {
506
- return !!provide && String(provide.protocol).toLowerCase() === "terminal" && !!provide.terminal;
507
- }
508
- function resolveTerminalProvide(instanceId, capability) {
509
- const provide = appService
510
- .getProvidedCapabilitiesForApp(instanceId)
511
- .find((entry) => entry.capability === capability);
512
- if (!provide)
513
- throw new Error(`Capability '${capability}' not found`);
514
- if (!isTerminalProvide(provide)) {
515
- throw new Error(`Capability '${capability}' is not a terminal provide`);
516
- }
517
- return provide;
518
- }
519
- function buildTerminalCommand(baseCommand, input) {
520
- if (!Array.isArray(baseCommand) || baseCommand.length === 0 || baseCommand.some((part) => typeof part !== "string" || !part.trim())) {
521
- throw new Error("Terminal provide is missing a valid base command");
522
- }
523
- const parsed = parseCommandLine(input);
524
- if (!parsed.length)
525
- throw new Error("Command cannot be empty");
526
- const baseName = baseCommand[0].split("/").pop() || baseCommand[0];
527
- const matchesBase = parsed.length >= baseCommand.length && baseCommand.every((part, index) => parsed[index] === part);
528
- const matchesBaseName = parsed[0] === baseName;
529
- if (matchesBase)
530
- return parsed;
531
- if (matchesBaseName)
532
- return [baseCommand[0], ...parsed.slice(1)];
533
- return [...baseCommand, ...parsed];
534
- }
535
- async function proxyProvidedCapability(req, reply) {
536
- const idErr = validateId(req.params.id);
537
- if (idErr)
538
- return reply.status(400).send({ detail: idErr });
539
- const rawInst = appService.getInstance(req.params.id);
540
- if (!rawInst)
541
- return reply.status(404).send({ detail: "Instance not found" });
542
- if (!appService.getInstanceInstallRecord(req.params.id))
543
- return reply.status(404).send({ detail: "App not found" });
544
- const capabilities = appService.getProvidedCapabilitiesForApp(req.params.id);
545
- const capability = capabilities.find((entry) => entry.capability === req.params.capability);
546
- if (!capability) {
547
- return reply.status(404).send({ detail: `Capability '${req.params.capability}' not found` });
548
- }
549
- if (capability.visibility === "internal") {
550
- return reply.status(403).send({ detail: `Capability '${req.params.capability}' is not externally accessible` });
551
- }
552
- if (capability.protocol !== "http" && capability.protocol !== "https") {
553
- return reply.status(400).send({ detail: `Capability '${req.params.capability}' does not use HTTP(S)` });
554
- }
555
- // Resolve only the canonical runtime hostPort. AppSpec declared ports are
556
- // template metadata; using them here would route users to the wrong
557
- // instance after reallocation or migration drift.
558
- let runtimePort;
559
- try {
560
- runtimePort = appService.resolveRuntimeCapabilityPort(req.params.id, req.params.capability) ?? undefined;
561
- }
562
- catch (error) {
563
- if (appService.isCanonicalRuntimePortRequiredError(error)) {
564
- return reply.status(error.statusCode || 409).send({ detail: error.message, code: error.code });
565
- }
566
- throw error;
567
- }
568
- if (typeof runtimePort !== "number" || runtimePort < 1) {
569
- return reply.status(500).send({ detail: `Capability '${req.params.capability}' has no resolved port` });
570
- }
571
- const upstreamHost = await appService.getHostForAppPort(req.params.id, runtimePort);
572
- const upstreamOrigin = `${capability.protocol}://${appService.urlHost(upstreamHost)}:${runtimePort}`;
573
- const wildcardSuffix = typeof req.params["*"] === "string" ? req.params["*"] : "";
574
- const proxyBasePath = capabilityProxyPath(req.params.id, req.params.capability);
575
- const querySuffix = req.raw.url?.includes("?") ? req.raw.url.slice(req.raw.url.indexOf("?")) : "";
576
- const requestPath = req.raw.url?.split("?")[0] ?? "";
577
- const canonicalProxyBase = canonicalCapabilityProxyBase(proxyBasePath, capability.path);
578
- if (!wildcardSuffix && canonicalProxyBase !== proxyBasePath && !requestPath.endsWith("/")) {
579
- reply.code(308).header("location", `${canonicalProxyBase}${querySuffix}`);
580
- return reply.send();
581
- }
582
- const upstreamPath = joinUpstreamPath(capability.path, wildcardSuffix);
583
- const targetUrl = `${upstreamOrigin}${upstreamPath}${querySuffix}`;
584
- const headers = new Headers();
585
- for (const [key, value] of Object.entries(req.headers)) {
586
- if (value == null)
587
- continue;
588
- const normalizedKey = key.toLowerCase();
589
- if (HOP_BY_HOP.has(normalizedKey) || normalizedKey === "host" || normalizedKey === "content-length" || normalizedKey === "accept-encoding") {
590
- continue;
591
- }
592
- // Strip panel session credentials to avoid leaking them upstream
593
- // (consistent with the WebSocket capability proxy in server.ts)
594
- if (normalizedKey === "authorization")
595
- continue;
596
- if (normalizedKey === "cookie") {
597
- const stripped = stripPanelSessionCookie(value);
598
- if (stripped)
599
- headers.set(key, stripped);
600
- continue;
601
- }
602
- if (Array.isArray(value)) {
603
- for (const item of value)
604
- headers.append(key, item);
605
- }
606
- else {
607
- headers.set(key, String(value));
608
- }
609
- }
610
- headers.set("accept-encoding", "identity");
611
- if (headers.has("origin"))
612
- headers.set("origin", upstreamOrigin);
613
- // `x-forwarded-prefix` is not a standard reverse-proxy header and some
614
- // upstream frameworks (notably SvelteKit apps like Hollama) treat it as a
615
- // deployment base path, which breaks `/_app/*` asset resolution under this
616
- // generic proxy. The HTML/base rewrite below already handles path prefixing.
617
- if (req.headers.host)
618
- headers.set("x-forwarded-host", String(req.headers.host));
619
- headers.set("x-forwarded-proto", req.protocol);
620
- const clientIp = typeof req.ip === "string" && req.ip
621
- ? req.ip
622
- : typeof req.raw?.socket?.remoteAddress === "string"
623
- ? req.raw.socket.remoteAddress
624
- : "";
625
- if (clientIp) {
626
- const forwardedFor = headers.get("x-forwarded-for");
627
- headers.set("x-forwarded-for", forwardedFor ? `${forwardedFor}, ${clientIp}` : clientIp);
628
- headers.set("x-real-ip", clientIp);
629
- }
630
- // Intercept service worker scripts BEFORE talking to upstream. SPAs like
631
- // OpenWebUI register a SvelteKit service worker that aggressively caches
632
- // HTML/JS at the proxy origin; once installed, the SW serves stale bodies
633
- // and the page never receives our latest bootstrap (WebSocket / fetch
634
- // monkey-patch). We replace the SW body with a self-unregistration stub so
635
- // the next browser update cycle removes the offending worker and restores
636
- // network-backed loading.
637
- if (/(?:^|\/)(?:service-worker|sw)\.js$/i.test(requestPath)) {
638
- reply
639
- .code(200)
640
- .header("content-type", "application/javascript; charset=utf-8")
641
- .header("cache-control", "no-store, no-cache, must-revalidate, max-age=0")
642
- .header("pragma", "no-cache")
643
- .header("service-worker-allowed", "/");
644
- return reply.send("// Capability proxy: service worker intentionally disabled.\n" +
645
- "self.addEventListener('install',function(e){self.skipWaiting()});\n" +
646
- "self.addEventListener('activate',function(e){\n" +
647
- " e.waitUntil((async function(){\n" +
648
- " try{var cs=await caches.keys();for(var i=0;i<cs.length;i++){try{await caches.delete(cs[i])}catch(_e){}}}catch(_e){}\n" +
649
- " try{var clients=await self.clients.matchAll({includeUncontrolled:true});clients.forEach(function(c){try{c.navigate(c.url)}catch(_e){}})}catch(_e){}\n" +
650
- " try{await self.registration.unregister()}catch(_e){}\n" +
651
- " })());\n" +
652
- "});\n");
653
- }
654
- // Single AbortController so we can cancel the upstream when the client
655
- // disconnects. AbortSignal.timeout() only limits connection establishment;
656
- // long-poll/SSE bodies (e.g. socket.io) would otherwise pin the fetch
657
- // promise indefinitely and starve the event loop.
658
- const upstreamAbort = new AbortController();
659
- const connectTimer = setTimeout(() => upstreamAbort.abort(new Error("upstream connect timeout")), 30_000);
660
- const onClientClose = () => {
661
- if (!reply.raw.writableEnded)
662
- upstreamAbort.abort(new Error("client disconnected"));
663
- };
664
- reply.raw.once("close", onClientClose);
665
- try {
666
- console.error("[cap-proxy] PRE buildBody method=", req.method, "bodyType=", typeof req.body, req.body?.constructor?.name, "signalAborted=", upstreamAbort.signal.aborted);
667
- const requestBody = await buildProxyRequestBody(req);
668
- console.error("[cap-proxy] POST buildBody size=", requestBody instanceof ArrayBuffer ? requestBody.byteLength : typeof requestBody === "string" ? requestBody.length : "undef", "signalAborted=", upstreamAbort.signal.aborted, "reason=", upstreamAbort.signal.reason?.message);
669
- const upstream = await fetch(targetUrl, {
670
- method: req.method,
671
- headers,
672
- body: requestBody,
673
- redirect: "manual",
674
- signal: upstreamAbort.signal,
675
- }).finally(() => clearTimeout(connectTimer));
676
- console.error("[cap-proxy] POST fetch status=", upstream.status);
677
- const upstreamContentType = upstream.headers.get("content-type");
678
- const willRewriteBody = shouldRewriteProxyResponse(upstreamContentType);
679
- const willInjectHtml = (upstreamContentType ?? "").toLowerCase().includes("text/html");
680
- reply.code(upstream.status);
681
- upstream.headers.forEach((value, key) => {
682
- const normalizedKey = key.toLowerCase();
683
- if (HOP_BY_HOP.has(normalizedKey) || normalizedKey === "content-length" || normalizedKey === "content-encoding") {
684
- return;
685
- }
686
- // When we rewrite the response body (HTML/CSS/JS), the upstream ETag /
687
- // Cache-Control values describe the *original* upstream bytes — but the
688
- // body the browser receives is post-rewrite (proxy-prefixed paths, JS
689
- // hard-coded redirect targets, etc.). Honoring the upstream cache hints
690
- // lets the browser pin a stale rewrite indefinitely: e.g. an early
691
- // visit that pre-dated the JS rewrite gets cached and survives across
692
- // app restarts, breaking the auth redirect logic until a hard refresh.
693
- // Strip cache validators and force revalidation on every load.
694
- if (willRewriteBody && (normalizedKey === "cache-control" ||
695
- normalizedKey === "etag" ||
696
- normalizedKey === "last-modified" ||
697
- normalizedKey === "expires" ||
698
- normalizedKey === "pragma")) {
699
- return;
700
- }
701
- if (willInjectHtml && (normalizedKey === "content-security-policy" ||
702
- normalizedKey === "content-security-policy-report-only" ||
703
- normalizedKey === "x-frame-options")) {
704
- return;
705
- }
706
- if (normalizedKey === "location") {
707
- if (value.startsWith("/")) {
708
- reply.header(key, rewriteCapabilityLocation(proxyBasePath, canonicalProxyBase, value));
709
- return;
710
- }
711
- try {
712
- const parsed = new URL(value);
713
- const upstreamBase = new URL(upstreamOrigin);
714
- if (parsed.origin === upstreamBase.origin) {
715
- reply.header(key, rewriteCapabilityLocation(proxyBasePath, canonicalProxyBase, parsed.pathname, parsed.search, parsed.hash));
716
- return;
717
- }
718
- }
719
- catch {
720
- // fall through to raw location header
721
- }
722
- }
723
- reply.header(key, value);
724
- });
725
- if (req.method === "HEAD") {
726
- reply.raw.off("close", onClientClose);
727
- return reply.send();
728
- }
729
- if (willRewriteBody) {
730
- // Pair with the cache-validator strip above.
731
- reply.header("cache-control", "no-cache, no-store, must-revalidate");
732
- reply.header("pragma", "no-cache");
733
- reply.header("expires", "0");
734
- let extraHeadHtml = "";
735
- const htmlRewriter = getCapabilityHtmlRewriter(capability);
736
- extraHeadHtml += (await htmlRewriter?.buildHeadHtml({
737
- instanceId: req.params.id,
738
- capability,
739
- upstreamOrigin,
740
- signal: upstreamAbort.signal,
741
- willInjectHtml,
742
- })) ?? "";
743
- // Inject a generic fetch/XHR monkey-patch for all capability-proxied
744
- // HTML pages. SPA frameworks like SvelteKit compile absolute API paths
745
- // (e.g. `/api/v1/...`, `/ollama/...`) into JS bundles at build time.
746
- // When the page is served under the proxy path those requests bypass
747
- // the proxy and hit the panel's own `/api/` routes instead. The patch
748
- // intercepts fetch() and XMLHttpRequest.open() and rewrites same-origin
749
- // absolute paths that do NOT already start with the proxy prefix.
750
- extraHeadHtml += capabilityProxyBootstrap(proxyBasePath);
751
- const rawBody = await upstream.text();
752
- // First-visit/content-change cleanup: if the browser still has a stale
753
- // ServiceWorker registered from an earlier panel build (which would
754
- // intercept this navigation and serve cached HTML *without* the
755
- // bootstrap patches), emit Clear-Site-Data so the browser drops the SW
756
- // + its cache and reloads through the proxy. We mark the success with a
757
- // long-lived cookie scoped to the proxy path to avoid a reload loop.
758
- // The cookie value is tied to the upstream HTML bytes and validators,
759
- // not a fixed boolean, so same-tag image rebuilds such as jishu-kb:0.1.1
760
- // can still trigger one fresh cleanup after the embedded UI changes.
761
- // Gate to HTML only — JS/CSS sub-resources also flow through this branch
762
- // now that we rewrite JS bundles, and emitting Clear-Site-Data on a JS
763
- // response would clear storage mid-page-load.
764
- if (willInjectHtml) {
765
- const cleanupToken = capabilityProxyCleanupToken([
766
- req.params.id,
767
- req.params.capability,
768
- upstreamOrigin,
769
- upstreamContentType,
770
- upstream.headers.get("etag"),
771
- upstream.headers.get("last-modified"),
772
- upstream.headers.get("content-length"),
773
- rawBody,
774
- ]);
775
- const cookieHeader = (req.headers.cookie || "").toString();
776
- const swCleaned = hasCookieValue(cookieHeader, "cap_proxy_sw_clean", cleanupToken);
777
- if (!swCleaned) {
778
- reply.header("Clear-Site-Data", '"cache", "storage"');
779
- reply.header("Set-Cookie", `cap_proxy_sw_clean=${cleanupToken}; Path=${proxyBasePath}; Max-Age=2592000; SameSite=Lax`);
780
- }
781
- }
782
- reply.raw.off("close", onClientClose);
783
- const rewritten = rewriteProxyTextBody(rawBody, upstreamContentType, proxyBasePath, extraHeadHtml);
784
- return reply.send(rewritten);
785
- }
786
- if (!upstream.body) {
787
- reply.raw.off("close", onClientClose);
788
- return reply.send();
789
- }
790
- const readable = Readable.fromWeb(upstream.body);
791
- readable.once("close", () => reply.raw.off("close", onClientClose));
792
- return reply.send(readable);
793
- }
794
- catch (error) {
795
- console.error("[cap-proxy] CATCH", error?.constructor?.name, error?.name, error?.message, "signalAborted=", upstreamAbort.signal.aborted, "reason=", upstreamAbort.signal.reason?.message, "stack=", error?.stack?.split("\n").slice(0, 4).join(" | "));
796
- reply.raw.off("close", onClientClose);
797
- return reply.status(502).send({ detail: error?.message || `Failed to proxy capability '${req.params.capability}'` });
798
- }
799
- }
800
- // Resolve service manager once at route registration, re-resolve on config change
801
- let _svc = null;
802
- let _svcType = "";
803
- export async function getSvc() {
804
- const currentType = getServiceManagerType();
805
- if (_svc && _svcType === currentType)
806
- return _svc;
807
- if (currentType !== "nomad") {
808
- // Step 13: process-manager / host-mode legacy runtime is unsupported.
809
- throw new Error(`service_manager='${currentType}' is no longer supported. ` +
810
- "Only 'nomad' runtimes are accepted on the runtime path. " +
811
- "Run `jishushell migrate legacy` to convert legacy instances.");
812
- }
813
- _svc = await import("../services/runtime/drivers/nomad.js");
814
- _svcType = currentType;
815
- return _svc;
816
- }
13
+ import { proxyProvidedCapability } from "../services/capability-proxy/http.js";
14
+ import { sendTerminalCapabilityInput, startTerminalCapabilitySession, stopTerminalCapabilitySession, streamTerminalCapabilitySession, } from "../services/capability-proxy/terminal.js";
15
+ import { inferRequestOrigin } from "../services/http/request-utils.js";
16
+ import { getCachedStatus } from "../services/instances/status.js";
17
+ import { approvePairingRequest, isInstancePairingError, listPairingRequests, } from "../services/instances/pairing.js";
18
+ // Generic instance API only. Product-specific HTTP behavior belongs in
19
+ // integrations/<kind> or app-modules/<app>, reached through explicit hooks.
817
20
  function getLifecycleActionSource(req) {
818
21
  const raw = req.headers["x-jishushell-action-source"];
819
22
  const value = Array.isArray(raw) ? raw[0] : raw;
820
23
  const source = typeof value === "string" ? value.trim() : "";
821
24
  return /^[a-z0-9._:-]{1,80}$/i.test(source) ? source : "unknown";
822
25
  }
823
- export async function getCachedStatus(_svc, instanceId, reason) {
824
- const snapshot = await refreshInstanceStatus(instanceId, {
825
- reason,
826
- force: true,
827
- minIntervalMs: 0,
828
- });
829
- return snapshot?.service ?? { status: "unknown", pid: null, uptime: null, memory_mb: null, cpu_percent: null };
830
- }
831
26
  export async function instanceRoutes(app) {
832
27
  // List
833
28
  app.get("/api/instances", async () => {
834
- const svc = await getSvc();
835
29
  const instances = appService.listInstances();
836
- const statuses = await Promise.all(instances.map(inst => getCachedStatus(svc, inst.id, "api-list").catch(() => ({ status: "unknown", pid: null, uptime: null, memory_mb: null, cpu_percent: null }))));
30
+ const statuses = await Promise.all(instances.map(inst => getCachedStatus(inst.id, "api-list").catch(() => ({ status: "unknown", pid: null, uptime: null, memory_mb: null, cpu_percent: null }))));
837
31
  return Promise.all(instances.map(async (inst, i) => ({
838
32
  ...(await augmentInstanceMetadata(inst.id, inst)),
839
33
  service: statuses[i],
@@ -845,7 +39,7 @@ export async function instanceRoutes(app) {
845
39
  return await createInstance(req.body);
846
40
  }
847
41
  catch (e) {
848
- // Structured rejection from createHermesInstance — return 409 with code
42
+ // Structured rejection from integration create policy — return 409 with code
849
43
  if (e && e.name === "InstanceCreationRejected") {
850
44
  return reply.status(409).send({
851
45
  detail: e.hint,
@@ -901,11 +95,10 @@ export async function instanceRoutes(app) {
901
95
  const idErr = validateId(req.params.id);
902
96
  if (idErr)
903
97
  return reply.status(400).send({ detail: idErr });
904
- const svc = await getSvc();
905
98
  const inst = appService.getInstance(req.params.id);
906
99
  if (!inst)
907
100
  return reply.status(404).send({ detail: "Instance not found" });
908
- const status = await getCachedStatus(svc, req.params.id, "api-detail");
101
+ const status = await getCachedStatus(req.params.id, "api-detail");
909
102
  return {
910
103
  ...(await augmentInstanceMetadata(req.params.id, inst)),
911
104
  service: status,
@@ -1135,36 +328,19 @@ export async function instanceRoutes(app) {
1135
328
  const idErr = validateId(req.params.id);
1136
329
  if (idErr)
1137
330
  return reply.status(400).send({ detail: idErr });
1138
- const inst = appService.getInstance(req.params.id);
1139
- if (!inst) {
1140
- return reply.status(404).send({ detail: "Instance not found" });
1141
- }
1142
- const capabilities = await getInstanceCapabilities(req.params.id, inst);
1143
- if (!capabilities.pairing.list) {
1144
- return reply.status(501).send({ detail: "Pairing list is not supported for this runtime" });
331
+ try {
332
+ return await listPairingRequests(req.params.id);
1145
333
  }
1146
- const integrationKind = resolvePrimaryIntegrationKind(inst);
1147
- if (!integrationKind) {
1148
- return reply.status(501).send({ detail: "Pairing list is not supported for this runtime" });
334
+ catch (error) {
335
+ if (isInstancePairingError(error))
336
+ return reply.status(error.statusCode).send({ detail: error.message });
337
+ throw error;
1149
338
  }
1150
- const svc = await getSvc();
1151
- // Pure integration dispatch — no hardcoded kind fallback.
1152
- const cmd = await getIntegration(integrationKind).buildPairingListCommand(req.params.id);
1153
- const result = await svc.exec(req.params.id, cmd, 15_000);
1154
- return { output: result.stdout + result.stderr, exitCode: result.exitCode };
1155
339
  });
1156
340
  app.post("/api/instances/:id/pairing/approve", async (req, reply) => {
1157
341
  const idErr = validateId(req.params.id);
1158
342
  if (idErr)
1159
343
  return reply.status(400).send({ detail: idErr });
1160
- const inst = appService.getInstance(req.params.id);
1161
- if (!inst) {
1162
- return reply.status(404).send({ detail: "Instance not found" });
1163
- }
1164
- const capabilities = await getInstanceCapabilities(req.params.id, inst);
1165
- if (!capabilities.pairing.approve) {
1166
- return reply.status(501).send({ detail: "Pairing approve is not supported for this runtime" });
1167
- }
1168
344
  const { channel, code, notify } = req.body ?? {};
1169
345
  if (!channel || !PAIRING_CHANNEL_RE.test(channel)) {
1170
346
  return reply.status(400).send({ detail: "Invalid channel: must be lowercase alphanumeric/hyphen/underscore" });
@@ -1172,29 +348,23 @@ export async function instanceRoutes(app) {
1172
348
  if (!code || !PAIRING_CODE_RE.test(code)) {
1173
349
  return reply.status(400).send({ detail: "Invalid pairing code: must be 4-16 uppercase alphanumeric characters" });
1174
350
  }
1175
- const integrationKind = resolvePrimaryIntegrationKind(inst);
1176
- if (!integrationKind) {
1177
- return reply.status(501).send({ detail: "Pairing approve is not supported for this runtime" });
351
+ try {
352
+ return await approvePairingRequest(req.params.id, { channel, code, notify });
1178
353
  }
1179
- const cmd = await getIntegration(integrationKind).buildPairingApproveCommand(req.params.id, {
1180
- channel,
1181
- code,
1182
- notify,
1183
- });
1184
- const svc = await getSvc();
1185
- const result = await svc.exec(req.params.id, cmd, 15_000);
1186
- if (result.exitCode !== 0) {
1187
- return reply.status(400).send({ detail: (result.stderr || result.stdout || "Approval failed").trim() });
354
+ catch (error) {
355
+ if (isInstancePairingError(error))
356
+ return reply.status(error.statusCode).send({ detail: error.message });
357
+ throw error;
1188
358
  }
1189
- return { ok: true, output: (result.stdout + result.stderr).trim() };
1190
359
  });
1191
- // Integration chat (inline chat panel for runtimes that expose an OpenAI-compat
1192
- // HTTP chat completion endpoint and declare chatPanel="inline" in their
1193
- // capability profile currently only Hermes).
360
+ // Inline Chat surface for runtimes that choose the JishuShell-rendered Chat
361
+ // tab instead of an iframe-backed app UI. The runtime declares how Core can
362
+ // reach its chat endpoint via `inlineChatDescriptor`; this route owns only
363
+ // the shared forwarding/SSE wrapper.
1194
364
  //
1195
- // Flow: panel JWT auth read per-instance API_SERVER_KEY from integration-home/.env →
1196
- // read allocated host port from runtime.ports → POST forward to
1197
- // http://127.0.0.1:<port>/v1/chat/completions.
365
+ // Flow: panel JWT auth -> read the declared per-instance secret from
366
+ // integration-home/.env -> read allocated host port from runtime.ports ->
367
+ // POST forward to the declared runtime chat endpoint.
1198
368
  //
1199
369
  // The response is framed as Server-Sent Events with periodic `: ping`
1200
370
  // heartbeats while we wait for the agent to finish. Long-running agent
@@ -1206,9 +376,8 @@ export async function instanceRoutes(app) {
1206
376
  // Errors before headers are sent fall back to HTTP 5xx JSON; errors
1207
377
  // after hijack go out as an `event: error` SSE payload.
1208
378
  //
1209
- // This is a thin server-side forwarder, NOT a new LLM proxy. The actual
1210
- // LLM call still goes through Hermes jsproxy JishuShell /proxy/v1
1211
- // upstream provider.
379
+ // This is a thin server-side forwarder, NOT a new LLM proxy. Managed model
380
+ // traffic still goes through the runtime's configured Core proxy binding.
1212
381
  app.post("/api/instances/:id/agent/chat", async (req, reply) => {
1213
382
  const idErr = validateId(req.params.id);
1214
383
  if (idErr)
@@ -1228,10 +397,9 @@ export async function instanceRoutes(app) {
1228
397
  detail: `Runtime "${integrationKind ?? "generic"}" does not support inline chat (chatPanel=${chatPanel})`,
1229
398
  });
1230
399
  }
1231
- // Integration-owned dispatch: the route no longer hardcodes Hermes's env
1232
- // var name or endpoint path. Any integration that declares chatPanel
1233
- // "inline" MUST also supply `inlineChatDescriptor` (api key env var +
1234
- // optional path/header/timeout) so this forwarder can reach its agent.
400
+ // Integration-owned dispatch: any integration that declares chatPanel
401
+ // "inline" MUST also supply `inlineChatDescriptor` (secret env var plus
402
+ // optional path/header/timeout) so this forwarder can reach its runtime.
1235
403
  if (!integrationKind) {
1236
404
  return reply.status(500).send({
1237
405
  detail: `Instance "${req.params.id}" has no integration identity but declared chatPanel=inline`,
@@ -1257,8 +425,8 @@ export async function instanceRoutes(app) {
1257
425
  }
1258
426
  throw error;
1259
427
  }
1260
- // Agent API key lives in the integration-managed secretEnv file. Integration
1261
- // declares which env var holds it (Hermes → API_SERVER_KEY).
428
+ // The runtime API key lives in the integration-managed secretEnv file.
429
+ // Integration declares which env var holds it.
1262
430
  const secretEnv = inst?.paths?.secretEnv;
1263
431
  if (!secretEnv) {
1264
432
  return reply.status(500).send({ detail: "Instance has no secretEnv path" });
@@ -1273,7 +441,7 @@ export async function instanceRoutes(app) {
1273
441
  const endpointPath = desc.endpointPath ?? "/v1/chat/completions";
1274
442
  const authHeader = desc.authHeader ?? "Authorization";
1275
443
  const authScheme = desc.authScheme ?? "Bearer ";
1276
- // Upstream budget: the Hermes call itself still gets a hard ceiling so
444
+ // Upstream budget: the runtime call still gets a hard ceiling so
1277
445
  // a wedged container can't hold the connection forever. Integration can
1278
446
  // extend this via inlineChatDescriptor.timeoutMs; default is 30 min
1279
447
  // which comfortably covers multi-step tool loops.
@@ -1346,101 +514,16 @@ export async function instanceRoutes(app) {
1346
514
  }
1347
515
  });
1348
516
  app.post("/api/instances/:id/provides/:capability/terminal/session", async (req, reply) => {
1349
- const idErr = validateId(req.params.id);
1350
- if (idErr)
1351
- return reply.status(400).send({ detail: idErr });
1352
- if (!appService.getInstance(req.params.id)) {
1353
- return reply.status(404).send({ detail: "Instance not found" });
1354
- }
1355
- try {
1356
- const provide = resolveTerminalProvide(req.params.id, req.params.capability);
1357
- const terminal = provide.terminal;
1358
- const input = typeof req.body?.input === "string" ? req.body.input : "";
1359
- const command = buildTerminalCommand(terminal.command, input);
1360
- const session = startTerminalSession({
1361
- instanceId: req.params.id,
1362
- capability: req.params.capability,
1363
- terminal,
1364
- command,
1365
- });
1366
- return reply.send(session);
1367
- }
1368
- catch (error) {
1369
- return reply.status(400).send({ detail: error?.message || "Failed to start terminal session" });
1370
- }
517
+ return startTerminalCapabilitySession(req, reply);
1371
518
  });
1372
519
  app.get("/api/instances/:id/provides/:capability/terminal/session/:sessionId/stream", async (req, reply) => {
1373
- const idErr = validateId(req.params.id);
1374
- if (idErr)
1375
- return reply.status(400).send({ detail: idErr });
1376
- if (!assertTerminalSessionOwner(req.params.sessionId, req.params.id, req.params.capability)) {
1377
- return reply.status(404).send({ detail: "Terminal session not found" });
1378
- }
1379
- const session = getTerminalSession(req.params.sessionId);
1380
- if (!session)
1381
- return reply.status(404).send({ detail: "Terminal session not found" });
1382
- const since = Math.max(parseInt(req.query.since || "0", 10) || 0, 0);
1383
- reply.hijack();
1384
- const raw = reply.raw;
1385
- raw.writeHead(200, {
1386
- "Content-Type": "text/event-stream; charset=utf-8",
1387
- "Cache-Control": "no-cache, no-transform",
1388
- "Connection": "keep-alive",
1389
- "X-Accel-Buffering": "no",
1390
- });
1391
- const writeEvent = (event, data) => {
1392
- raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
1393
- };
1394
- for (const event of getTerminalSessionEvents(req.params.sessionId, since)) {
1395
- writeEvent(event.type, event);
1396
- }
1397
- if (!session.running) {
1398
- writeEvent("done", { sessionId: req.params.sessionId });
1399
- raw.end();
1400
- return;
1401
- }
1402
- const unsubscribe = subscribeTerminalSession(req.params.sessionId, (event) => {
1403
- writeEvent(event.type, event);
1404
- if (event.type === "exit" || event.type === "error") {
1405
- writeEvent("done", { sessionId: req.params.sessionId });
1406
- unsubscribe?.();
1407
- raw.end();
1408
- }
1409
- });
1410
- req.raw.on("close", () => {
1411
- unsubscribe?.();
1412
- });
1413
- raw.write(": ping\n\n");
520
+ return streamTerminalCapabilitySession(req, reply);
1414
521
  });
1415
522
  app.post("/api/instances/:id/provides/:capability/terminal/session/:sessionId/input", async (req, reply) => {
1416
- const idErr = validateId(req.params.id);
1417
- if (idErr)
1418
- return reply.status(400).send({ detail: idErr });
1419
- if (!assertTerminalSessionOwner(req.params.sessionId, req.params.id, req.params.capability)) {
1420
- return reply.status(404).send({ detail: "Terminal session not found" });
1421
- }
1422
- try {
1423
- sendTerminalSessionInput(req.params.sessionId, typeof req.body?.input === "string" ? req.body.input : "");
1424
- return reply.send({ ok: true });
1425
- }
1426
- catch (error) {
1427
- return reply.status(400).send({ detail: error?.message || "Failed to send terminal input" });
1428
- }
523
+ return sendTerminalCapabilityInput(req, reply);
1429
524
  });
1430
525
  app.post("/api/instances/:id/provides/:capability/terminal/session/:sessionId/stop", async (req, reply) => {
1431
- const idErr = validateId(req.params.id);
1432
- if (idErr)
1433
- return reply.status(400).send({ detail: idErr });
1434
- if (!assertTerminalSessionOwner(req.params.sessionId, req.params.id, req.params.capability)) {
1435
- return reply.status(404).send({ detail: "Terminal session not found" });
1436
- }
1437
- try {
1438
- stopTerminalSession(req.params.sessionId);
1439
- return reply.send({ ok: true });
1440
- }
1441
- catch (error) {
1442
- return reply.status(400).send({ detail: error?.message || "Failed to stop terminal session" });
1443
- }
526
+ return stopTerminalCapabilitySession(req, reply);
1444
527
  });
1445
528
  app.all("/api/instances/:id/provides/:capability", async (req, reply) => proxyProvidedCapability(req, reply));
1446
529
  app.all("/api/instances/:id/provides/:capability/*", async (req, reply) => proxyProvidedCapability(req, reply));
@@ -1461,128 +544,6 @@ export async function instanceRoutes(app) {
1461
544
  }
1462
545
  return { lines: logLines };
1463
546
  });
1464
- // Admin: re-encrypt all instance secrets with current AES key
1465
- app.post("/api/admin/migrate-secrets", async (_req, _reply) => {
1466
- const { getAesKey, getJwtSecret } = await import("../config.js");
1467
- const { scryptSync, createDecipheriv, createCipheriv, randomBytes } = await import("crypto");
1468
- const { readFileSync, existsSync: fsExistsSync } = await import("fs");
1469
- const instances = appService.listInstances();
1470
- const results = [];
1471
- const currentKey = getAesKey();
1472
- const legacyKey = scryptSync(getJwtSecret(), "jishushell-apikey-v1", 32);
1473
- for (const inst of instances) {
1474
- const envFiles = appService.getRuntimeEnvFiles(inst.id);
1475
- const providerEnvFile = envFiles[0]?.replace(/model\.env$/, "provider.env");
1476
- if (!providerEnvFile || !fsExistsSync(providerEnvFile)) {
1477
- results.push({ id: inst.id, status: "skipped", error: "no provider.env" });
1478
- continue;
1479
- }
1480
- const envContent = readFileSync(providerEnvFile, "utf-8");
1481
- const match = envContent.match(/UPSTREAM_API_KEY=(.+)/);
1482
- if (!match || !match[1]?.startsWith("enc:")) {
1483
- results.push({ id: inst.id, status: "skipped", error: "no encrypted key" });
1484
- continue;
1485
- }
1486
- const encrypted = match[1];
1487
- const raw = Buffer.from(encrypted.slice(4), "base64");
1488
- if (raw.length < 29) {
1489
- results.push({ id: inst.id, status: "error", error: "encrypted data too short" });
1490
- continue;
1491
- }
1492
- const iv = raw.subarray(0, 12);
1493
- const tag = raw.subarray(12, 28);
1494
- const ciphertext = raw.subarray(28);
1495
- // Try decrypt with current key first
1496
- let plaintext = null;
1497
- let needsReEncrypt = false;
1498
- try {
1499
- const d = createDecipheriv("aes-256-gcm", currentKey, iv);
1500
- d.setAuthTag(tag);
1501
- plaintext = d.update(ciphertext, undefined, "utf-8") + d.final("utf-8");
1502
- // Already encrypted with current key — no migration needed
1503
- results.push({ id: inst.id, status: "ok" });
1504
- continue;
1505
- }
1506
- catch {
1507
- // Current key failed — try legacy
1508
- needsReEncrypt = true;
1509
- }
1510
- if (needsReEncrypt) {
1511
- try {
1512
- const d = createDecipheriv("aes-256-gcm", legacyKey, iv);
1513
- d.setAuthTag(tag);
1514
- plaintext = d.update(ciphertext, undefined, "utf-8") + d.final("utf-8");
1515
- }
1516
- catch {
1517
- results.push({ id: inst.id, status: "error", error: "decrypt failed with both keys" });
1518
- continue;
1519
- }
1520
- // Re-encrypt with current key
1521
- const newIv = randomBytes(12);
1522
- const c = createCipheriv("aes-256-gcm", currentKey, newIv);
1523
- const enc = Buffer.concat([c.update(plaintext, "utf-8"), c.final()]);
1524
- const newTag = c.getAuthTag();
1525
- const newEncrypted = "enc:" + Buffer.concat([newIv, newTag, enc]).toString("base64");
1526
- // Write back
1527
- const newContent = envContent.replace(/UPSTREAM_API_KEY=.+/, `UPSTREAM_API_KEY=${newEncrypted}`);
1528
- writeSecretFile(providerEnvFile, newContent);
1529
- results.push({ id: inst.id, status: "migrated" });
1530
- }
1531
- }
1532
- // Invalidate proxy config cache for migrated instances
1533
- for (const r of results) {
1534
- if (r.status === "migrated")
1535
- invalidateConfigCache(r.id);
1536
- }
1537
- return { ok: true, results };
1538
- });
1539
- // ── Docker image check & pull ───────────────────────────────────────────
1540
- // Generic Docker operations used by NewInstance UI to verify / pull runtime
1541
- // images before creating an instance. Framework-level (not integration-scoped)
1542
- // because every container runtime shares the same docker CLI here.
1543
- app.get("/api/docker/image-check", async (req, reply) => {
1544
- const image = req.query.image;
1545
- if (!image || typeof image !== "string") {
1546
- return reply.status(400).send({ detail: "Missing 'image' query parameter" });
1547
- }
1548
- // Validate image name to prevent command injection
1549
- if (!/^[a-zA-Z0-9][a-zA-Z0-9\-_.:/@]*$/.test(image) || image.length > 256) {
1550
- return reply.status(400).send({ detail: "Invalid Docker image name" });
1551
- }
1552
- const { inspectDockerImage, toDockerInspectUserError } = await import("../utils/docker-inspect.js");
1553
- const result = inspectDockerImage(image, { timeout: 10000 });
1554
- if (result.ok) {
1555
- return { exists: true, image };
1556
- }
1557
- if (result.reason === "not_found")
1558
- return { exists: false, image };
1559
- const inspectError = toDockerInspectUserError(image, result);
1560
- return reply.status(inspectError.statusCode).send({
1561
- detail: inspectError.message,
1562
- code: inspectError.code,
1563
- exists: false,
1564
- image,
1565
- });
1566
- });
1567
- app.post("/api/docker/image-pull", async (req, reply) => {
1568
- const image = req.body?.image;
1569
- if (!image || typeof image !== "string") {
1570
- return reply.status(400).send({ detail: "Missing 'image' in request body" });
1571
- }
1572
- if (!/^[a-zA-Z0-9][a-zA-Z0-9\-_.:/@]*$/.test(image) || image.length > 256) {
1573
- return reply.status(400).send({ detail: "Invalid Docker image name" });
1574
- }
1575
- try {
1576
- const { execFile } = await import("child_process");
1577
- const { promisify } = await import("util");
1578
- const execFileAsync = promisify(execFile);
1579
- await execFileAsync("docker", ["pull", image], { timeout: 600_000 });
1580
- return { ok: true, image };
1581
- }
1582
- catch (e) {
1583
- return reply.status(500).send({ detail: `Failed to pull image: ${e.message}` });
1584
- }
1585
- });
1586
547
  // ── Connections REST API ────────────────────────────────────────────────
1587
548
  /** GET /api/instances/:id/connections — view spec.requires + bindings. */
1588
549
  app.get("/api/instances/:id/connections", async (req, reply) => {
@@ -1632,106 +593,5 @@ export async function instanceRoutes(app) {
1632
593
  return { state: "error", unboundRequired: [], unboundOptional: [], bindable: 0, error: e.message };
1633
594
  }
1634
595
  });
1635
- // ── Suggestions API (PR 6) ─────────────────────────────────────────────
1636
- app.get("/api/suggestions", async () => {
1637
- const { computeSuggestions } = await import("../services/connections/suggestions.js");
1638
- return { suggestions: computeSuggestions() };
1639
- });
1640
- app.post("/api/suggestions/:id/apply", async (req, reply) => {
1641
- const { computeSuggestions } = await import("../services/connections/suggestions.js");
1642
- const all = computeSuggestions();
1643
- const target = all.find((s) => s.id === req.params.id);
1644
- if (!target) {
1645
- return reply.status(404).send({ detail: "Suggestion no longer applies" });
1646
- }
1647
- // Apply by issuing the equivalent PUT /connections — read current
1648
- // bindings, splice in the new one for `slot`, persist via the
1649
- // transactor.
1650
- const meta = appService.getInstance(target.consumerInstanceId);
1651
- if (!meta)
1652
- return reply.status(404).send({ detail: "Consumer instance not found" });
1653
- // Resolve consumer spec from canonical sources only. The suggestion
1654
- // target is an instance id; AppSpec id is only the template identity.
1655
- const appData = appService.getInstanceInstallRecord(target.consumerInstanceId);
1656
- let consumerSpec = appData?.spec ?? null;
1657
- if (!consumerSpec) {
1658
- const { readCanonicalSpecForInstance } = await import("../services/app-common/instance-store.js");
1659
- consumerSpec = readCanonicalSpecForInstance(target.consumerInstanceId);
1660
- }
1661
- if (!consumerSpec) {
1662
- return reply.status(409).send({
1663
- detail: "Instance has no canonical AppSpec; run `jishushell migrate legacy --yes` first",
1664
- code: "INSTANCE_NEEDS_MIGRATION",
1665
- });
1666
- }
1667
- const newConnections = {
1668
- ...(meta.connections ?? {}),
1669
- [target.slot]: {
1670
- kind: "single",
1671
- providerId: target.candidate.providerId,
1672
- capability: target.candidate.capability,
1673
- },
1674
- };
1675
- const { applyConnections } = await import("../services/connections/transactor.js");
1676
- const safeJson = await import("../utils/safe-json.js");
1677
- const fs = await import("fs");
1678
- const path = await import("path");
1679
- const instancePath = appService.instanceMetaPath(target.consumerInstanceId);
1680
- const readInstanceJson = async () => (safeJson.safeReadJson(instancePath, `instance:${target.consumerInstanceId}`) ?? {});
1681
- const saveInstanceJson = async (_id, mutator) => {
1682
- const cur = (safeJson.safeReadJson(instancePath, `instance:${target.consumerInstanceId}`) ?? {});
1683
- try {
1684
- fs.mkdirSync(path.dirname(instancePath), { recursive: true });
1685
- }
1686
- catch {
1687
- /* noop */
1688
- }
1689
- safeJson.safeWriteJson(instancePath, mutator(cur));
1690
- };
1691
- let integration = null;
1692
- const hasIntegrationMetadata = Array.isArray(meta.integrations)
1693
- && meta.integrations.length > 0;
1694
- if (hasIntegrationMetadata) {
1695
- const integrationKind = resolvePrimaryIntegrationKind(meta);
1696
- if (!integrationKind || !hasIntegration(integrationKind)) {
1697
- return reply.status(409).send({
1698
- detail: "Instance is missing canonical integration identity; run migration or repair first",
1699
- code: "INSTANCE_NEEDS_MIGRATION",
1700
- });
1701
- }
1702
- const resolvedIntegration = getIntegration(integrationKind);
1703
- integration =
1704
- typeof resolvedIntegration.applyConnectionEnv === "function"
1705
- ? resolvedIntegration
1706
- : null;
1707
- }
1708
- try {
1709
- const result = await applyConnections({
1710
- instance: meta,
1711
- spec: consumerSpec,
1712
- newConnections,
1713
- saveInstanceJson,
1714
- readInstanceJson,
1715
- integration,
1716
- });
1717
- return { ok: true, applied: target.id, resolved: result.resolved.length };
1718
- }
1719
- catch (e) {
1720
- return reply.status(e?.statusCode ?? 500).send({
1721
- detail: e?.message ?? "Suggestion apply failed",
1722
- code: e?.code,
1723
- });
1724
- }
1725
- });
1726
- app.post("/api/suggestions/:id/dismiss", async (req, reply) => {
1727
- const { dismissSuggestion } = await import("../services/connections/suggestions.js");
1728
- try {
1729
- await dismissSuggestion(req.params.id);
1730
- return { ok: true };
1731
- }
1732
- catch (e) {
1733
- return reply.status(400).send({ detail: e?.message ?? "Invalid suggestion id" });
1734
- }
1735
- });
1736
596
  }
1737
597
  //# sourceMappingURL=instances.js.map