@pi-harness/pi-harness 0.1.22 → 0.1.24

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 (307) hide show
  1. package/README.md +12 -10
  2. package/apps/web/dist/assets/index-BcLXLKqO.js +24 -0
  3. package/apps/web/dist/assets/index-xF647pQJ.css +2 -0
  4. package/apps/web/dist/index.html +2 -2
  5. package/apps/web/package.json +6 -6
  6. package/apps/web/profile/cordis.yml +1 -0
  7. package/apps/web/server-dist/bin.js +45 -7
  8. package/apps/web/server-dist/bin.js.map +1 -1
  9. package/apps/web/src/server/bin.ts +45 -7
  10. package/apps/web/test/bin.test.ts +313 -0
  11. package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
  12. package/node_modules/@pi-harness/api-gateway/dist/index.js +295 -86
  13. package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
  14. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
  15. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  16. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
  17. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  18. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
  19. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  20. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  21. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +1 -1
  22. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
  23. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
  24. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  25. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
  26. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
  27. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
  28. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
  29. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
  30. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +1 -1
  31. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
  32. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
  33. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
  34. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
  35. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
  36. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
  37. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
  38. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  39. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +3 -3
  40. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
  41. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  42. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  43. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +1 -1
  44. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  45. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  46. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  47. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  48. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +1 -1
  49. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  50. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  51. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +1 -1
  52. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  53. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  54. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +1 -1
  55. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  56. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  57. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +1 -1
  58. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  59. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  60. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +1 -1
  61. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
  62. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  63. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  64. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  65. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  66. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  67. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +1 -1
  68. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  69. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  70. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +1 -1
  71. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  72. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  73. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +1 -1
  74. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
  75. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  76. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  77. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  78. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  79. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +1 -1
  80. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  81. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
  82. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
  83. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  84. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
  85. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  86. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  87. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  88. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +1 -1
  89. package/node_modules/@pi-harness/api-gateway/package.json +1 -1
  90. package/node_modules/@pi-harness/cli/dist/main.d.ts +1 -0
  91. package/node_modules/@pi-harness/cli/dist/main.d.ts.map +1 -1
  92. package/node_modules/@pi-harness/cli/dist/main.js +11 -0
  93. package/node_modules/@pi-harness/cli/dist/main.js.map +1 -1
  94. package/node_modules/@pi-harness/cli/dist/node-stdio.d.ts.map +1 -1
  95. package/node_modules/@pi-harness/cli/dist/node-stdio.js +3 -1
  96. package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
  97. package/node_modules/@pi-harness/cli/dist/relaunch.d.ts.map +1 -1
  98. package/node_modules/@pi-harness/cli/dist/relaunch.js +4 -1
  99. package/node_modules/@pi-harness/cli/dist/relaunch.js.map +1 -1
  100. package/node_modules/@pi-harness/cli/package.json +1 -1
  101. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts +1 -0
  102. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
  103. package/node_modules/@pi-harness/host-webserver/dist/index.js +86 -3
  104. package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
  105. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  106. package/node_modules/@pi-harness/web-app/dist/index.d.ts +3 -0
  107. package/node_modules/@pi-harness/web-app/dist/index.d.ts.map +1 -1
  108. package/node_modules/@pi-harness/web-app/dist/index.js +21 -8
  109. package/node_modules/@pi-harness/web-app/dist/index.js.map +1 -1
  110. package/node_modules/@pi-harness/web-app/package.json +1 -1
  111. package/package.json +8 -8
  112. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  113. package/packages/api-gateway/dist/index.js +295 -86
  114. package/packages/api-gateway/dist/index.js.map +1 -1
  115. package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
  116. package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  117. package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
  118. package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  119. package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
  120. package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  121. package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  122. package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +1 -1
  123. package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
  124. package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
  125. package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  126. package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
  127. package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
  128. package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
  129. package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
  130. package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
  131. package/packages/api-gateway/dist/marketplace-entries/official/context.json +1 -1
  132. package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
  133. package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
  134. package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
  135. package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
  136. package/packages/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
  137. package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
  138. package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
  139. package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  140. package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +3 -3
  141. package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
  142. package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  143. package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  144. package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +1 -1
  145. package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  146. package/packages/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  147. package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  148. package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  149. package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +1 -1
  150. package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  151. package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  152. package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +1 -1
  153. package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  154. package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  155. package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +1 -1
  156. package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  157. package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  158. package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +1 -1
  159. package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  160. package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  161. package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +1 -1
  162. package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
  163. package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  164. package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  165. package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  166. package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  167. package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  168. package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +1 -1
  169. package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  170. package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  171. package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +1 -1
  172. package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  173. package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  174. package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +1 -1
  175. package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
  176. package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  177. package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  178. package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  179. package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  180. package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +1 -1
  181. package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  182. package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
  183. package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
  184. package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  185. package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
  186. package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  187. package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  188. package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  189. package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +1 -1
  190. package/packages/api-gateway/package.json +1 -1
  191. package/packages/api-gateway/src/index.ts +310 -87
  192. package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +1 -1
  193. package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
  194. package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -1
  195. package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
  196. package/packages/api-gateway/src/marketplace-entries/official/at-file.json +1 -1
  197. package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +1 -1
  198. package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +1 -1
  199. package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +1 -1
  200. package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +1 -1
  201. package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +1 -1
  202. package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
  203. package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +1 -1
  204. package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +1 -1
  205. package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +1 -1
  206. package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -1
  207. package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +1 -1
  208. package/packages/api-gateway/src/marketplace-entries/official/context.json +1 -1
  209. package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +1 -1
  210. package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +1 -1
  211. package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +1 -1
  212. package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +1 -1
  213. package/packages/api-gateway/src/marketplace-entries/official/genui.json +1 -1
  214. package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +1 -1
  215. package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +1 -1
  216. package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
  217. package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +3 -3
  218. package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +1 -1
  219. package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +1 -1
  220. package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +1 -1
  221. package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +1 -1
  222. package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +1 -1
  223. package/packages/api-gateway/src/marketplace-entries/official/memory.json +1 -1
  224. package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +1 -1
  225. package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +1 -1
  226. package/packages/api-gateway/src/marketplace-entries/official/modlens.json +1 -1
  227. package/packages/api-gateway/src/marketplace-entries/official/module-search.json +1 -1
  228. package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +1 -1
  229. package/packages/api-gateway/src/marketplace-entries/official/openpets.json +1 -1
  230. package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +1 -1
  231. package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +1 -1
  232. package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +1 -1
  233. package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +1 -1
  234. package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +1 -1
  235. package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +1 -1
  236. package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +1 -1
  237. package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +1 -1
  238. package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +1 -1
  239. package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +1 -1
  240. package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +1 -1
  241. package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +1 -1
  242. package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +1 -1
  243. package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +1 -1
  244. package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +1 -1
  245. package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +1 -1
  246. package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +1 -1
  247. package/packages/api-gateway/src/marketplace-entries/official/session-export.json +1 -1
  248. package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +1 -1
  249. package/packages/api-gateway/src/marketplace-entries/official/session-search.json +1 -1
  250. package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +1 -1
  251. package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +1 -1
  252. package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +1 -1
  253. package/packages/api-gateway/src/marketplace-entries/official/synapse.json +1 -1
  254. package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +1 -1
  255. package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +1 -1
  256. package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +1 -1
  257. package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +1 -1
  258. package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
  259. package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +1 -1
  260. package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +1 -1
  261. package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
  262. package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +1 -1
  263. package/packages/api-gateway/src/marketplace-entries/official/web-research.json +1 -1
  264. package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +1 -1
  265. package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +1 -1
  266. package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +1 -1
  267. package/packages/api-gateway/test/index.test.ts +799 -3
  268. package/packages/api-gateway/test/marketplace-schema.test.ts +139 -0
  269. package/packages/api-gateway/test/packed-package.test.ts +70 -0
  270. package/packages/api-gateway/test/reference-docs.test.ts +50 -0
  271. package/packages/api-gateway/test/release-contract.test.ts +77 -0
  272. package/packages/api-gateway/test/release-package.test.ts +33 -2
  273. package/packages/api-gateway/test/release-version.test.ts +46 -4
  274. package/packages/bundle-web-app/dist/index.d.ts +3 -0
  275. package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
  276. package/packages/bundle-web-app/dist/index.js +21 -8
  277. package/packages/bundle-web-app/dist/index.js.map +1 -1
  278. package/packages/bundle-web-app/package.json +1 -1
  279. package/packages/bundle-web-app/src/index.ts +25 -8
  280. package/packages/bundle-web-app/test/index.test.ts +116 -4
  281. package/packages/cli/dist/main.d.ts +1 -0
  282. package/packages/cli/dist/main.d.ts.map +1 -1
  283. package/packages/cli/dist/main.js +11 -0
  284. package/packages/cli/dist/main.js.map +1 -1
  285. package/packages/cli/dist/node-stdio.d.ts.map +1 -1
  286. package/packages/cli/dist/node-stdio.js +3 -1
  287. package/packages/cli/dist/node-stdio.js.map +1 -1
  288. package/packages/cli/dist/relaunch.d.ts.map +1 -1
  289. package/packages/cli/dist/relaunch.js +4 -1
  290. package/packages/cli/dist/relaunch.js.map +1 -1
  291. package/packages/cli/package.json +1 -1
  292. package/packages/cli/src/main.ts +11 -0
  293. package/packages/cli/src/node-stdio.ts +6 -3
  294. package/packages/cli/src/relaunch.ts +4 -1
  295. package/packages/cli/test/cli.test.ts +3 -1
  296. package/packages/cli/test/main.test.ts +134 -0
  297. package/packages/cli/test/node-stdio.test.ts +43 -0
  298. package/packages/cli/test/relaunch.test.ts +110 -0
  299. package/packages/host-webserver/dist/index.d.ts +1 -0
  300. package/packages/host-webserver/dist/index.d.ts.map +1 -1
  301. package/packages/host-webserver/dist/index.js +86 -3
  302. package/packages/host-webserver/dist/index.js.map +1 -1
  303. package/packages/host-webserver/package.json +1 -1
  304. package/packages/host-webserver/src/index.ts +95 -3
  305. package/packages/host-webserver/test/index.test.ts +160 -1
  306. package/apps/web/dist/assets/index-BVI_IS-R.css +0 -2
  307. package/apps/web/dist/assets/index-w0KQgv8x.js +0 -22
