@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,28 +1,43 @@
1
1
  import { execFile } from "node:child_process";
2
- import { mkdir, mkdtemp, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
3
- import { basename, isAbsolute, join, relative, resolve } from "node:path";
2
+ import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { SessionManager } from "@earendil-works/pi-coding-agent";
6
- import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, sortMarketplaceByRecommendation, } from "./marketplace.js";
6
+ import { atomicWriteFile } from "@pi-harness/core";
7
+ import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, marketplaceNpmPackageName, sortMarketplaceByRecommendation, } from "./marketplace.js";
7
8
  import { parseGitWorktrees } from "./workspaces.js";
8
9
  const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
10
+ const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
11
+ const IMPORT_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
12
+ // 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.
13
+ const IMPORT_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
14
+ // Retained trajectory events are replayed to every SSE client and returned by /api/session, so the array must stay bounded.
15
+ const MAX_RETAINED_EVENTS = 2000;
16
+ const GIT_TIMEOUT_MS = 15_000;
17
+ class PayloadTooLargeError extends Error {
18
+ constructor() {
19
+ super("Request body is too large");
20
+ this.name = "PayloadTooLargeError";
21
+ }
22
+ }
9
23
  function sendJson(response, status, payload) {
10
24
  const body = JSON.stringify(payload);
11
25
  response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) });
12
26
  response.end(body);
13
27
  }
