@pi-harness/pi-harness 0.1.24 → 0.1.26

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 (256) hide show
  1. package/README.md +3 -0
  2. package/apps/web/dist/assets/{index-BcLXLKqO.js → index-BCeWdgv2.js} +3 -2
  3. package/apps/web/dist/index.html +1 -1
  4. package/apps/web/package.json +6 -6
  5. package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
  6. package/node_modules/@pi-harness/api-gateway/dist/index.js +67 -47
  7. package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
  8. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
  9. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  10. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
  11. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  12. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
  13. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  14. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  15. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +1 -1
  16. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
  17. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
  18. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  19. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
  20. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
  21. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
  22. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
  23. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
  24. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +1 -1
  25. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
  26. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
  27. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
  28. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
  29. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
  30. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
  31. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
  32. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  33. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
  34. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
  35. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  36. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  37. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +1 -1
  38. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  39. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  40. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  41. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  42. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +1 -1
  43. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  44. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  45. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +1 -1
  46. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  47. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  48. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +1 -1
  49. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  50. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  51. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +1 -1
  52. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  53. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  54. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +1 -1
  55. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
  56. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  57. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  58. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  59. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  60. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  61. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +1 -1
  62. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  63. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  64. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +1 -1
  65. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  66. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  67. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +1 -1
  68. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
  69. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  70. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  71. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  72. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  73. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +1 -1
  74. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  75. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
  76. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
  77. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  78. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
  79. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  80. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  81. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  82. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +1 -1
  83. package/node_modules/@pi-harness/api-gateway/package.json +1 -1
  84. package/node_modules/@pi-harness/cli/package.json +1 -1
  85. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
  86. package/node_modules/@pi-harness/host-webserver/dist/index.js +4 -2
  87. package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
  88. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  89. package/node_modules/@pi-harness/web-app/package.json +1 -1
  90. package/package.json +6 -6
  91. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  92. package/packages/api-gateway/dist/index.js +67 -47
  93. package/packages/api-gateway/dist/index.js.map +1 -1
  94. package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
  95. package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  96. package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
  97. package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  98. package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
  99. package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  100. package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  101. package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +1 -1
  102. package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
  103. package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
  104. package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  105. package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
  106. package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
  107. package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
  108. package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
  109. package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
  110. package/packages/api-gateway/dist/marketplace-entries/official/context.json +1 -1
  111. package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
  112. package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
  113. package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
  114. package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
  115. package/packages/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
  116. package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
  117. package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
  118. package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  119. package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
  120. package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
  121. package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  122. package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  123. package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +1 -1
  124. package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  125. package/packages/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  126. package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  127. package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  128. package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +1 -1
  129. package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  130. package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  131. package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +1 -1
  132. package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  133. package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  134. package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +1 -1
  135. package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  136. package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  137. package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +1 -1
  138. package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  139. package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  140. package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +1 -1
  141. package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
  142. package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  143. package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  144. package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  145. package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  146. package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  147. package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +1 -1
  148. package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  149. package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  150. package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +1 -1
  151. package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  152. package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  153. package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +1 -1
  154. package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
  155. package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  156. package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  157. package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  158. package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  159. package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +1 -1
  160. package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  161. package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
  162. package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
  163. package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  164. package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
  165. package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  166. package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  167. package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  168. package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +1 -1
  169. package/packages/api-gateway/package.json +1 -1
  170. package/packages/api-gateway/src/index.ts +100 -66
  171. package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +1 -1
  172. package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
  173. package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -1
  174. package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
  175. package/packages/api-gateway/src/marketplace-entries/official/at-file.json +1 -1
  176. package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +1 -1
  177. package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +1 -1
  178. package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +1 -1
  179. package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +1 -1
  180. package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +1 -1
  181. package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
  182. package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +1 -1
  183. package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +1 -1
  184. package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +1 -1
  185. package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -1
  186. package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +1 -1
  187. package/packages/api-gateway/src/marketplace-entries/official/context.json +1 -1
  188. package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +1 -1
  189. package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +1 -1
  190. package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +1 -1
  191. package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +1 -1
  192. package/packages/api-gateway/src/marketplace-entries/official/genui.json +1 -1
  193. package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +1 -1
  194. package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +1 -1
  195. package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
  196. package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +1 -1
  197. package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +1 -1
  198. package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +1 -1
  199. package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +1 -1
  200. package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +1 -1
  201. package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +1 -1
  202. package/packages/api-gateway/src/marketplace-entries/official/memory.json +1 -1
  203. package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +1 -1
  204. package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +1 -1
  205. package/packages/api-gateway/src/marketplace-entries/official/modlens.json +1 -1
  206. package/packages/api-gateway/src/marketplace-entries/official/module-search.json +1 -1
  207. package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +1 -1
  208. package/packages/api-gateway/src/marketplace-entries/official/openpets.json +1 -1
  209. package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +1 -1
  210. package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +1 -1
  211. package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +1 -1
  212. package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +1 -1
  213. package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +1 -1
  214. package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +1 -1
  215. package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +1 -1
  216. package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +1 -1
  217. package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +1 -1
  218. package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +1 -1
  219. package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +1 -1
  220. package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +1 -1
  221. package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +1 -1
  222. package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +1 -1
  223. package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +1 -1
  224. package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +1 -1
  225. package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +1 -1
  226. package/packages/api-gateway/src/marketplace-entries/official/session-export.json +1 -1
  227. package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +1 -1
  228. package/packages/api-gateway/src/marketplace-entries/official/session-search.json +1 -1
  229. package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +1 -1
  230. package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +1 -1
  231. package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +1 -1
  232. package/packages/api-gateway/src/marketplace-entries/official/synapse.json +1 -1
  233. package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +1 -1
  234. package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +1 -1
  235. package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +1 -1
  236. package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +1 -1
  237. package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
  238. package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +1 -1
  239. package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +1 -1
  240. package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
  241. package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +1 -1
  242. package/packages/api-gateway/src/marketplace-entries/official/web-research.json +1 -1
  243. package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +1 -1
  244. package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +1 -1
  245. package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +1 -1
  246. package/packages/api-gateway/test/index.test.ts +220 -2
  247. package/packages/api-gateway/test/release-contract.test.ts +7 -2
  248. package/packages/api-gateway/test/release-package.test.ts +16 -0
  249. package/packages/bundle-web-app/package.json +1 -1
  250. package/packages/cli/package.json +1 -1
  251. package/packages/host-webserver/dist/index.d.ts.map +1 -1
  252. package/packages/host-webserver/dist/index.js +4 -2
  253. package/packages/host-webserver/dist/index.js.map +1 -1
  254. package/packages/host-webserver/package.json +1 -1
  255. package/packages/host-webserver/src/index.ts +4 -2
  256. package/packages/host-webserver/test/index.test.ts +22 -0