@@ -1,11 +1,12 @@
1
- import { execFile } from "node:child_process";
2
- import { mkdir, mkdtemp, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
1
+ import { execFile, type ExecFileException } from "node:child_process";
2
+ import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
3
3
  import type { IncomingMessage, ServerResponse } from "node:http";
4
- import { basename, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import type { Context } from "@deepseek-ai/cordis";
7
7
  import { SessionManager, type AgentSessionEvent, type ExtensionUIContext } from "@earendil-works/pi-coding-agent";
8
8
  import type Loader from "@deepseek-ai/cordis-plugin-loader";
9
+ import { atomicWriteFile } from "@pi-harness/core";
9
10
  import type { PiPluginUiRegistry, PiRuntimeService, PiModelsService, PiHarnessLaunch } from "@pi-harness/core";
10
11
  import type { WebServer } from "@pi-harness/host-webserver";
11
12
  import {
@@ -17,6 +18,7 @@ import {
17
18
  paginateMarketplace,
18
19
  searchMarketplace,
19
20
  needsMarketplacePackageInstall,
21
+ marketplaceNpmPackageName,
20
22
  sortMarketplaceByRecommendation,
21
23
  type MarketplacePlugin,
22
24
  } from "./marketplace.js";
@@ -33,23 +35,39 @@ interface ApiServices {
33
35
 
34
36
  const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
35
37
 
38
+ const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
39
+ const IMPORT_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
40
+ // The import body carries the JSONL content as a JSON string, so quotes and newlines are escaped; leave headroom above the content limit for that overhead.
41
+ const IMPORT_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
42
+ // Retained trajectory events are replayed to every SSE client and returned by /api/session, so the array must stay bounded.
43
+ const MAX_RETAINED_EVENTS = 2000;
44
+ const GIT_TIMEOUT_MS = 15_000;
45
+
46
+ class PayloadTooLargeError extends Error {
47
+ constructor() {
48
+ super("Request body is too large");
49
+ this.name = "PayloadTooLargeError";
50
+ }
51
+ }
52
+
36
53
  function sendJson(response: ServerResponse, status: number, payload: unknown): void {
37
54
  const body = JSON.stringify(payload);
38
55
  response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) });
39
56
  response.end(body);
40
57
  }
41
58
 
42
- async function bodyText(request: IncomingMessage): Promise<string> {
43
- const chunks: string[] = [];
59
+ // A multi-byte UTF-8 sequence can straddle a chunk boundary, so the raw bytes are collected and decoded once; decoding each chunk on its own would replace the split sequence with U+FFFD.
60
+ async function bodyText(request: IncomingMessage, maxBytes = DEFAULT_BODY_LIMIT_BYTES): Promise<string> {
61
+ const chunks: Buffer[] = [];
44
62
  let length = 0;
45
63
  for await (const chunk of request) {
46
64
  const input: string | Uint8Array = chunk as string | Uint8Array;
47
- const value = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
48
- length += Buffer.byteLength(value);
49
- if (length > 64 * 1024) throw new Error("Request body is too large");
50
- chunks.push(value);
65
+ const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
66
+ length += buffer.length;
67
+ if (length > maxBytes) throw new PayloadTooLargeError();
68
+ chunks.push(buffer);
51
69
  }
52
- return chunks.join("");
70
+ return Buffer.concat(chunks).toString("utf8");
53
71
  }
54
72
 
55
73
  function errorText(error: unknown): string {
@@ -64,9 +82,14 @@ function jsonSafe(value: unknown, seen = new WeakSet<object>()): unknown {
64
82
  if (value instanceof Date) return value.toISOString();
65
83
  if (typeof value !== "object") return Object.prototype.toString.call(value);
66
84
  if (seen.has(value)) return "[Circular]";
85
+ // Only the ancestors of the current node stay marked: a sibling that repeats the same object (Pi pushes one AgentMessage instance into both the message list and its events) is legitimate data, not a cycle.
67
86
  seen.add(value);
68
- if (Array.isArray(value)) return value.map((item) => jsonSafe(item, seen));
69
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
87
+ try {
88
+ if (Array.isArray(value)) return value.map((item) => jsonSafe(item, seen));
89
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
90
+ } finally {
91
+ seen.delete(value);
92
+ }
70
93
  }
71
94
 
72
95
  function createStatus(services: ApiServices, events: readonly AgentSessionEvent[]) {
@@ -139,26 +162,67 @@ function sessionMetadataFile(manager: SessionManager): string {
139
162
  return resolve(manager.getSessionDir(), ".pi-harness-session-meta.json");
140
163
  }
141
164
 
142
- async function readSessionMetadata(manager: SessionManager): Promise<SessionMetadataMap> {
165
+ interface MetadataLogger {
166
+ warn(message: string): void;
167
+ }
168
+
169
+ // A missing file is the normal first-run state. Any other read or parse failure means the file exists but is unusable; it is renamed aside so the wreckage survives for recovery and the next write cannot silently replace the only copy.
170
+ async function readSessionMetadata(manager: SessionManager, logger: MetadataLogger): Promise<SessionMetadataMap> {
171
+ const file = sessionMetadataFile(manager);
172
+ let raw: string;
173
+ try {
174
+ raw = await readFile(file, "utf8");
175
+ } catch (error) {
176
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
177
+ await quarantineSessionMetadata(file, logger, error);
178
+ return {};
179
+ }
143
180
  try {
144
- const parsed: unknown = JSON.parse(await readFile(sessionMetadataFile(manager), "utf8"));
145
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
181
+ const parsed: unknown = JSON.parse(raw);
182
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("session metadata must be a JSON object");
146
183
  return parsed as SessionMetadataMap;
147
- } catch {
184
+ } catch (error) {
185
+ await quarantineSessionMetadata(file, logger, error);
148
186
  return {};
149
187
  }
150
188
  }
151
189
 
190
+ async function quarantineSessionMetadata(file: string, logger: MetadataLogger, error: unknown): Promise<void> {
191
+ const quarantined = `${file}.corrupt-${Date.now()}`;
192
+ try {
193
+ await rename(file, quarantined);
194
+ logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}); moved it to ${quarantined} and starting from an empty map`);
195
+ } catch (renameError) {
196
+ logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}) and could not be quarantined: ${errorText(renameError)}`);
197
+ }
198
+ }
199
+
152
200
  async function writeSessionMetadata(manager: SessionManager, metadata: SessionMetadataMap): Promise<void> {
153
201
  await mkdir(manager.getSessionDir(), { recursive: true });
154
- await writeFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", "utf8");
202
+ await atomicWriteFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", { encoding: "utf8" });
203
+ }
204
+
205
+ // Every read-modify-write of the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates.
206
+ let sessionMetadataQueue: Promise<unknown> = Promise.resolve();
207
+
208
+ function mutateSessionMetadata<T>(manager: SessionManager, logger: MetadataLogger, mutate: (metadata: SessionMetadataMap) => Promise<T> | T): Promise<T> {
209
+ const operation = sessionMetadataQueue
210
+ .catch(() => undefined)
211
+ .then(async () => {
212
+ const metadata = await readSessionMetadata(manager, logger);
213
+ const result = await mutate(metadata);
214
+ await writeSessionMetadata(manager, metadata);
215
+ return result;
216
+ });
217
+ sessionMetadataQueue = operation;
218
+ return operation;
155
219
  }
156
220
 
157
221
  function sessionPathInDirectory(path: string, manager: SessionManager): boolean {
158
222
  const root = resolve(manager.getSessionDir());
159
223
  const target = resolve(path);
160
224
  const relativePath = relative(root, target);
161
- return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + "/") && target.endsWith(".jsonl");
225
+ return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + sep) && target.endsWith(".jsonl");
162
226
  }