14
- async function bodyText(request) {
28
+ // 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.
29
+ async function bodyText(request, maxBytes = DEFAULT_BODY_LIMIT_BYTES) {
15
30
  const chunks = [];
16
31
  let length = 0;
17
32
  for await (const chunk of request) {
18
33
  const input = chunk;
19
- const value = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
20
- length += Buffer.byteLength(value);
21
- if (length > 64 * 1024)
22
- throw new Error("Request body is too large");
23
- chunks.push(value);
34
+ const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
35
+ length += buffer.length;
36
+ if (length > maxBytes)
37
+ throw new PayloadTooLargeError();
38
+ chunks.push(buffer);
24
39
  }
25
- return chunks.join("");
40
+ return Buffer.concat(chunks).toString("utf8");
26
41
  }
27
42
  function errorText(error) {
28
43
  return error instanceof Error ? error.message : String(error);
@@ -42,10 +57,16 @@ function jsonSafe(value, seen = new WeakSet()) {
42
57
  return Object.prototype.toString.call(value);
43
58
  if (seen.has(value))
44
59
  return "[Circular]";
60
+ // 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.
45
61
  seen.add(value);
46
- if (Array.isArray(value))
47
- return value.map((item) => jsonSafe(item, seen));
48
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
62
+ try {
63
+ if (Array.isArray(value))
64
+ return value.map((item) => jsonSafe(item, seen));
65
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
66
+ }
67
+ finally {
68
+ seen.delete(value);
69
+ }
49
70
  }
50
71
  function createStatus(services, events) {
51
72
  const activeModel = services.runtime.session.model ?? services.models.model;
@@ -106,26 +127,63 @@ function piConfig(services) {
106
127
  function sessionMetadataFile(manager) {
107
128
  return resolve(manager.getSessionDir(), ".pi-harness-session-meta.json");
108
129
  }
109
- async function readSessionMetadata(manager) {
130
+ // 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.
131
+ async function readSessionMetadata(manager, logger) {
132
+ const file = sessionMetadataFile(manager);
133
+ let raw;
110
134
  try {
111
- const parsed = JSON.parse(await readFile(sessionMetadataFile(manager), "utf8"));
112
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
135
+ raw = await readFile(file, "utf8");
136
+ }
137
+ catch (error) {
138
+ if (error.code === "ENOENT")
113
139
  return {};
140
+ await quarantineSessionMetadata(file, logger, error);
141
+ return {};
142
+ }
143
+ try {
144
+ const parsed = JSON.parse(raw);
145
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
146
+ throw new Error("session metadata must be a JSON object");
114
147
  return parsed;
115
148
  }
116
- catch {
149
+ catch (error) {
150
+ await quarantineSessionMetadata(file, logger, error);
117
151
  return {};
118
152
  }
119
153
  }
154
+ async function quarantineSessionMetadata(file, logger, error) {
155
+ const quarantined = `${file}.corrupt-${Date.now()}`;
156
+ try {
157
+ await rename(file, quarantined);
158
+ logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}); moved it to ${quarantined} and starting from an empty map`);
159
+ }
160
+ catch (renameError) {
161
+ logger.warn(`Session metadata at ${file} is unreadable (${errorText(error)}) and could not be quarantined: ${errorText(renameError)}`);
162
+ }
163
+ }
120
164
  async function writeSessionMetadata(manager, metadata) {
121
165
  await mkdir(manager.getSessionDir(), { recursive: true });
122
- await writeFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", "utf8");
166
+ await atomicWriteFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", { encoding: "utf8" });
167
+ }
168
+ // Every read-modify-write of the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates.
169
+ let sessionMetadataQueue = Promise.resolve();
170
+ function mutateSessionMetadata(manager, logger, mutate) {
171
+ const operation = sessionMetadataQueue
172
+ .catch(() => undefined)
173
+ .then(async () => {
174
+ const metadata = await readSessionMetadata(manager, logger);
175
+ const result = await mutate(metadata);
176
+ await writeSessionMetadata(manager, metadata);
177
+ return result;
178
+ });
179
+ sessionMetadataQueue = operation;
180
+ return operation;
123
181
  }
124
182
  function sessionPathInDirectory(path, manager) {
125
183
  const root = resolve(manager.getSessionDir());
126
184
  const target = resolve(path);
127
185
  const relativePath = relative(root, target);
128
- return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + "/") && target.endsWith(".jsonl");
186
+ return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + sep) && target.endsWith(".jsonl");
129
187
  }
130
188
  const webSessionUi = {
131
189
  select: (_title, options) => Promise.resolve(options[0]),
@@ -255,6 +313,7 @@ function sanitizeLogValue(value, state, depth = 0) {
255
313
  return "[Max Depth]";
256
314
  if (state.seen.has(value))
257
315
  return "[Circular]";
316
+ // Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
258
317
  state.seen.add(value);
259
318
  try {
260
319
  if (value instanceof Date)
@@ -288,6 +347,9 @@ function sanitizeLogValue(value, state, depth = 0) {
288
347
  catch {
289
348
  return "[Unavailable]";
290
349
  }
350
+ finally {
351
+ state.seen.delete(value);
352
+ }
291
353
  }
292
354
  function sanitizeLogArguments(args) {
293
355
  const sanitized = sanitizeLogValue(args, { remaining: loggerPanelNodeLimit, seen: new WeakSet() });
@@ -412,21 +474,53 @@ async function updateMarketplaceProfile(configPath, entryId, update) {
412
474
  function writeSse(response, payload) {
413
475
  response.write(`data: ${JSON.stringify(jsonSafe(payload))}\n\n`);
414
476
  }
477
+ // 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.
478
+ function gitTermination(error) {
479
+ if (error === null)
480
+ return undefined;
481
+ if (error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER")
482
+ return "output-limit";
483
+ if (error.killed === true)
484
+ return "timeout";
485
+ return undefined;
486
+ }
487
+ function gitTerminationMessage(command, termination) {
488
+ return termination === "timeout"
489
+ ? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
490
+ : `git ${command} produced more output than the buffer limit allows`;
491
+ }
492
+ // `-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.
415
493
  function gitStatus(cwd) {
416
- return new Promise((resolve) => {
417
- execFile("git", ["status", "--short", "--untracked-files=all"], { cwd, maxBuffer: 512 * 1024 }, (error, stdout) => resolve(error ? "" : stdout));
494
+ return new Promise((resolveStatus, rejectStatus) => {
495
+ execFile("git", ["status", "--short", "-z", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
496
+ if (error === null) {
497
+ resolveStatus({ output: stdout, truncated: false });
498
+ return;
499
+ }
500
+ const termination = gitTermination(error);
501
+ if (termination === "output-limit") {
502
+ // Keep the complete records that arrived before the kill; the final one may have been cut mid-path.
503
+ resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
504
+ return;
505
+ }
506
+ if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
507
+ resolveStatus({ output: "", truncated: false });
508
+ return;
509
+ }
510
+ rejectStatus(new Error(termination ? gitTerminationMessage("status", termination) : stderr.trim() || error.message, { cause: error }));
511
+ });
418
512
  });
419
513
  }
420
514
  function gitDiff(cwd, path) {
421
515
  return new Promise((resolveOutput) => {
422
- execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, maxBuffer: 1024 * 1024 }, (error, stdout) => resolveOutput(error && stdout.length === 0 ? "" : stdout));
516
+ execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) => resolveOutput(error && stdout.length === 0 ? "" : stdout));
423
517
  });
424
518
  }
425
519
  function gitCommand(cwd, args) {
426
520
  return new Promise((resolveResult) => {
427
- execFile("git", [...args], { cwd, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
521
+ execFile("git", [...args], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
428
522
  const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
429
- resolveResult({ stdout, stderr, code });
523
+ resolveResult({ stdout, stderr, code, terminated: gitTermination(error) });
430
524
  });
431
525
  });
432
526
  }
@@ -457,6 +551,26 @@ async function isDirectory(path) {
457
551
  return false;
458
552
  }
459
553
  }
554
+ async function isFile(path) {
555
+ try {
556
+ return (await stat(path)).isFile();
557
+ }
558
+ catch {
559
+ return false;
560
+ }
561
+ }
562
+ // 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.
563
+ async function marketplaceInstallDirectory(configPath, fallback) {
564
+ let directory = dirname(resolve(configPath));
565
+ let parent = dirname(directory);
566
+ while (parent !== directory) {
567
+ if (await isFile(join(directory, "package.json")))
568
+ return directory;
569
+ directory = parent;
570
+ parent = dirname(directory);
571
+ }
572
+ return fallback;
573
+ }
460
574
  function pickDirectory() {
461
575
  return new Promise((resolvePath, reject) => {
462
576
  execFile("osascript", ["-e", 'POSIX path of (choose folder with prompt "选择 Pi 工作区")'], { timeout: 120_000, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
@@ -503,7 +617,12 @@ export default {
503
617
  const events = [];
504
618
  const eventClients = new Set();
505
619
  const handleEvent = (event) => {
506
- events.push(event);
620
+ // Streaming deltas reach clients live over SSE and each one carries the whole partial message, so only durable events are retained for snapshots.
621
+ if (event.type !== "message_update") {
622
+ events.push(event);
623
+ if (events.length > MAX_RETAINED_EVENTS)
624
+ events.splice(0, events.length - MAX_RETAINED_EVENTS);
625
+ }
507
626
  for (const response of eventClients) {
508
627
  if (response.writableEnded || response.destroyed) {
509
628
  eventClients.delete(response);
@@ -639,7 +758,9 @@ export default {
639
758
  return;
640
759
  }
641
760
  const path = join(services.launch.agentDir, "settings.json");
642
- await writeFile(path, JSON.stringify(parsed, null, 2) + "\n", "utf8");
761
+ // Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits.
762
+ const mode = await stat(path).then((info) => info.mode & 0o777, () => 0o644);
763
+ await atomicWriteFile(path, JSON.stringify(parsed, null, 2) + "\n", { encoding: "utf8", mode });
643
764
  await services.runtime.session.settingsManager.reload();
644
765
  sendJson(response, 200, piConfig(services));
645
766
  }
@@ -900,15 +1021,17 @@ export default {
900
1021
  sendJson(response, 409, { error: "Plugin is already installed", plugin });
901
1022
  return;
902
1023
  }
903
- const packageJsonPath = join(services.launch.cwd, "package.json");
904
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1024
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1025
+ const packageJsonPath = join(installDirectory, "package.json");
1026
+ const packageLockPath = join(installDirectory, "package-lock.json");
905
1027
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
906
1028
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
907
1029
  let profileBefore;
908
1030
  let entryId;
909
1031
  try {
910
1032
  if (needsMarketplacePackageInstall(plugin)) {
911
- await runProcess("npm", ["install", "--save-exact", "--package-lock=false", `${plugin.packageName}@${plugin.version}`], services.launch.cwd);
1033
+ const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
1034
+ await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
912
1035
  }
913
1036
  profileBefore = await appendMarketplaceProfile(configPath, plugin);
914
1037
  if (!needsMarketplacePackageInstall(plugin)) {
@@ -932,9 +1055,14 @@ export default {
932
1055
  await loader.remove(entryId).catch(() => { });
933
1056
  if (profileBefore !== undefined)
934
1057
  await writeFile(configPath, profileBefore, "utf8").catch(() => { });
935
- if (packageJsonBefore !== undefined)
1058
+ // A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
1059
+ if (packageJsonBefore === undefined)
1060
+ await unlink(packageJsonPath).catch(() => { });
1061
+ else
936
1062
  await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => { });
937
- if (packageLockBefore !== undefined)
1063
+ if (packageLockBefore === undefined)
1064
+ await unlink(packageLockPath).catch(() => { });
1065
+ else
938
1066
  await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => { });
939
1067
  sendJson(response, 502, { error: errorText(error) });
940
1068
  }
@@ -1036,8 +1164,9 @@ export default {
1036
1164
  }
1037
1165
  const profileEntryId = entry.options.id;
1038
1166
  const profileBefore = await readFile(configPath, "utf8");
1039
- const packageJsonPath = join(services.launch.cwd, "package.json");
1040
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1167
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1168
+ const packageJsonPath = join(installDirectory, "package.json");
1169
+ const packageLockPath = join(installDirectory, "package-lock.json");
1041
1170
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
1042
1171
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
1043
1172
  await entry.parent.remove(entry.options.id);
@@ -1045,7 +1174,7 @@ export default {
1045
1174
  try {
1046
1175
  await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
1047
1176
  if (needsMarketplacePackageInstall(plugin))
1048
- await runProcess("npm", ["uninstall", "--package-lock=false", plugin.packageName], services.launch.cwd);
1177
+ await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
1049
1178
  sendJson(response, 200, { uninstalled: true, id: payload.id });
1050
1179
  }
1051
1180
  catch (error) {
@@ -1149,20 +1278,31 @@ export default {
1149
1278
  const disposeFiles = services.webServer.register({
1150
1279
  path: "/api/files",
1151
1280
  async handler(_request, response) {
1152
- const output = await gitStatus(activeCwd(services));
1153
- const items = output
1154
- .split("\n")
1155
- .map((line) => line.trimEnd())
1156
- .filter((line) => line.length > 0)
1157
- .map((line) => {
1158
- const status = line.slice(0, 2).trim() || "??";
1159
- return {
1160
- path: line.slice(3),
1161
- status,
1162
- label: status === "??" ? "untracked" : status.includes("D") ? "deleted" : status.includes("A") ? "added" : "modified",
1163
- };
1164
- });
1165
- sendJson(response, 200, { items });
1281
+ let status;
1282
+ try {
1283
+ status = await gitStatus(activeCwd(services));
1284
+ }
1285
+ catch (error) {
1286
+ sendJson(response, 500, { error: errorText(error) });
1287
+ return;
1288
+ }
1289
+ const records = status.output.split("\0");
1290
+ const items = [];
1291
+ for (let index = 0; index < records.length; index += 1) {
1292
+ const record = records[index] ?? "";
1293
+ if (record.length < 4)
1294
+ continue;
1295
+ const code = record.slice(0, 2).trim() || "??";
1296
+ // Rename and copy records carry the original path in a second NUL-terminated field, which is not a workspace change of its own.
1297
+ if (code.startsWith("R") || code.startsWith("C"))
1298
+ index += 1;
1299
+ items.push({
1300
+ path: record.slice(3),
1301
+ status: code,
1302
+ label: code === "??" ? "untracked" : code.includes("D") ? "deleted" : code.includes("A") ? "added" : "modified",
1303
+ });
1304
+ }
1305
+ sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
1166
1306
  },
1167
1307
  });
1168
1308
  const disposeFileDiff = services.webServer.register({
@@ -1181,7 +1321,7 @@ export default {
1181
1321
  const root = resolve(activeCwd(services));
1182
1322
  const absolute = resolve(root, requested);
1183
1323
  const relativePath = relative(root, absolute);
1184
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + "/")) {
1324
+ if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep)) {
1185
1325
  sendJson(response, 400, { error: "path must stay inside the workspace" });
1186
1326
  return;
1187
1327
  }
@@ -1208,11 +1348,19 @@ export default {
1208
1348
  }
1209
1349
  const root = resolve(activeCwd(services));
1210
1350
  const add = await gitCommand(root, ["add", "-A", "--", ...paths]);
1351
+ if (add.terminated) {
1352
+ sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated) });
1353
+ return;
1354
+ }
1211
1355
  if (add.code !== 0) {
1212
1356
  sendJson(response, 409, { error: add.stderr.trim() || "Unable to stage workspace files" });
1213
1357
  return;
1214
1358
  }
1215
1359
  const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths]);
1360
+ if (commit.terminated) {
1361
+ sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated) });
1362
+ return;
1363
+ }
1216
1364
  if (commit.code !== 0) {
1217
1365
  sendJson(response, 409, { error: commit.stdout.trim() || commit.stderr.trim() || "Nothing to commit" });
1218
1366
  return;
@@ -1244,16 +1392,39 @@ export default {
1244
1392
  sendJson(response, 400, { error: paths.message });
1245
1393
  return;
1246
1394
  }
1247
- const untracked = await gitCommand(root, ["ls-files", "--others", "--exclude-standard", "--", ...paths]);
1248
- const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...paths]);
1249
- if (restore.code !== 0 && !restore.stderr.includes("pathspec")) {
1250
- sendJson(response, 409, { error: restore.stderr.trim() || "Unable to restore workspace files" });
1395
+ const untracked = await gitCommand(root, ["ls-files", "-z", "--others", "--exclude-standard", "--", ...paths]);
1396
+ // `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.
1397
+ let tracked = await gitCommand(root, ["ls-files", "-z", "--with-tree=HEAD", "--", ...paths]);
1398
+ if (tracked.code !== 0 && tracked.terminated === undefined)
1399
+ tracked = await gitCommand(root, ["ls-files", "-z", "--", ...paths]);
1400
+ const listing = [untracked, tracked].find((result) => result.terminated !== undefined || result.code !== 0);
1401
+ if (listing) {
1402
+ if (listing.terminated)
1403
+ sendJson(response, 500, { error: gitTerminationMessage("ls-files", listing.terminated) });
1404
+ else
1405
+ sendJson(response, 409, { error: listing.stderr.trim() || "Unable to inspect workspace files" });
1406
+ return;
1407
+ }
1408
+ const untrackedPaths = untracked.stdout.split("\0").filter(Boolean);
1409
+ const trackedPaths = tracked.stdout.split("\0").filter(Boolean);
1410
+ const known = [...untrackedPaths, ...trackedPaths];
1411
+ const unknown = paths.filter((path) => !known.some((candidate) => candidate === path || candidate.startsWith(path + "/")));
1412
+ if (unknown.length > 0) {
1413
+ sendJson(response, 409, { error: `Paths did not match any file known to git: ${unknown.join(", ")}` });
1251
1414
  return;
1252
1415
  }
1253
- for (const path of untracked.stdout
1254
- .split("\n")
1255
- .map((item) => item.trim())
1256
- .filter(Boolean)) {
1416
+ if (trackedPaths.length > 0) {
1417
+ const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths]);
1418
+ if (restore.terminated) {
1419
+ sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated) });
1420
+ return;
1421
+ }
1422
+ if (restore.code !== 0) {
1423
+ sendJson(response, 409, { error: restore.stderr.trim() || "Unable to restore workspace files" });
1424
+ return;
1425
+ }
1426
+ }
1427
+ for (const path of untrackedPaths) {
1257
1428
  await rm(resolve(root, path), { recursive: true, force: true });
1258
1429
  }
1259
1430
  sendJson(response, 200, { reverted: true, paths });
@@ -1291,6 +1462,10 @@ export default {
1291
1462
  const disposePrompt = services.webServer.register({
1292
1463
  path: "/api/prompt",
1293
1464
  async handler(request, response) {
1465
+ if (request.method !== "POST") {
1466
+ sendJson(response, 405, { error: "Method not allowed" });
1467
+ return;
1468
+ }
1294
1469
  if (busy) {
1295
1470
  sendJson(response, 409, { error: "Another prompt is already running" });
1296
1471
  return;
@@ -1541,9 +1716,9 @@ export default {
1541
1716
  events.length = 0;
1542
1717
  }
1543
1718
  await unlink(path);
1544
- const metadata = await readSessionMetadata(manager);
1545
- delete metadata[path];
1546
- await writeSessionMetadata(manager, metadata);
1719
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1720
+ delete metadata[path];
1721
+ });
1547
1722
  sendJson(response, 200, { deleted: true, path, sessionFile: services.runtime.session.sessionFile });
1548
1723
  }
1549
1724
  catch (error) {
@@ -1566,16 +1741,17 @@ export default {
1566
1741
  sendJson(response, 400, { error: "Invalid session path" });
1567
1742
  return;
1568
1743
  }
1569
- const metadata = await readSessionMetadata(manager);
1570
- const currentMetadata = metadata[path] ?? {};
1571
- const nextMetadata = {
1572
- ...currentMetadata,
1573
- ...(payload.archived === undefined ? {} : { archived: payload.archived === true }),
1574
- ...(payload.pinned === undefined ? {} : { pinned: payload.pinned === true }),
1575
- };
1576
- metadata[path] = nextMetadata;
1577
- await writeSessionMetadata(manager, metadata);
1578
- sendJson(response, 200, { path, metadata: metadata[path] });
1744
+ const nextMetadata = await mutateSessionMetadata(manager, context.logger, (metadata) => {
1745
+ const currentMetadata = metadata[path] ?? {};
1746
+ const updated = {
1747
+ ...currentMetadata,
1748
+ ...(payload.archived === undefined ? {} : { archived: payload.archived === true }),
1749
+ ...(payload.pinned === undefined ? {} : { pinned: payload.pinned === true }),
1750
+ };
1751
+ metadata[path] = updated;
1752
+ return updated;
1753
+ });
1754
+ sendJson(response, 200, { path, metadata: nextMetadata });
1579
1755
  }
1580
1756
  catch (error) {
1581
1757
  sendJson(response, 400, { error: errorText(error) });
@@ -1612,13 +1788,41 @@ export default {
1612
1788
  sendJson(response, 409, { error: "Cannot batch-delete the active session" });
1613
1789
  return;
1614
1790
  }
1615
- const metadata = await readSessionMetadata(manager);
1616
- for (const path of paths) {
1617
- if (action === "delete") {
1618
- await unlink(path);
1619
- delete metadata[path];
1791
+ if (action === "delete") {
1792
+ // 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.
1793
+ const removed = [];
1794
+ const failed = [];
1795
+ for (const path of paths) {
1796
+ try {
1797
+ await unlink(path);
1798
+ removed.push(path);
1799
+ }
1800
+ catch (error) {
1801
+ // A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
1802
+ if (error.code === "ENOENT")
1803
+ removed.push(path);
1804
+ else
1805
+ failed.push({ path, error: errorText(error) });
1806
+ }
1620
1807
  }
1621
- else {
1808
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1809
+ for (const path of removed)
1810
+ delete metadata[path];
1811
+ });
1812
+ if (failed.length > 0) {
1813
+ sendJson(response, 409, {
1814
+ error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
1815
+ action,
1816
+ count: removed.length,
1817
+ failed,
1818
+ });
1819
+ return;
1820
+ }
1821
+ sendJson(response, 200, { action, count: removed.length });
1822
+ return;
1823
+ }
1824
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1825
+ for (const path of paths) {
1622
1826
  const current = metadata[path] ?? {};
1623
1827
  metadata[path] = {
1624
1828
  ...current,
@@ -1626,8 +1830,7 @@ export default {
1626
1830
  ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1627
1831
  };
1628
1832
  }
1629
- }
1630
- await writeSessionMetadata(manager, metadata);
1833
+ });
1631
1834
  sendJson(response, 200, { action, count: paths.length });
1632
1835
  }
1633
1836
  catch (error) {
@@ -1677,11 +1880,16 @@ export default {
1677
1880
  return;
1678
1881
  }
1679
1882
  try {
1680
- const payload = JSON.parse(await bodyText(request));
1883
+ const payload = JSON.parse(await bodyText(request, IMPORT_BODY_LIMIT_BYTES));
1681
1884
  const suppliedPath = typeof payload.path === "string" ? payload.path : "";
1682
1885
  const content = typeof payload.content === "string" ? payload.content : undefined;
1683
- if ((!suppliedPath || !isAbsolute(suppliedPath)) && content === undefined) {
1684
- sendJson(response, 400, { error: "A JSONL file path or file content is required" });
1886
+ // `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.
1887
+ if (suppliedPath && content !== undefined) {
1888
+ sendJson(response, 400, { error: "Provide either a JSONL file path or file content, not both" });
1889
+ return;
1890
+ }
1891
+ if (content === undefined && (!suppliedPath || !isAbsolute(suppliedPath) || !suppliedPath.endsWith(".jsonl"))) {
1892
+ sendJson(response, 400, { error: "An absolute .jsonl file path or file content is required" });
1685
1893
  return;
1686
1894
  }
1687
1895
  const manager = services.runtime.session.sessionManager;
@@ -1691,11 +1899,12 @@ export default {
1691
1899
  }
1692
1900
  const targetCwd = typeof payload.cwd === "string" && payload.cwd.trim() ? resolve(payload.cwd) : activeCwd(services);
1693
1901
  const temporaryDirectory = content === undefined ? undefined : await mkdtemp(join(tmpdir(), "pi-harness-import-"));
1694
- const importPath = suppliedPath ||
1695
- join(temporaryDirectory, basename(typeof payload.filename === "string" && payload.filename.trim() ? payload.filename : "import.jsonl"));
1902
+ const requestedName = basename(typeof payload.filename === "string" && payload.filename.trim() ? payload.filename : "import.jsonl");
1903
+ const importName = requestedName === "" || requestedName === "." || requestedName === ".." ? "import.jsonl" : requestedName;
1904
+ const importPath = temporaryDirectory === undefined ? suppliedPath : join(temporaryDirectory, importName);
1696
1905
  try {
1697
1906
  if (content !== undefined) {
1698
- if (Buffer.byteLength(content, "utf8") > 10 * 1024 * 1024) {
1907
+ if (Buffer.byteLength(content, "utf8") > IMPORT_CONTENT_LIMIT_BYTES) {
1699
1908
  sendJson(response, 413, { error: "Imported session must be at most 10 MiB" });
1700
1909
  return;
1701
1910
  }
@@ -1723,7 +1932,7 @@ export default {
1723
1932
  }
1724
1933
  }
1725
1934
  catch (error) {
1726
- sendJson(response, 400, { error: errorText(error) });
1935
+ sendJson(response, error instanceof PayloadTooLargeError ? 413 : 400, { error: errorText(error) });
1727
1936
  }
1728
1937
  },
1729
1938
  });
@@ -1761,7 +1970,7 @@ export default {
1761
1970
  const page = Math.max(0, Number.parseInt(url.searchParams.get("page") ?? "0", 10) || 0);
1762
1971
  const pageSize = Math.min(100, Math.max(1, Number.parseInt(url.searchParams.get("pageSize") ?? "50", 10) || 50));
1763
1972
  const includeArchived = url.searchParams.get("includeArchived") === "true";
1764
- const metadata = await readSessionMetadata(manager);
1973
+ const metadata = await readSessionMetadata(manager, context.logger);
1765
1974
  const items = typeof manager.isPersisted === "function" && manager.isPersisted() ? await SessionManager.list(services.launch.cwd, manager.getSessionDir()) : [];
1766
1975
  const filtered = items.filter((item) => includeArchived || metadata[item.path]?.archived !== true);
1767
1976
  const sorted = filtered.sort((a, b) => Number(metadata[b.path]?.pinned === true) - Number(metadata[a.path]?.pinned === true) || b.modified.getTime() - a.modified.getTime());