@@ -1,4 +1,4 @@
1
- import { chmod, mkdir, mkdtemp, readdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
1
+ import { chmod, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rm, stat, symlink, writeFile } from "node:fs/promises";
2
2
  import { execFile as execFileCallback } from "node:child_process";
3
3
  import { createServer } from "node:http";
4
4
  import { promisify } from "node:util";
@@ -13,6 +13,19 @@ import webServerPlugin from "@pi-harness/host-webserver";
13
13
  import { PiPluginUiRegistry, PiToolRegistry } from "@pi-harness/core";
14
14
  import type { MarketplacePlugin } from "../src/marketplace.js";
15
15
  import apiPlugin from "../src/index.js";
16
+ import type * as FsPromises from "node:fs/promises";
17
+
18
+ // The gateway reads the session metadata file through node:fs/promises; this pass-through mock lets one test hold a read open after its bytes arrived so lock ordering can be observed deterministically. Everything else goes straight to the real implementation.
19
+ const fsHooks = vi.hoisted(() => ({ afterReadFile: undefined as ((path: string) => Promise<void>) | undefined }));
20
+ vi.mock("node:fs/promises", async (importOriginal) => {
21
+ const actual = await importOriginal<typeof FsPromises>();
22
+ const readFile = async (path: unknown, options?: unknown) => {
23
+ const result: unknown = await (actual.readFile as (path: unknown, options?: unknown) => Promise<unknown>)(path, options);
24
+ if (fsHooks.afterReadFile) await fsHooks.afterReadFile(String(path));
25
+ return result;
26
+ };
27
+ return { ...actual, readFile };
28
+ });
16
29
 
17
30
  // Every shipped catalog entry that npm has to install names a bare package, so `packageName` and its npm package name coincide and the install route cannot show whether it narrows the specifier. This entry is the case where the two differ: a deep import path whose npm package is only the first two segments. It is hoisted because the module mock below reads it while the test module body is still in its temporal dead zone.
18
31
  const { subpathPlugin } = vi.hoisted(() => ({
@@ -43,6 +56,7 @@ vi.mock("../src/marketplace.js", async (importOriginal) => {
43
56
  const contexts: Context[] = [];
44
57
  const temporaryDirectories: string[] = [];
45
58
  const execFile = promisify(execFileCallback);
59
+ const sleep = (ms: number) => new Promise<void>((resolveSleep) => setTimeout(resolveSleep, ms));
46
60
 
47
61
  afterEach(async () => {
48
62
  await Promise.all(contexts.splice(0).map(async (context) => context.fiber.dispose()));
@@ -2581,7 +2595,12 @@ describe("API gateway plugin", () => {
2581
2595
  expect(failed.status).toBe(500);
2582
2596
  await expect(failed.json()).resolves.toEqual({ error: "fatal: index file corrupt" });
2583
2597
 
2584
- await writeFile(shim, "#!/bin/sh\n{ printf ' M tracked.txt\\n'; seq 1 40000 | sed 's/^/?? untracked-/;s/$/.txt/'; } | tr '\\n' '\\0'\n", { mode: 0o755 });
2598
+ // The gateway consumes NUL-separated `--porcelain -z` entries and then asks git for the cwd prefix, so the shim answers both calls.
2599
+ await writeFile(
2600
+ shim,
2601
+ "#!/bin/sh\ncase \"$1\" in rev-parse) exit 0;; esac\nprintf ' M tracked.txt\\0'\nseq 1 40000 | sed 's/^/?? untracked-/;s/$/.txt/' | tr '\\n' '\\0'\n",
2602
+ { mode: 0o755 },
2603
+ );
2585
2604
  const truncated = await fetch(context.webServer.url + "/api/files");
2586
2605
  expect(truncated.status).toBe(200);
2587
2606
  const payload = (await truncated.json()) as { items: { path: string }[]; truncated?: boolean };
@@ -3215,4 +3234,203 @@ describe("API gateway plugin", () => {
3215
3234
  await expect(readFile(join(harnessDir, "package.json"), "utf8")).resolves.toBe('{ "name": "harness" }\n');
3216
3235
  await expect(stat(join(harnessDir, "package-lock.json"))).rejects.toMatchObject({ code: "ENOENT" });
3217
3236
  });
3237
+
3238
+ test("propagates metadata read errors other than ENOENT instead of quarantining a file it could not read", async () => {
3239
+ const context = new Context();
3240
+ contexts.push(context);
3241
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-metadata-eisdir-"));
3242
+ temporaryDirectories.push(directory);
3243
+ const metadataFile = join(directory, ".pi-harness-session-meta.json");
3244
+ // A directory at the metadata path makes readFile fail with EISDIR while rename would still succeed, which is exactly the shape of a transient read failure.
3245
+ await mkdir(metadataFile);
3246
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
3247
+ const session = {
3248
+ sessionId: "metadata-session",
3249
+ sessionFile: undefined,
3250
+ messages: [],
3251
+ isStreaming: false,
3252
+ sessionManager: { getSessionDir: () => directory, isPersisted: () => true, getEntries: () => [] },
3253
+ subscribe: () => () => {},
3254
+ };
3255
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
3256
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
3257
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
3258
+ await context.plugin(apiPlugin);
3259
+
3260
+ const list = await fetch(context.webServer.url + "/api/sessions");
3261
+ expect(list.status).toBe(500);
3262
+ const listBody = (await list.json()) as { error: string };
3263
+ expect(listBody.error).toContain("EISDIR");
3264
+ const update = await fetch(context.webServer.url + "/api/session/metadata", {
3265
+ method: "POST",
3266
+ headers: { "content-type": "application/json" },
3267
+ body: JSON.stringify({ path: join(directory, "2026-08-30T00-00-00-000Z_pinned.jsonl"), pinned: true }),
3268
+ });
3269
+ expect(update.status).toBe(400);
3270
+ expect((await stat(metadataFile)).isDirectory()).toBe(true);
3271
+ expect((await readdir(directory)).filter((name) => name.startsWith(".pi-harness-session-meta.json.corrupt-"))).toEqual([]);
3272
+ });
3273
+
3274
+ test("serializes the session list read with metadata mutations so a stale quarantine cannot discard a fresh write", async () => {
3275
+ const context = new Context();
3276
+ contexts.push(context);
3277
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-metadata-race-"));
3278
+ temporaryDirectories.push(directory);
3279
+ const metadataFile = join(directory, ".pi-harness-session-meta.json");
3280
+ const corrupt = '{ "other.jsonl": { "arc';
3281
+ await writeFile(metadataFile, corrupt, "utf8");
3282
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
3283
+ const session = {
3284
+ sessionId: "metadata-session",
3285
+ sessionFile: undefined,
3286
+ messages: [],
3287
+ isStreaming: false,
3288
+ sessionManager: { getSessionDir: () => directory, isPersisted: () => true, getEntries: () => [] },
3289
+ subscribe: () => () => {},
3290
+ };
3291
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
3292
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
3293
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
3294
+ await context.plugin(apiPlugin);
3295
+
3296
+ // Hold the first metadata read after its (corrupt) bytes arrived: the reader now owns the lock but has not parsed yet.
3297
+ let release = () => {};
3298
+ const held = new Promise<void>((resolveHeld) => {
3299
+ let first = true;
3300
+ fsHooks.afterReadFile = async (path) => {
3301
+ if (path !== metadataFile || !first) return;
3302
+ first = false;
3303
+ resolveHeld();
3304
+ await new Promise<void>((resolveRelease) => {
3305
+ release = resolveRelease;
3306
+ });
3307
+ };
3308
+ });
3309
+ try {
3310
+ const list = fetch(context.webServer.url + "/api/sessions");
3311
+ await held;
3312
+ const path = join(directory, "2026-08-30T00-00-00-000Z_pinned.jsonl");
3313
+ const update = fetch(context.webServer.url + "/api/session/metadata", {
3314
+ method: "POST",
3315
+ headers: { "content-type": "application/json" },
3316
+ body: JSON.stringify({ path, pinned: true }),
3317
+ });
3318
+ await expect(Promise.race([update.then(() => "settled"), sleep(300).then(() => "pending")])).resolves.toBe("pending");
3319
+ release();
3320
+ expect((await list).status).toBe(200);
3321
+ const updated = await update;
3322
+ expect(updated.status).toBe(200);
3323
+ await expect(updated.json()).resolves.toEqual({ path, metadata: { pinned: true } });
3324
+ expect(JSON.parse(await readFile(metadataFile, "utf8"))).toEqual({ [path]: { pinned: true } });
3325
+ const quarantined = (await readdir(directory)).filter((name) => name.startsWith(".pi-harness-session-meta.json.corrupt-"));
3326
+ expect(quarantined).toHaveLength(1);
3327
+ await expect(readFile(join(directory, quarantined[0] ?? ""), "utf8")).resolves.toBe(corrupt);
3328
+ } finally {
3329
+ fsHooks.afterReadFile = undefined;
3330
+ release();
3331
+ }
3332
+ });
3333
+
3334
+ test("lets git commit outlive the read-only timeout so slow hooks are not killed mid-commit", { timeout: 40_000 }, async () => {
3335
+ const context = new Context();
3336
+ contexts.push(context);
3337
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-git-slow-commit-"));
3338
+ temporaryDirectories.push(directory);
3339
+ const shimDirectory = join(directory, "bin");
3340
+ await mkdir(shimDirectory);
3341
+ // A commit that takes longer than the 15 s read-only bound, as a pre-commit hook running a test suite would.
3342
+ await writeFile(join(shimDirectory, "git"), '#!/bin/sh\ncase "$1" in commit) sleep 16;; rev-parse) printf abc1234;; esac\nexit 0\n', { mode: 0o755 });
3343
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
3344
+ const session = { sessionId: "commit-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
3345
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve(), abort: () => Promise.resolve(), dispose: () => Promise.resolve() } as never);
3346
+ context.provide("piModels", { model: { provider: "test", id: "model" }, runtime: { getModels: () => [], getModel: () => undefined } } as never);
3347
+ context.provide("piHarnessLaunch", { cwd: directory, agentDir: "/tmp/agent", args: [], requestExit() {} });
3348
+ await context.plugin(apiPlugin);
3349
+
3350
+ const originalPath = process.env.PATH ?? "";
3351
+ process.env.PATH = `${shimDirectory}:${originalPath}`;
3352
+ try {
3353
+ const response = await fetch(context.webServer.url + "/api/files/commit", {
3354
+ method: "POST",
3355
+ headers: { "content-type": "application/json" },
3356
+ body: JSON.stringify({ paths: ["README.md"], message: "Slow hook" }),
3357
+ });
3358
+ expect(response.status).toBe(200);
3359
+ await expect(response.json()).resolves.toEqual({ committed: true, message: "Slow hook", commit: "abc1234" });
3360
+ } finally {
3361
+ process.env.PATH = originalPath;
3362
+ }
3363
+ });
3364
+
3365
+ test("reports workspace paths relative to a subdirectory cwd so diff and revert resolve them", async () => {
3366
+ const context = new Context();
3367
+ contexts.push(context);
3368
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-files-subdir-"));
3369
+ temporaryDirectories.push(directory);
3370
+ await execFile("git", ["init", "-q"], { cwd: directory });
3371
+ await execFile("git", ["config", "user.email", "pi-harness@test.invalid"], { cwd: directory });
3372
+ await execFile("git", ["config", "user.name", "Pi Harness Test"], { cwd: directory });
3373
+ await mkdir(join(directory, "sub", "nested"), { recursive: true });
3374
+ await writeFile(join(directory, "sub", "nested", "inner.txt"), "before\n", "utf8");
3375
+ await writeFile(join(directory, "root.txt"), "before\n", "utf8");
3376
+ await execFile("git", ["add", "-A"], { cwd: directory });
3377
+ await execFile("git", ["commit", "-qm", "initial"], { cwd: directory });
3378
+ await writeFile(join(directory, "sub", "nested", "inner.txt"), "after\n", "utf8");
3379
+ await writeFile(join(directory, "root.txt"), "after\n", "utf8");
3380
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
3381
+ const session = { sessionId: "files-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
3382
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve(), abort: () => Promise.resolve(), dispose: () => Promise.resolve() } as never);
3383
+ context.provide("piModels", { model: { provider: "test", id: "model" }, runtime: { getModels: () => [], getModel: () => undefined } } as never);
3384
+ context.provide("piHarnessLaunch", { cwd: join(directory, "sub"), agentDir: "/tmp/agent", args: [], requestExit() {} });
3385
+ await context.plugin(apiPlugin);
3386
+
3387
+ const list = await fetch(context.webServer.url + "/api/files");
3388
+ const payload = (await list.json()) as { items: { path: string }[] };
3389
+ expect(payload.items.map((item) => item.path).sort()).toEqual(["../root.txt", "nested/inner.txt"]);
3390
+ const diff = await fetch(context.webServer.url + "/api/files/diff?path=" + encodeURIComponent("nested/inner.txt"));
3391
+ const nestedDiffBody = (await diff.json()) as { path: string; diff: string };
3392
+ expect(nestedDiffBody.path).toBe("nested/inner.txt");
3393
+ expect(nestedDiffBody.diff).toContain("+after");
3394
+ const revert = await fetch(context.webServer.url + "/api/files/revert", {
3395
+ method: "POST",
3396
+ headers: { "content-type": "application/json" },
3397
+ body: JSON.stringify({ paths: ["nested/inner.txt"], confirm: true }),
3398
+ });
3399
+ expect(revert.status).toBe(200);
3400
+ await expect(readFile(join(directory, "sub", "nested", "inner.txt"), "utf8")).resolves.toBe("before\n");
3401
+ await expect(readFile(join(directory, "root.txt"), "utf8")).resolves.toBe("after\n");
3402
+ });
3403
+
3404
+ test("writes through a symlinked settings.json instead of replacing the link with a regular file", async () => {
3405
+ const context = new Context();
3406
+ contexts.push(context);
3407
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-agent-symlink-"));
3408
+ temporaryDirectories.push(directory);
3409
+ const agentDir = join(directory, "agent");
3410
+ const dotfiles = join(directory, "dotfiles");
3411
+ await mkdir(agentDir);
3412
+ await mkdir(dotfiles);
3413
+ const realSettings = join(dotfiles, "settings.json");
3414
+ const settingsPath = join(agentDir, "settings.json");
3415
+ await writeFile(realSettings, '{"stale":true}\n', "utf8");
3416
+ await symlink(realSettings, settingsPath);
3417
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
3418
+ const settingsManager = new Proxy({}, { get: () => () => ({}) });
3419
+ const session = { sessionId: "config-session", sessionFile: undefined, messages: [], isStreaming: false, settingsManager, subscribe: () => () => {} };
3420
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
3421
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
3422
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir, args: [], requestExit() {} });
3423
+ await context.plugin(apiPlugin);
3424
+
3425
+ const response = await fetch(context.webServer.url + "/api/config/source", {
3426
+ method: "POST",
3427
+ headers: { "content-type": "application/json" },
3428
+ body: JSON.stringify({ source: '{"defaultProvider":"test"}' }),
3429
+ });
3430
+ expect(response.status).toBe(200);
3431
+ expect((await lstat(settingsPath)).isSymbolicLink()).toBe(true);
3432
+ await expect(readFile(realSettings, "utf8")).resolves.toBe('{\n "defaultProvider": "test"\n}\n');
3433
+ expect((await readdir(dotfiles)).filter((name) => name.endsWith(".tmp"))).toEqual([]);
3434
+ expect((await readdir(agentDir)).filter((name) => name.endsWith(".tmp"))).toEqual([]);
3435
+ });
3218
3436
  });
@@ -58,7 +58,7 @@ describe("release contract", () => {
58
58
  });
59
59
 
60
60
  test("core declares every package its sources and shipped profiles load", () => {
61
- const core = readJson("packages/core/package.json") as { name: string; dependencies: Record<string, string> };
61
+ const core = readJson("packages/core/package.json") as { name: string; dependencies: Record<string, string>; peerDependencies: Record<string, string> };
62
62
  const specifierPattern =
63
63
  /(?:from\s*|import\s*\(\s*|import\.meta\.resolve\(\s*|require(?:\.resolve)?\(\s*)["']([@a-z][^"'\s]*)["']|"(@[a-z0-9._~-]+\/[a-z0-9._~-]+(?:\/[a-z0-9._~-]+)*)"/gu;
64
64
  const required = new Map<string, string>();
@@ -72,6 +72,11 @@ describe("release contract", () => {
72
72
  }
73
73
  const external = [...required].filter(([name]) => !name.startsWith("node:") && !name.includes(":") && name !== core.name);
74
74
  expect(external.length).toBeGreaterThan(0);
75
- for (const [name, file] of external) expect(core.dependencies[name], `${name} (used by ${file}) is missing from ${core.name} dependencies`).toBeDefined();
75
+ // A runtime that appears in the published type surface is declared as a peer instead, so the consumer owns the single installed copy.
76
+ for (const [name, file] of external)
77
+ expect(
78
+ core.dependencies[name] ?? core.peerDependencies[name],
79
+ `${name} (used by ${file}) is declared by neither ${core.name} dependencies nor peerDependencies`,
80
+ ).toBeDefined();
76
81
  });
77
82
  });
@@ -7,6 +7,8 @@ const bundledWorkspacePaths = ["api-gateway", "cli", "host-webserver", "bundle-w
7
7
  const bundledRuntimePackageNames = ["@earendil-works/pi-agent-core", "@earendil-works/pi-ai", "@earendil-works/pi-coding-agent"] as const;
8
8
  const bundledWorkspacePackageNames = ["@pi-harness/api-gateway", "@pi-harness/cli", "@pi-harness/host-webserver", "@pi-harness/web-app"] as const;
9
9
  const bundledPackageNames = [...bundledRuntimePackageNames, ...bundledWorkspacePackageNames];
10
+ // Runtimes whose types appear in the published surface of @pi-harness/core. npm nests a second copy of a pinned dependency when the consumer wants a different version; a peer resolves to the consumer's single copy instead.
11
+ const corePeerRuntimeNames = ["@deepseek-ai/cordis", "@deepseek-ai/schemastery", "@earendil-works/pi-ai", "@earendil-works/pi-coding-agent"] as const;
10
12
 
11
13
  const readJson = async (path: string): Promise<Record<string, unknown>> =>
12
14
  JSON.parse(await readFile(resolve(repositoryRoot, path), "utf8")) as Record<string, unknown>;
@@ -36,6 +38,20 @@ describe("release package", () => {
36
38
  expect(workflow).not.toContain("RELEASE_TAG_EXISTS");
37
39
  });
38
40
 
41
+ it("keeps the runtimes in the core type surface as peers so a plugin author resolves one copy", async () => {
42
+ const rootManifest = await readJson("package.json");
43
+ const rootDependencies = rootManifest.dependencies as Record<string, string>;
44
+ const core = await readJson("packages/core/package.json");
45
+ const coreDependencies = (core.dependencies ?? {}) as Record<string, string>;
46
+ const corePeers = (core.peerDependencies ?? {}) as Record<string, string>;
47
+
48
+ expect(Object.keys(corePeers).sort()).toEqual([...corePeerRuntimeNames]);
49
+ for (const name of corePeerRuntimeNames) {
50
+ expect(coreDependencies[name], `${name} must not also be a dependency, which reintroduces the nested copy`).toBeUndefined();
51
+ expect(corePeers[name], `${name} peer range must pin the version the launcher installs`).toBe(rootDependencies[name]);
52
+ }
53
+ });
54
+
39
55
  it("cleans every compiled workspace output", async () => {
40
56
  // `tsc` never removes stale emit, so an output directory that no clean script touches keeps publishing files whose sources are gone.
41
57
  const rootManifest = await readJson("package.json");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/web-app",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": true,
5
5
  "description": "Patchable Web application bundle for Pi Harness",
6
6
  "homepage": "https://github.com/pi-harness/pi-harness#readme",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/cli",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": true,
5
5
  "description": "Plugin-first Pi Harness CLI",
6
6
  "homepage": "https://github.com/pi-harness/pi-harness#readme",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACtC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,SAAS,EAAE,SAAS,CAAC;KACtB;CACF;;;mBAqGsB,OAAO,UAAU,eAAe;;AAFvD,wBA4DE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACtC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI,CAAC;IAC3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,SAAS,EAAE,SAAS,CAAC;KACtB;CACF;;;mBAqGsB,OAAO,UAAU,eAAe;;AAFvD,wBA8DE"}
@@ -94,6 +94,8 @@ export default {
94
94
  const host = config.host ?? "127.0.0.1";
95
95
  const port = config.port ?? DEFAULT_WEB_SERVER_PORT;
96
96
  const allowedHosts = config.allowedHosts ?? [];
97
+ // A wildcard bind (including the empty host, which listen() accepts) has no hostname of its own, and "http://" + "" is not a parsable URL base, so resolving a request path against it threw out of the request listener. Loopback is the address such a bind always answers on, so it stands in both as the base for request URLs and as the advertised URL.
98
+ const urlHost = WILDCARD_HOSTS.has(host) ? "127.0.0.1" : hostForUrl(host);
97
99
  const routes = new Map();
98
100
  let fallback;
99
101
  // Assigned once listen() completes; tests bind port 0 so the configured port is not the one clients address.
@@ -101,7 +103,7 @@ export default {
101
103
  const server = createServer((request, response) => {
102
104
  if (rejectForeignRequest(request, response, host, boundPort, allowedHosts))
103
105
  return;
104
- const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
106
+ const path = new URL(request.url ?? "/", "http://" + urlHost).pathname;
105
107
  const handler = routes.get(path) ?? fallback;
106
108
  if (handler === undefined) {
107
109
  notFound(response);
@@ -131,7 +133,7 @@ export default {
131
133
  const service = {
132
134
  host,
133
135
  port: address.port,
134
- url: "http://" + hostForUrl(host) + ":" + address.port,
136
+ url: "http://" + urlHost + ":" + address.port,
135
137
  register(route) {
136
138
  const path = normalizePath(route.path);
137
139
  if (routes.has(path))
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAwBzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAQ5C,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4DAA4D,GAAG,IAAI,CAAC,CAAC;IAC3J,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,QAAwB,EAAE,MAAc,EAAE,OAAe;IAC1E,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE,CAAC,CAAC;IAClF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,QAAQ,CAAC,QAAwB;IACxC,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AAO9D,6PAA6P;AAC7P,SAAS,kBAAkB,CAAC,KAAa;IACvC,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,GAAG,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACxJ,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACxF,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;AACnF,CAAC;AAED,kNAAkN;AAClN,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,qXAAqX;AACrX,SAAS,cAAc;IACrB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,GAAG,GAAG,iBAAiB,CAAC,eAAe,EAAE,CAAC,CAAC;IACjD,IAAI,GAAG,KAAK,SAAS;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7E,IAAI,IAAI,KAAK,SAAS;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,QAAgB,EAAE,cAAsB,EAAE,YAA+B;IAClG,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACrF,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC;QAAE,OAAO,QAAQ,KAAK,UAAU,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,CAAC;IACtG,OAAO,cAAc,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACxC,CAAC;AAED,qxBAAqxB;AACrxB,SAAS,oBAAoB,CAC3B,OAAwB,EACxB,QAAwB,EACxB,cAAsB,EACtB,SAAiB,EACjB,YAA+B;IAE/B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;IACxC,MAAM,SAAS,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACxF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,qBAAqB,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,QAAQ,EAAE,cAAc,EAAE,YAAY,CAAC,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACzG,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,mDAAmD,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,eAAe,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtH,IAAI,eAAe,KAAK,SAAS,IAAI,eAAe,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,IAAI,eAAe,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QAChI,SAAS,CAAC,QAAQ,EAAE,GAAG,EAAE,uCAAuC,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,eAAe;IACb,IAAI,EAAE,cAAc;IACpB,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,MAAuB;QACnD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,uBAAuB,CAAC;QACpD,MAAM,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;QAC/C,8VAA8V;QAC9V,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QACtD,IAAI,QAAyC,CAAC;QAC9C,6GAA6G;QAC7G,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YAChD,IAAI,oBAAoB,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,CAAC;gBAAE,OAAO;YACnF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,CAAC,QAAQ,CAAC;YACvE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;YAC7C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,mLAAmL;YACnL,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACvE,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;oBACzB,QAAQ,CAAC,OAAO,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC5E,OAAO;gBACT,CAAC;gBACD,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,iCAAiC,EAAE,CAAC,CAAC;gBAC/E,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAClG,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;gBAC7B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;gBAC5B,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAChH,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,MAAM,OAAO,GAAc;YACzB,IAAI;YACJ,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,GAAG,EAAE,SAAS,GAAG,OAAO,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI;YAC7C,QAAQ,CAAC,KAAK;gBACZ,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACvC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAAC;gBAC/E,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAChC,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;YACD,gBAAgB,CAAC,OAAO;gBACtB,IAAI,QAAQ,KAAK,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;gBAClF,QAAQ,GAAG,OAAO,CAAC;gBACnB,OAAO,GAAG,EAAE;oBACV,IAAI,QAAQ,KAAK,OAAO;wBAAE,QAAQ,GAAG,SAAS,CAAC;gBACjD,CAAC,CAAC;YACJ,CAAC;YACD,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;SAClH,CAAC;QACF,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACtC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IACpD,CAAC;CACF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-harness/host-webserver",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": true,
5
5
  "description": "Cordis route-registration service for the Pi Harness web host",
6
6
  "homepage": "https://github.com/pi-harness/pi-harness#readme",
@@ -134,13 +134,15 @@ export default {
134
134
  const host = config.host ?? "127.0.0.1";
135
135
  const port = config.port ?? DEFAULT_WEB_SERVER_PORT;
136
136
  const allowedHosts = config.allowedHosts ?? [];
137
+ // A wildcard bind (including the empty host, which listen() accepts) has no hostname of its own, and "http://" + "" is not a parsable URL base, so resolving a request path against it threw out of the request listener. Loopback is the address such a bind always answers on, so it stands in both as the base for request URLs and as the advertised URL.
138
+ const urlHost = WILDCARD_HOSTS.has(host) ? "127.0.0.1" : hostForUrl(host);
137
139
  const routes = new Map<string, WebRoute["handler"]>();
138
140
  let fallback: WebRoute["handler"] | undefined;
139
141
  // Assigned once listen() completes; tests bind port 0 so the configured port is not the one clients address.
140
142
  let boundPort = 0;
141
143
  const server = createServer((request, response) => {
142
144
  if (rejectForeignRequest(request, response, host, boundPort, allowedHosts)) return;
143
- const path = new URL(request.url ?? "/", "http://" + hostForUrl(host)).pathname;
145
+ const path = new URL(request.url ?? "/", "http://" + urlHost).pathname;
144
146
  const handler = routes.get(path) ?? fallback;
145
147
  if (handler === undefined) {
146
148
  notFound(response);
@@ -169,7 +171,7 @@ export default {
169
171
  const service: WebServer = {
170
172
  host,
171
173
  port: address.port,
172
- url: "http://" + hostForUrl(host) + ":" + address.port,
174
+ url: "http://" + urlHost + ":" + address.port,
173
175
  register(route) {
174
176
  const path = normalizePath(route.path);
175
177
  if (routes.has(path)) throw new Error("Web route already registered: " + path);
@@ -173,6 +173,7 @@ describe("web server plugin", () => {
173
173
  test("accepts only this machine's own names on a wildcard bind so DNS rebinding still fails", async () => {
174
174
  const wildcard = await startServer({ host: "0.0.0.0", port: 0 });
175
175
  const health = registerHealth(wildcard);
176
+ expect(wildcard.url).toBe("http://127.0.0.1:" + wildcard.port);
176
177
 
177
178
  await expect(rawRequest(wildcard, { host: "attacker.example:" + wildcard.port })).resolves.toEqual({
178
179
  status: 400,
@@ -199,6 +200,27 @@ describe("web server plugin", () => {
199
200
  await expect(rawRequest(proxied, { host: "other.lan:" + proxied.port })).resolves.toMatchObject({ status: 400 });
200
201
  });
201
202
 
203
+ test("canonicalizes configured hostnames the same way as the Host header", async () => {
204
+ // WHATWG URL compresses the IPv6 literal in the Host header, so a bind address or allowlist entry written out in full has to go through the same parser or it can never match.
205
+ const server = await startServer({ host: "127.0.0.1", port: 0, allowedHosts: ["2001:db8:0:0:0:0:0:1", "WORKSTATION.LAN"] });
206
+ registerHealth(server);
207
+
208
+ await expect(rawRequest(server, { host: "[2001:db8::1]:" + server.port })).resolves.toMatchObject({ status: 200 });
209
+ await expect(rawRequest(server, { host: "workstation.lan:" + server.port })).resolves.toMatchObject({ status: 200 });
210
+ await expect(rawRequest(server, { host: "[2001:db8::2]:" + server.port })).resolves.toMatchObject({ status: 400 });
211
+ });
212
+
213
+ test("serves an empty configured host, which listen() treats as a wildcard bind", async () => {
214
+ // "http://" + "" is not a parsable URL base, so resolving the request path against the raw configured host threw a TypeError out of the request listener and killed the process on the very first request.
215
+ const server = await startServer({ host: "", port: 0 });
216
+ const health = registerHealth(server);
217
+
218
+ expect(server.url).toBe("http://127.0.0.1:" + server.port);
219
+ await expect(rawRequest(server, { host: "127.0.0.1:" + server.port })).resolves.toMatchObject({ status: 200, body: "ok" });
220
+ await expect(rawRequest(server, { host: "evil.example:" + server.port })).resolves.toMatchObject({ status: 400 });
221
+ expect(health.calls).toBe(1);
222
+ });
223
+
202
224
  test("serves an IPv6 bind at its bracketed URL", async () => {
203
225
  const ipv6 = await startServer({ host: "::1", port: 0 });
204
226
  registerHealth(ipv6);