163
227
 
164
228
  const webSessionUi = {
@@ -293,6 +357,7 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
293
357
  if (typeof value !== "object") return Object.prototype.toString.call(value);
294
358
  if (depth >= loggerPanelDepthLimit) return "[Max Depth]";
295
359
  if (state.seen.has(value)) return "[Circular]";
360
+ // Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
296
361
  state.seen.add(value);
297
362
 
298
363
  try {
@@ -322,6 +387,8 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
322
387
  return Object.fromEntries(entries);
323
388
  } catch {
324
389
  return "[Unavailable]";
390
+ } finally {
391
+ state.seen.delete(value);
325
392
  }
326
393
  }
327
394
 
@@ -452,25 +519,70 @@ function writeSse(response: ServerResponse, payload: unknown): void {
452
519
  response.write(`data: ${JSON.stringify(jsonSafe(payload))}\n\n`);
453
520
  }
454
521
 
455
- function gitStatus(cwd: string): Promise<string> {
456
- return new Promise((resolve) => {
457
- execFile("git", ["status", "--short", "--untracked-files=all"], { cwd, maxBuffer: 512 * 1024 }, (error, stdout) => resolve(error ? "" : stdout));
522
+ type GitTermination = "timeout" | "output-limit";
523
+
524
+ // Node reports a maxBuffer overflow through a string error code and a timeout through `killed`; neither is a git exit status, so they must not be folded into an ordinary non-zero exit.
525
+ function gitTermination(error: ExecFileException | null): GitTermination | undefined {
526
+ if (error === null) return undefined;
527
+ if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return "output-limit";
528
+ if (error.killed === true) return "timeout";
529
+ return undefined;
530
+ }
531
+
532
+ function gitTerminationMessage(command: string, termination: GitTermination): string {
533
+ return termination === "timeout"
534
+ ? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
535
+ : `git ${command} produced more output than the buffer limit allows`;
536
+ }
537
+
538
+ interface GitStatusResult {
539
+ readonly output: string;
540
+ readonly truncated: boolean;
541
+ }
542
+
543
+ // `-z` is what makes the paths usable: without it git wraps any path holding a space, a quote or a non-ASCII byte in C-style quoting, and the quoted form is not a pathspec git will accept back on diff, add or ls-files.
544
+ function gitStatus(cwd: string): Promise<GitStatusResult> {
545
+ return new Promise((resolveStatus, rejectStatus) => {
546
+ execFile("git", ["status", "--short", "-z", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
547
+ if (error === null) {
548
+ resolveStatus({ output: stdout, truncated: false });
549
+ return;
550
+ }
551
+ const termination = gitTermination(error);
552
+ if (termination === "output-limit") {
553
+ // Keep the complete records that arrived before the kill; the final one may have been cut mid-path.
554
+ resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
555
+ return;
556
+ }
557
+ if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
558
+ resolveStatus({ output: "", truncated: false });
559
+ return;
560
+ }
561
+ rejectStatus(new Error(termination ? gitTerminationMessage("status", termination) : stderr.trim() || error.message, { cause: error }));
562
+ });
458
563
  });
459
564
  }
460
565
 
461
566
  function gitDiff(cwd: string, path: string): Promise<string> {
462
567
  return new Promise((resolveOutput) => {
463
- execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, maxBuffer: 1024 * 1024 }, (error, stdout) =>
568
+ execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) =>
464
569
  resolveOutput(error && stdout.length === 0 ? "" : stdout),
465
570
  );
466
571
  });
467
572
  }
468
573
 
469
- function gitCommand(cwd: string, args: readonly string[]): Promise<{ stdout: string; stderr: string; code: number }> {
574
+ interface GitCommandResult {
575
+ readonly stdout: string;
576
+ readonly stderr: string;
577
+ readonly code: number;
578
+ readonly terminated: GitTermination | undefined;
579
+ }
580
+
581
+ function gitCommand(cwd: string, args: readonly string[]): Promise<GitCommandResult> {
470
582
  return new Promise((resolveResult) => {
471
- execFile("git", [...args], { cwd, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
583
+ execFile("git", [...args], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
472
584
  const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
473
- resolveResult({ stdout, stderr, code });
585
+ resolveResult({ stdout, stderr, code, terminated: gitTermination(error) });
474
586
  });
475
587
  });
476
588
  }
@@ -509,6 +621,26 @@ async function isDirectory(path: string): Promise<boolean> {
509
621
  }
510
622
  }
511
623
 
624
+ async function isFile(path: string): Promise<boolean> {
625
+ try {
626
+ return (await stat(path)).isFile();
627
+ } catch {
628
+ return false;
629
+ }
630
+ }
631
+
632
+ // The loader resolves a bare plugin specifier from the profile file upwards (`createRequire(profilePath).resolve(name)`), never from the shell the harness was started in, so a marketplace package has to land in the package that owns the profile or it installs successfully and then fails to load.
633
+ async function marketplaceInstallDirectory(configPath: string, fallback: string): Promise<string> {
634
+ let directory = dirname(resolve(configPath));
635
+ let parent = dirname(directory);
636
+ while (parent !== directory) {
637
+ if (await isFile(join(directory, "package.json"))) return directory;
638
+ directory = parent;
639
+ parent = dirname(directory);
640
+ }
641
+ return fallback;
642
+ }
643
+
512
644
  function pickDirectory(): Promise<string> {
513
645
  return new Promise((resolvePath, reject) => {
514
646
  execFile(
@@ -559,7 +691,11 @@ export default {
559
691
  const events: AgentSessionEvent[] = [];
560
692
  const eventClients = new Set<ServerResponse>();
561
693
  const handleEvent = (event: AgentSessionEvent) => {
562
- events.push(event);
694
+ // Streaming deltas reach clients live over SSE and each one carries the whole partial message, so only durable events are retained for snapshots.
695
+ if (event.type !== "message_update") {
696
+ events.push(event);
697
+ if (events.length > MAX_RETAINED_EVENTS) events.splice(0, events.length - MAX_RETAINED_EVENTS);
698
+ }
563
699
  for (const response of eventClients) {
564
700
  if (response.writableEnded || response.destroyed) {
565
701
  eventClients.delete(response);
@@ -686,7 +822,12 @@ export default {
686
822
  return;
687
823
  }
688
824
  const path = join(services.launch.agentDir, "settings.json");
689
- await writeFile(path, JSON.stringify(parsed, null, 2) + "\n", "utf8");
825
+ // Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits.
826
+ const mode = await stat(path).then(
827
+ (info) => info.mode & 0o777,
828
+ () => 0o644,
829
+ );
830
+ await atomicWriteFile(path, JSON.stringify(parsed, null, 2) + "\n", { encoding: "utf8", mode });
690
831
  await services.runtime.session.settingsManager.reload();
691
832
  sendJson(response, 200, piConfig(services));
692
833
  } catch (error) {
@@ -963,15 +1104,17 @@ export default {
963
1104
  sendJson(response, 409, { error: "Plugin is already installed", plugin });
964
1105
  return;
965
1106
  }
966
- const packageJsonPath = join(services.launch.cwd, "package.json");
967
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1107
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1108
+ const packageJsonPath = join(installDirectory, "package.json");
1109
+ const packageLockPath = join(installDirectory, "package-lock.json");
968
1110
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
969
1111
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
970
1112
  let profileBefore: string | undefined;
971
1113
  let entryId: string | undefined;
972
1114
  try {
973
1115
  if (needsMarketplacePackageInstall(plugin)) {
974
- await runProcess("npm", ["install", "--save-exact", "--package-lock=false", `${plugin.packageName}@${plugin.version}`], services.launch.cwd);
1116
+ const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
1117
+ await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
975
1118
  }
976
1119
  profileBefore = await appendMarketplaceProfile(configPath, plugin);
977
1120
  if (!needsMarketplacePackageInstall(plugin)) {
@@ -991,8 +1134,11 @@ export default {
991
1134
  } catch (error) {
992
1135
  if (entryId !== undefined) await loader.remove(entryId).catch(() => {});
993
1136
  if (profileBefore !== undefined) await writeFile(configPath, profileBefore, "utf8").catch(() => {});
994
- if (packageJsonBefore !== undefined) await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => {});
995
- if (packageLockBefore !== undefined) await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => {});
1137
+ // A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
1138
+ if (packageJsonBefore === undefined) await unlink(packageJsonPath).catch(() => {});
1139
+ else await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => {});
1140
+ if (packageLockBefore === undefined) await unlink(packageLockPath).catch(() => {});
1141
+ else await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => {});
996
1142
  sendJson(response, 502, { error: errorText(error) });
997
1143
  }
998
1144
  } catch (error) {
@@ -1088,15 +1234,17 @@ export default {
1088
1234
  }
1089
1235
  const profileEntryId = entry.options.id;
1090
1236
  const profileBefore = await readFile(configPath, "utf8");
1091
- const packageJsonPath = join(services.launch.cwd, "package.json");
1092
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1237
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1238
+ const packageJsonPath = join(installDirectory, "package.json");
1239
+ const packageLockPath = join(installDirectory, "package-lock.json");
1093
1240
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
1094
1241
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
1095
1242
  await entry.parent.remove(entry.options.id);
1096
1243
  entry.parent.tree.write();
1097
1244
  try {
1098
1245
  await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
1099
- if (needsMarketplacePackageInstall(plugin)) await runProcess("npm", ["uninstall", "--package-lock=false", plugin.packageName], services.launch.cwd);
1246
+ if (needsMarketplacePackageInstall(plugin))
1247
+ await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
1100
1248
  sendJson(response, 200, { uninstalled: true, id: payload.id });
1101
1249
  } catch (error) {
1102
1250
  await writeFile(configPath, profileBefore, "utf8").catch(() => {});
@@ -1192,20 +1340,28 @@ export default {
1192
1340
  const disposeFiles = services.webServer.register({
1193
1341
  path: "/api/files",
1194
1342
  async handler(_request, response) {
1195
- const output = await gitStatus(activeCwd(services));
1196
- const items = output
1197
- .split("\n")
1198
- .map((line) => line.trimEnd())
1199
- .filter((line) => line.length > 0)
1200
- .map((line) => {
1201
- const status = line.slice(0, 2).trim() || "??";
1202
- return {
1203
- path: line.slice(3),
1204
- status,
1205
- label: status === "??" ? "untracked" : status.includes("D") ? "deleted" : status.includes("A") ? "added" : "modified",
1206
- };
1343
+ let status: GitStatusResult;
1344
+ try {
1345
+ status = await gitStatus(activeCwd(services));
1346
+ } catch (error) {
1347
+ sendJson(response, 500, { error: errorText(error) });
1348
+ return;
1349
+ }
1350
+ const records = status.output.split("\0");
1351
+ const items: { path: string; status: string; label: string }[] = [];
1352
+ for (let index = 0; index < records.length; index += 1) {
1353
+ const record = records[index] ?? "";
1354
+ if (record.length < 4) continue;
1355
+ const code = record.slice(0, 2).trim() || "??";
1356
+ // Rename and copy records carry the original path in a second NUL-terminated field, which is not a workspace change of its own.
1357
+ if (code.startsWith("R") || code.startsWith("C")) index += 1;
1358
+ items.push({
1359
+ path: record.slice(3),
1360
+ status: code,
1361
+ label: code === "??" ? "untracked" : code.includes("D") ? "deleted" : code.includes("A") ? "added" : "modified",
1207
1362
  });
1208
- sendJson(response, 200, { items });
1363
+ }
1364
+ sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
1209
1365
  },
1210
1366
  });
1211
1367
  const disposeFileDiff = services.webServer.register({
@@ -1224,7 +1380,7 @@ export default {
1224
1380
  const root = resolve(activeCwd(services));
1225
1381
  const absolute = resolve(root, requested);
1226
1382
  const relativePath = relative(root, absolute);
1227
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + "/")) {
1383
+ if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep)) {
1228
1384
  sendJson(response, 400, { error: "path must stay inside the workspace" });
1229
1385
  return;
1230
1386
  }
@@ -1251,11 +1407,19 @@ export default {
1251
1407
  }
1252
1408
  const root = resolve(activeCwd(services));
1253
1409
  const add = await gitCommand(root, ["add", "-A", "--", ...paths]);
1410
+ if (add.terminated) {
1411
+ sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated) });
1412
+ return;
1413
+ }
1254
1414
  if (add.code !== 0) {
1255
1415
  sendJson(response, 409, { error: add.stderr.trim() || "Unable to stage workspace files" });
1256
1416
  return;
1257
1417
  }
1258
1418
  const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths]);
1419
+ if (commit.terminated) {
1420
+ sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated) });
1421
+ return;
1422
+ }
1259
1423
  if (commit.code !== 0) {
1260
1424
  sendJson(response, 409, { error: commit.stdout.trim() || commit.stderr.trim() || "Nothing to commit" });
1261
1425
  return;
@@ -1286,16 +1450,36 @@ export default {
1286
1450
  sendJson(response, 400, { error: paths.message });
1287
1451
  return;
1288
1452
  }
1289
- const untracked = await gitCommand(root, ["ls-files", "--others", "--exclude-standard", "--", ...paths]);
1290
- const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...paths]);
1291
- if (restore.code !== 0 && !restore.stderr.includes("pathspec")) {
1292
- sendJson(response, 409, { error: restore.stderr.trim() || "Unable to restore workspace files" });
1453
+ const untracked = await gitCommand(root, ["ls-files", "-z", "--others", "--exclude-standard", "--", ...paths]);
1454
+ // `git restore` refuses the whole request when any pathspec is unknown to it, so restore only the paths git tracks. `--with-tree=HEAD` keeps staged deletions in the list; it fails on an unborn HEAD, where the plain index listing is all there is.
1455
+ let tracked = await gitCommand(root, ["ls-files", "-z", "--with-tree=HEAD", "--", ...paths]);
1456
+ if (tracked.code !== 0 && tracked.terminated === undefined) tracked = await gitCommand(root, ["ls-files", "-z", "--", ...paths]);
1457
+ const listing = [untracked, tracked].find((result) => result.terminated !== undefined || result.code !== 0);
1458
+ if (listing) {
1459
+ if (listing.terminated) sendJson(response, 500, { error: gitTerminationMessage("ls-files", listing.terminated) });
1460
+ else sendJson(response, 409, { error: listing.stderr.trim() || "Unable to inspect workspace files" });
1461
+ return;
1462
+ }
1463
+ const untrackedPaths = untracked.stdout.split("\0").filter(Boolean);
1464
+ const trackedPaths = tracked.stdout.split("\0").filter(Boolean);
1465
+ const known = [...untrackedPaths, ...trackedPaths];
1466
+ const unknown = paths.filter((path) => !known.some((candidate) => candidate === path || candidate.startsWith(path + "/")));
1467
+ if (unknown.length > 0) {
1468
+ sendJson(response, 409, { error: `Paths did not match any file known to git: ${unknown.join(", ")}` });
1293
1469
  return;
1294
1470
  }
1295
- for (const path of untracked.stdout
1296
- .split("\n")
1297
- .map((item) => item.trim())
1298
- .filter(Boolean)) {
1471
+ if (trackedPaths.length > 0) {
1472
+ const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths]);
1473
+ if (restore.terminated) {
1474
+ sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated) });
1475
+ return;
1476
+ }
1477
+ if (restore.code !== 0) {
1478
+ sendJson(response, 409, { error: restore.stderr.trim() || "Unable to restore workspace files" });
1479
+ return;
1480
+ }
1481
+ }
1482
+ for (const path of untrackedPaths) {
1299
1483
  await rm(resolve(root, path), { recursive: true, force: true });
1300
1484
  }
1301
1485
  sendJson(response, 200, { reverted: true, paths });
@@ -1332,6 +1516,10 @@ export default {
1332
1516
  const disposePrompt = services.webServer.register({
1333
1517
  path: "/api/prompt",
1334
1518
  async handler(request, response) {
1519
+ if (request.method !== "POST") {
1520
+ sendJson(response, 405, { error: "Method not allowed" });
1521
+ return;
1522
+ }
1335
1523
  if (busy) {
1336
1524
  sendJson(response, 409, { error: "Another prompt is already running" });
1337
1525
  return;
@@ -1583,9 +1771,9 @@ export default {
1583
1771
  events.length = 0;
1584
1772
  }
1585
1773
  await unlink(path);
1586
- const metadata = await readSessionMetadata(manager);
1587
- delete metadata[path];
1588
- await writeSessionMetadata(manager, metadata);
1774
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1775
+ delete metadata[path];
1776
+ });
1589
1777
  sendJson(response, 200, { deleted: true, path, sessionFile: services.runtime.session.sessionFile });
1590
1778
  } catch (error) {
1591
1779
  sendJson(response, 400, { error: errorText(error) });
@@ -1607,16 +1795,17 @@ export default {
1607
1795
  sendJson(response, 400, { error: "Invalid session path" });
1608
1796
  return;
1609
1797
  }
1610
- const metadata = await readSessionMetadata(manager);
1611
- const currentMetadata = metadata[path] ?? {};
1612
- const nextMetadata: SessionMetadata = {
1613
- ...currentMetadata,
1614
- ...(payload.archived === undefined ? {} : { archived: payload.archived === true }),
1615
- ...(payload.pinned === undefined ? {} : { pinned: payload.pinned === true }),
1616
- };
1617
- metadata[path] = nextMetadata;
1618
- await writeSessionMetadata(manager, metadata);
1619
- sendJson(response, 200, { path, metadata: metadata[path] });
1798
+ const nextMetadata = await mutateSessionMetadata(manager, context.logger, (metadata) => {
1799
+ const currentMetadata = metadata[path] ?? {};
1800
+ const updated: SessionMetadata = {
1801
+ ...currentMetadata,
1802
+ ...(payload.archived === undefined ? {} : { archived: payload.archived === true }),
1803
+ ...(payload.pinned === undefined ? {} : { pinned: payload.pinned === true }),
1804
+ };
1805
+ metadata[path] = updated;
1806
+ return updated;
1807
+ });
1808
+ sendJson(response, 200, { path, metadata: nextMetadata });
1620
1809
  } catch (error) {
1621
1810
  sendJson(response, 400, { error: errorText(error) });
1622
1811
  }
@@ -1654,12 +1843,37 @@ export default {
1654
1843
  sendJson(response, 409, { error: "Cannot batch-delete the active session" });
1655
1844
  return;
1656
1845
  }
1657
- const metadata = await readSessionMetadata(manager);
1658
- for (const path of paths) {
1659
- if (action === "delete") {
1660
- await unlink(path);
1661
- delete metadata[path];
1662
- } else {
1846
+ if (action === "delete") {
1847
+ // The unlinks run outside the metadata mutation: a failure halfway through must not discard the key removals for the files that are already gone from disk.
1848
+ const removed: string[] = [];
1849
+ const failed: { path: string; error: string }[] = [];
1850
+ for (const path of paths) {
1851
+ try {
1852
+ await unlink(path);
1853
+ removed.push(path);
1854
+ } catch (error) {
1855
+ // A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
1856
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") removed.push(path);
1857
+ else failed.push({ path, error: errorText(error) });
1858
+ }
1859
+ }
1860
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1861
+ for (const path of removed) delete metadata[path];
1862
+ });
1863
+ if (failed.length > 0) {
1864
+ sendJson(response, 409, {
1865
+ error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
1866
+ action,
1867
+ count: removed.length,
1868
+ failed,
1869
+ });
1870
+ return;
1871
+ }
1872
+ sendJson(response, 200, { action, count: removed.length });
1873
+ return;
1874
+ }
1875
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1876
+ for (const path of paths) {
1663
1877
  const current = metadata[path] ?? {};
1664
1878
  metadata[path] = {
1665
1879
  ...current,
@@ -1667,8 +1881,7 @@ export default {
1667
1881
  ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1668
1882
  };
1669
1883
  }
1670
- }
1671
- await writeSessionMetadata(manager, metadata);
1884
+ });
1672
1885
  sendJson(response, 200, { action, count: paths.length });
1673
1886
  } catch (error) {
1674
1887
  sendJson(response, 400, { error: errorText(error) });
@@ -1716,11 +1929,21 @@ export default {
1716
1929
  return;
1717
1930
  }
1718
1931
  try {
1719
- const payload = JSON.parse(await bodyText(request)) as { path?: unknown; content?: unknown; filename?: unknown; cwd?: unknown };
1932
+ const payload = JSON.parse(await bodyText(request, IMPORT_BODY_LIMIT_BYTES)) as {
1933
+ path?: unknown;
1934
+ content?: unknown;
1935
+ filename?: unknown;
1936
+ cwd?: unknown;
1937
+ };
1720
1938
  const suppliedPath = typeof payload.path === "string" ? payload.path : "";
1721
1939
  const content = typeof payload.content === "string" ? payload.content : undefined;
1722
- if ((!suppliedPath || !isAbsolute(suppliedPath)) && content === undefined) {
1723
- sendJson(response, 400, { error: "A JSONL file path or file content is required" });
1940
+ // `path` reads an existing file and `content` is written to a private temporary file; accepting both would let the request choose where the content lands.
1941
+ if (suppliedPath && content !== undefined) {
1942
+ sendJson(response, 400, { error: "Provide either a JSONL file path or file content, not both" });
1943
+ return;
1944
+ }
1945
+ if (content === undefined && (!suppliedPath || !isAbsolute(suppliedPath) || !suppliedPath.endsWith(".jsonl"))) {
1946
+ sendJson(response, 400, { error: "An absolute .jsonl file path or file content is required" });
1724
1947
  return;
1725
1948
  }
1726
1949
  const manager = services.runtime.session.sessionManager;
@@ -1730,12 +1953,12 @@ export default {
1730
1953
  }
1731
1954
  const targetCwd = typeof payload.cwd === "string" && payload.cwd.trim() ? resolve(payload.cwd) : activeCwd(services);
1732
1955
  const temporaryDirectory = content === undefined ? undefined : await mkdtemp(join(tmpdir(), "pi-harness-import-"));
1733
- const importPath =
1734
- suppliedPath ||
1735
- join(temporaryDirectory as string, basename(typeof payload.filename === "string" && payload.filename.trim() ? payload.filename : "import.jsonl"));
1956
+ const requestedName = basename(typeof payload.filename === "string" && payload.filename.trim() ? payload.filename : "import.jsonl");
1957
+ const importName = requestedName === "" || requestedName === "." || requestedName === ".." ? "import.jsonl" : requestedName;
1958
+ const importPath = temporaryDirectory === undefined ? suppliedPath : join(temporaryDirectory, importName);
1736
1959
  try {
1737
1960
  if (content !== undefined) {
1738
- if (Buffer.byteLength(content, "utf8") > 10 * 1024 * 1024) {
1961
+ if (Buffer.byteLength(content, "utf8") > IMPORT_CONTENT_LIMIT_BYTES) {
1739
1962
  sendJson(response, 413, { error: "Imported session must be at most 10 MiB" });
1740
1963
  return;
1741
1964
  }
@@ -1759,7 +1982,7 @@ export default {
1759
1982
  if (temporaryDirectory) await rm(temporaryDirectory, { recursive: true, force: true });
1760
1983
  }
1761
1984
  } catch (error) {
1762
- sendJson(response, 400, { error: errorText(error) });
1985
+ sendJson(response, error instanceof PayloadTooLargeError ? 413 : 400, { error: errorText(error) });
1763
1986
  }
1764
1987
  },
1765
1988
  });
@@ -1796,7 +2019,7 @@ export default {
1796
2019
  const page = Math.max(0, Number.parseInt(url.searchParams.get("page") ?? "0", 10) || 0);
1797
2020
  const pageSize = Math.min(100, Math.max(1, Number.parseInt(url.searchParams.get("pageSize") ?? "50", 10) || 50));
1798
2021
  const includeArchived = url.searchParams.get("includeArchived") === "true";
1799
- const metadata = await readSessionMetadata(manager);
2022
+ const metadata = await readSessionMetadata(manager, context.logger);
1800
2023
  const items =
1801
2024
  typeof manager.isPersisted === "function" && manager.isPersisted() ? await SessionManager.list(services.launch.cwd, manager.getSessionDir()) : [];
1802
2025
  const filtered = items.filter((item) => includeArchived || metadata[item.path]?.archived !== true);