@pi-harness/pi-harness 0.1.23 → 0.1.25

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 (295) hide show
  1. package/README.md +14 -9
  2. package/apps/web/dist/assets/index-Deo3hxmM.js +25 -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 +27 -2
  8. package/apps/web/server-dist/bin.js.map +1 -1
  9. package/apps/web/src/server/bin.ts +27 -2
  10. package/apps/web/test/bin.test.ts +228 -5
  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 +160 -69
  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 +1 -1
  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/relaunch.d.ts.map +1 -1
  95. package/node_modules/@pi-harness/cli/dist/relaunch.js +2 -4
  96. package/node_modules/@pi-harness/cli/dist/relaunch.js.map +1 -1
  97. package/node_modules/@pi-harness/cli/package.json +1 -1
  98. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts +1 -0
  99. package/node_modules/@pi-harness/host-webserver/dist/index.d.ts.map +1 -1
  100. package/node_modules/@pi-harness/host-webserver/dist/index.js +43 -7
  101. package/node_modules/@pi-harness/host-webserver/dist/index.js.map +1 -1
  102. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  103. package/node_modules/@pi-harness/web-app/dist/index.d.ts.map +1 -1
  104. package/node_modules/@pi-harness/web-app/dist/index.js +12 -3
  105. package/node_modules/@pi-harness/web-app/dist/index.js.map +1 -1
  106. package/node_modules/@pi-harness/web-app/package.json +1 -1
  107. package/package.json +7 -7
  108. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  109. package/packages/api-gateway/dist/index.js +160 -69
  110. package/packages/api-gateway/dist/index.js.map +1 -1
  111. package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +1 -1
  112. package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  113. package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -1
  114. package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  115. package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +1 -1
  116. package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  117. package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  118. package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +1 -1
  119. package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +1 -1
  120. package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +1 -1
  121. package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  122. package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +1 -1
  123. package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +1 -1
  124. package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +1 -1
  125. package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -1
  126. package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +1 -1
  127. package/packages/api-gateway/dist/marketplace-entries/official/context.json +1 -1
  128. package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +1 -1
  129. package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +1 -1
  130. package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +1 -1
  131. package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +1 -1
  132. package/packages/api-gateway/dist/marketplace-entries/official/genui.json +1 -1
  133. package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +1 -1
  134. package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +1 -1
  135. package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  136. package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
  137. package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +1 -1
  138. package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  139. package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  140. package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +1 -1
  141. package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  142. package/packages/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  143. package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  144. package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  145. package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +1 -1
  146. package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  147. package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  148. package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +1 -1
  149. package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  150. package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  151. package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +1 -1
  152. package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  153. package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  154. package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +1 -1
  155. package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  156. package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  157. package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +1 -1
  158. package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +1 -1
  159. package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  160. package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  161. package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  162. package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  163. package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  164. package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +1 -1
  165. package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  166. package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  167. package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +1 -1
  168. package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  169. package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  170. package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +1 -1
  171. package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +1 -1
  172. package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  173. package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  174. package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  175. package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  176. package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +1 -1
  177. package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  178. package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +1 -1
  179. package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +1 -1
  180. package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  181. package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +1 -1
  182. package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  183. package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  184. package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  185. package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +1 -1
  186. package/packages/api-gateway/package.json +1 -1
  187. package/packages/api-gateway/src/index.ts +186 -88
  188. package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +1 -1
  189. package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
  190. package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -1
  191. package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
  192. package/packages/api-gateway/src/marketplace-entries/official/at-file.json +1 -1
  193. package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +1 -1
  194. package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +1 -1
  195. package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +1 -1
  196. package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +1 -1
  197. package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +1 -1
  198. package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
  199. package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +1 -1
  200. package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +1 -1
  201. package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +1 -1
  202. package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -1
  203. package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +1 -1
  204. package/packages/api-gateway/src/marketplace-entries/official/context.json +1 -1
  205. package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +1 -1
  206. package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +1 -1
  207. package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +1 -1
  208. package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +1 -1
  209. package/packages/api-gateway/src/marketplace-entries/official/genui.json +1 -1
  210. package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +1 -1
  211. package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +1 -1
  212. package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
  213. package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +1 -1
  214. package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +1 -1
  215. package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +1 -1
  216. package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +1 -1
  217. package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +1 -1
  218. package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +1 -1
  219. package/packages/api-gateway/src/marketplace-entries/official/memory.json +1 -1
  220. package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +1 -1
  221. package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +1 -1
  222. package/packages/api-gateway/src/marketplace-entries/official/modlens.json +1 -1
  223. package/packages/api-gateway/src/marketplace-entries/official/module-search.json +1 -1
  224. package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +1 -1
  225. package/packages/api-gateway/src/marketplace-entries/official/openpets.json +1 -1
  226. package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +1 -1
  227. package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +1 -1
  228. package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +1 -1
  229. package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +1 -1
  230. package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +1 -1
  231. package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +1 -1
  232. package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +1 -1
  233. package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +1 -1
  234. package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +1 -1
  235. package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +1 -1
  236. package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +1 -1
  237. package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +1 -1
  238. package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +1 -1
  239. package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +1 -1
  240. package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +1 -1
  241. package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +1 -1
  242. package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +1 -1
  243. package/packages/api-gateway/src/marketplace-entries/official/session-export.json +1 -1
  244. package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +1 -1
  245. package/packages/api-gateway/src/marketplace-entries/official/session-search.json +1 -1
  246. package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +1 -1
  247. package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +1 -1
  248. package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +1 -1
  249. package/packages/api-gateway/src/marketplace-entries/official/synapse.json +1 -1
  250. package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +1 -1
  251. package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +1 -1
  252. package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +1 -1
  253. package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +1 -1
  254. package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
  255. package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +1 -1
  256. package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +1 -1
  257. package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
  258. package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +1 -1
  259. package/packages/api-gateway/src/marketplace-entries/official/web-research.json +1 -1
  260. package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +1 -1
  261. package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +1 -1
  262. package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +1 -1
  263. package/packages/api-gateway/test/index.test.ts +656 -4
  264. package/packages/api-gateway/test/packed-package.test.ts +70 -0
  265. package/packages/api-gateway/test/reference-docs.test.ts +50 -0
  266. package/packages/api-gateway/test/release-package.test.ts +32 -1
  267. package/packages/api-gateway/test/release-version.test.ts +40 -0
  268. package/packages/bundle-web-app/dist/index.d.ts.map +1 -1
  269. package/packages/bundle-web-app/dist/index.js +12 -3
  270. package/packages/bundle-web-app/dist/index.js.map +1 -1
  271. package/packages/bundle-web-app/package.json +1 -1
  272. package/packages/bundle-web-app/src/index.ts +13 -3
  273. package/packages/bundle-web-app/test/index.test.ts +83 -2
  274. package/packages/cli/dist/main.d.ts +1 -0
  275. package/packages/cli/dist/main.d.ts.map +1 -1
  276. package/packages/cli/dist/main.js +11 -0
  277. package/packages/cli/dist/main.js.map +1 -1
  278. package/packages/cli/dist/relaunch.d.ts.map +1 -1
  279. package/packages/cli/dist/relaunch.js +2 -4
  280. package/packages/cli/dist/relaunch.js.map +1 -1
  281. package/packages/cli/package.json +1 -1
  282. package/packages/cli/src/main.ts +11 -0
  283. package/packages/cli/src/relaunch.ts +2 -3
  284. package/packages/cli/test/cli.test.ts +3 -1
  285. package/packages/cli/test/main.test.ts +134 -0
  286. package/packages/cli/test/relaunch.test.ts +86 -11
  287. package/packages/host-webserver/dist/index.d.ts +1 -0
  288. package/packages/host-webserver/dist/index.d.ts.map +1 -1
  289. package/packages/host-webserver/dist/index.js +43 -7
  290. package/packages/host-webserver/dist/index.js.map +1 -1
  291. package/packages/host-webserver/package.json +1 -1
  292. package/packages/host-webserver/src/index.ts +48 -8
  293. package/packages/host-webserver/test/index.test.ts +58 -5
  294. package/apps/web/dist/assets/index-BVI_IS-R.css +0 -2
  295. package/apps/web/dist/assets/index-Bo9CNI_U.js +0 -22
@@ -1,7 +1,7 @@
1
1
  import { execFile, type ExecFileException } from "node:child_process";
2
- import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
2
+ import { mkdir, mkdtemp, readFile, realpath, 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, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, posix, 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";
@@ -18,6 +18,7 @@ import {
18
18
  paginateMarketplace,
19
19
  searchMarketplace,
20
20
  needsMarketplacePackageInstall,
21
+ marketplaceNpmPackageName,
21
22
  sortMarketplaceByRecommendation,
22
23
  type MarketplacePlugin,
23
24
  } from "./marketplace.js";
@@ -41,6 +42,8 @@ const IMPORT_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
41
42
  // Retained trajectory events are replayed to every SSE client and returned by /api/session, so the array must stay bounded.
42
43
  const MAX_RETAINED_EVENTS = 2000;
43
44
  const GIT_TIMEOUT_MS = 15_000;
45
+ // Mutating commands run user hooks (pre-commit, lint-staged, test suites) and may stage large trees; killing them part-way leaves index.lock and a half-finished operation behind, so they get a far more generous bound than read-only queries.
46
+ const GIT_MUTATION_TIMEOUT_MS = 120_000;
44
47
 
45
48
  class PayloadTooLargeError extends Error {
46
49
  constructor() {
@@ -55,17 +58,18 @@ function sendJson(response: ServerResponse, status: number, payload: unknown): v
55
58
  response.end(body);
56
59
  }
57
60
 
61
+ // 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.
58
62
  async function bodyText(request: IncomingMessage, maxBytes = DEFAULT_BODY_LIMIT_BYTES): Promise<string> {
59
- const chunks: string[] = [];
63
+ const chunks: Buffer[] = [];
60
64
  let length = 0;
61
65
  for await (const chunk of request) {
62
66
  const input: string | Uint8Array = chunk as string | Uint8Array;
63
- const value = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
64
- length += Buffer.byteLength(value);
67
+ const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
68
+ length += buffer.length;
65
69
  if (length > maxBytes) throw new PayloadTooLargeError();
66
- chunks.push(value);
70
+ chunks.push(buffer);
67
71
  }
68
- return chunks.join("");
72
+ return Buffer.concat(chunks).toString("utf8");
69
73
  }
70
74
 
71
75
  function errorText(error: unknown): string {
@@ -80,9 +84,14 @@ function jsonSafe(value: unknown, seen = new WeakSet<object>()): unknown {
80
84
  if (value instanceof Date) return value.toISOString();
81
85
  if (typeof value !== "object") return Object.prototype.toString.call(value);
82
86
  if (seen.has(value)) return "[Circular]";
87
+ // 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.
83
88
  seen.add(value);
84
- if (Array.isArray(value)) return value.map((item) => jsonSafe(item, seen));
85
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
89
+ try {
90
+ if (Array.isArray(value)) return value.map((item) => jsonSafe(item, seen));
91
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
92
+ } finally {
93
+ seen.delete(value);
94
+ }
86
95
  }
87
96
 
88
97
  function createStatus(services: ApiServices, events: readonly AgentSessionEvent[]) {
@@ -159,7 +168,7 @@ interface MetadataLogger {
159
168
  warn(message: string): void;
160
169
  }
161
170
 
162
- // 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.
171
+ // A missing file is the normal first-run state. A parse or shape 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. Any other read error (EMFILE, EBUSY, EISDIR, permissions) says nothing about the content, so it propagates and the caller's write is skipped instead of replacing a valid map with an empty one.
163
172
  async function readSessionMetadata(manager: SessionManager, logger: MetadataLogger): Promise<SessionMetadataMap> {
164
173
  const file = sessionMetadataFile(manager);
165
174
  let raw: string;
@@ -167,8 +176,7 @@ async function readSessionMetadata(manager: SessionManager, logger: MetadataLogg
167
176
  raw = await readFile(file, "utf8");
168
177
  } catch (error) {
169
178
  if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
170
- await quarantineSessionMetadata(file, logger, error);
171
- return {};
179
+ throw error;
172
180
  }
173
181
  try {
174
182
  const parsed: unknown = JSON.parse(raw);
@@ -195,27 +203,38 @@ async function writeSessionMetadata(manager: SessionManager, metadata: SessionMe
195
203
  await atomicWriteFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", { encoding: "utf8" });
196
204
  }
197
205
 
198
- // Every read-modify-write of the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates.
206
+ // Every access to the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates. Plain reads take the lock too: a read that finds corrupt bytes quarantines the file, and doing that outside the lock could rename away a valid map that a concurrent mutation had just written.
199
207
  let sessionMetadataQueue: Promise<unknown> = Promise.resolve();
200
208
 
209
+ function withSessionMetadataLock<T>(operation: () => Promise<T>): Promise<T> {
210
+ const next = sessionMetadataQueue.catch(() => undefined).then(operation);
211
+ sessionMetadataQueue = next;
212
+ return next;
213
+ }
214
+
215
+ function readSessionMetadataLocked(manager: SessionManager, logger: MetadataLogger): Promise<SessionMetadataMap> {
216
+ return withSessionMetadataLock(() => readSessionMetadata(manager, logger));
217
+ }
218
+
201
219
  function mutateSessionMetadata<T>(manager: SessionManager, logger: MetadataLogger, mutate: (metadata: SessionMetadataMap) => Promise<T> | T): Promise<T> {
202
- const operation = sessionMetadataQueue
203
- .catch(() => undefined)
204
- .then(async () => {
205
- const metadata = await readSessionMetadata(manager, logger);
206
- const result = await mutate(metadata);
207
- await writeSessionMetadata(manager, metadata);
208
- return result;
209
- });
210
- sessionMetadataQueue = operation;
211
- return operation;
220
+ return withSessionMetadataLock(async () => {
221
+ const metadata = await readSessionMetadata(manager, logger);
222
+ const result = await mutate(metadata);
223
+ await writeSessionMetadata(manager, metadata);
224
+ return result;
225
+ });
226
+ }
227
+
228
+ // True when a path.relative() result points outside the directory it was computed from. All containment checks share this predicate so they agree on every platform separator.
229
+ function escapesRoot(relativePath: string): boolean {
230
+ return isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep);
212
231
  }
213
232
 
214
233
  function sessionPathInDirectory(path: string, manager: SessionManager): boolean {
215
234
  const root = resolve(manager.getSessionDir());
216
235
  const target = resolve(path);
217
236
  const relativePath = relative(root, target);
218
- return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + sep) && target.endsWith(".jsonl");
237
+ return relativePath !== "" && !escapesRoot(relativePath) && target.endsWith(".jsonl");
219
238
  }
220
239
 
221
240
  const webSessionUi = {
@@ -350,6 +369,7 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
350
369
  if (typeof value !== "object") return Object.prototype.toString.call(value);
351
370
  if (depth >= loggerPanelDepthLimit) return "[Max Depth]";
352
371
  if (state.seen.has(value)) return "[Circular]";
372
+ // Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
353
373
  state.seen.add(value);
354
374
 
355
375
  try {
@@ -379,6 +399,8 @@ function sanitizeLogValue(value: unknown, state: LogSanitizeState, depth = 0): u
379
399
  return Object.fromEntries(entries);
380
400
  } catch {
381
401
  return "[Unavailable]";
402
+ } finally {
403
+ state.seen.delete(value);
382
404
  }
383
405
  }
384
406
 
@@ -519,39 +541,71 @@ function gitTermination(error: ExecFileException | null): GitTermination | undef
519
541
  return undefined;
520
542
  }
521
543
 
522
- function gitTerminationMessage(command: string, termination: GitTermination): string {
544
+ function gitTerminationMessage(command: string, termination: GitTermination, timeoutMs = GIT_TIMEOUT_MS): string {
523
545
  return termination === "timeout"
524
- ? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
546
+ ? `git ${command} timed out after ${timeoutMs / 1000} seconds`
525
547
  : `git ${command} produced more output than the buffer limit allows`;
526
548
  }
527
549
 
550
+ interface GitStatusEntry {
551
+ readonly path: string;
552
+ readonly status: string;
553
+ }
554
+
528
555
  interface GitStatusResult {
529
- readonly output: string;
556
+ readonly entries: readonly GitStatusEntry[];
530
557
  readonly truncated: boolean;
531
558
  }
532
559
 
533
- function gitStatus(cwd: string): Promise<GitStatusResult> {
560
+ // `-z` output is NUL-separated and never C-quoted, so non-ASCII names arrive verbatim and a rename carries its original path as the following field instead of an `old -> new` pair. The paths are relative to the repository root regardless of cwd, so they are rebased onto the cwd prefix to keep the contract with /api/files/diff, /api/files/commit and /api/files/revert, which resolve paths against the same cwd.
561
+ function parseGitStatus(output: string, prefix: string): GitStatusEntry[] {
562
+ const fields = output.split("\0");
563
+ const entries: GitStatusEntry[] = [];
564
+ for (let index = 0; index < fields.length; index += 1) {
565
+ const field = fields[index] ?? "";
566
+ if (field.length < 4) continue;
567
+ const code = field.slice(0, 2);
568
+ const path = field.slice(3);
569
+ if (code.startsWith("R") || code.startsWith("C")) index += 1;
570
+ entries.push({ path: prefix === "" ? path : posix.relative("/" + prefix, "/" + path), status: code.trim() || "??" });
571
+ }
572
+ return entries;
573
+ }
574
+
575
+ function gitStatusOutput(cwd: string): Promise<{ output: string; truncated: boolean }> {
534
576
  return new Promise((resolveStatus, rejectStatus) => {
535
- execFile("git", ["status", "--short", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
536
- if (error === null) {
537
- resolveStatus({ output: stdout, truncated: false });
538
- return;
539
- }
540
- const termination = gitTermination(error);
541
- if (termination === "output-limit") {
542
- // Keep the complete lines that arrived before the kill; the final line may have been cut mid-path.
543
- resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\n") + 1), truncated: true });
544
- return;
545
- }
546
- if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
547
- resolveStatus({ output: "", truncated: false });
548
- return;
549
- }
550
- rejectStatus(new Error(termination ? gitTerminationMessage("status", termination) : stderr.trim() || error.message, { cause: error }));
551
- });
577
+ execFile(
578
+ "git",
579
+ ["status", "--porcelain", "-z", "--untracked-files=all"],
580
+ { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 },
581
+ (error, stdout, stderr) => {
582
+ if (error === null) {
583
+ resolveStatus({ output: stdout, truncated: false });
584
+ return;
585
+ }
586
+ const termination = gitTermination(error);
587
+ if (termination === "output-limit") {
588
+ // Keep the complete entries that arrived before the kill; the final entry may have been cut mid-path.
589
+ resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
590
+ return;
591
+ }
592
+ if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
593
+ resolveStatus({ output: "", truncated: false });
594
+ return;
595
+ }
596
+ rejectStatus(new Error(termination ? gitTerminationMessage("status", termination) : stderr.trim() || error.message, { cause: error }));
597
+ },
598
+ );
552
599
  });
553
600
  }
554
601
 
602
+ async function gitStatus(cwd: string): Promise<GitStatusResult> {
603
+ const { output, truncated } = await gitStatusOutput(cwd);
604
+ if (output === "") return { entries: [], truncated };
605
+ const prefix = await gitCommand(cwd, ["rev-parse", "--show-prefix"]);
606
+ return { entries: parseGitStatus(output, prefix.code === 0 ? prefix.stdout.trim() : ""), truncated };
607
+ }
608
+
555
609
  function gitDiff(cwd: string, path: string): Promise<string> {
556
610
  return new Promise((resolveOutput) => {
557
611
  execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) =>
@@ -567,9 +621,9 @@ interface GitCommandResult {
567
621
  readonly terminated: GitTermination | undefined;
568
622
  }
569
623
 
570
- function gitCommand(cwd: string, args: readonly string[]): Promise<GitCommandResult> {
624
+ function gitCommand(cwd: string, args: readonly string[], timeoutMs = GIT_TIMEOUT_MS): Promise<GitCommandResult> {
571
625
  return new Promise((resolveResult) => {
572
- execFile("git", [...args], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
626
+ execFile("git", [...args], { cwd, timeout: timeoutMs, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
573
627
  const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
574
628
  resolveResult({ stdout, stderr, code, terminated: gitTermination(error) });
575
629
  });
@@ -610,6 +664,26 @@ async function isDirectory(path: string): Promise<boolean> {
610
664
  }
611
665
  }
612
666
 
667
+ async function isFile(path: string): Promise<boolean> {
668
+ try {
669
+ return (await stat(path)).isFile();
670
+ } catch {
671
+ return false;
672
+ }
673
+ }
674
+
675
+ // 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.
676
+ async function marketplaceInstallDirectory(configPath: string, fallback: string): Promise<string> {
677
+ let directory = dirname(resolve(configPath));
678
+ let parent = dirname(directory);
679
+ while (parent !== directory) {
680
+ if (await isFile(join(directory, "package.json"))) return directory;
681
+ directory = parent;
682
+ parent = dirname(directory);
683
+ }
684
+ return fallback;
685
+ }
686
+
613
687
  function pickDirectory(): Promise<string> {
614
688
  return new Promise((resolvePath, reject) => {
615
689
  execFile(
@@ -637,7 +711,7 @@ function workspacePaths(root: string, paths: unknown): string[] | Error {
637
711
  const requested = path as string;
638
712
  const absolute = resolve(root, requested);
639
713
  const relativePath = relative(root, absolute);
640
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith("../")) return new Error("path must stay inside the workspace");
714
+ if (escapesRoot(relativePath)) return new Error("path must stay inside the workspace");
641
715
  normalized.push(relativePath);
642
716
  }
643
717
  return [...new Set(normalized)];
@@ -790,8 +864,8 @@ export default {
790
864
  sendJson(response, 400, { error: "settings source must contain a JSON object" });
791
865
  return;
792
866
  }
793
- const path = join(services.launch.agentDir, "settings.json");
794
- // Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits.
867
+ // Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits. The rename must land on the resolved target, otherwise a symlinked settings.json (dotfiles repositories) would be replaced by a detached regular file.
868
+ const path = await realpath(join(services.launch.agentDir, "settings.json")).catch(() => join(services.launch.agentDir, "settings.json"));
795
869
  const mode = await stat(path).then(
796
870
  (info) => info.mode & 0o777,
797
871
  () => 0o644,
@@ -1073,15 +1147,17 @@ export default {
1073
1147
  sendJson(response, 409, { error: "Plugin is already installed", plugin });
1074
1148
  return;
1075
1149
  }
1076
- const packageJsonPath = join(services.launch.cwd, "package.json");
1077
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1150
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1151
+ const packageJsonPath = join(installDirectory, "package.json");
1152
+ const packageLockPath = join(installDirectory, "package-lock.json");
1078
1153
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
1079
1154
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
1080
1155
  let profileBefore: string | undefined;
1081
1156
  let entryId: string | undefined;
1082
1157
  try {
1083
1158
  if (needsMarketplacePackageInstall(plugin)) {
1084
- await runProcess("npm", ["install", "--save-exact", "--package-lock=false", `${plugin.packageName}@${plugin.version}`], services.launch.cwd);
1159
+ const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
1160
+ await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
1085
1161
  }
1086
1162
  profileBefore = await appendMarketplaceProfile(configPath, plugin);
1087
1163
  if (!needsMarketplacePackageInstall(plugin)) {
@@ -1101,8 +1177,11 @@ export default {
1101
1177
  } catch (error) {
1102
1178
  if (entryId !== undefined) await loader.remove(entryId).catch(() => {});
1103
1179
  if (profileBefore !== undefined) await writeFile(configPath, profileBefore, "utf8").catch(() => {});
1104
- if (packageJsonBefore !== undefined) await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => {});
1105
- if (packageLockBefore !== undefined) await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => {});
1180
+ // A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
1181
+ if (packageJsonBefore === undefined) await unlink(packageJsonPath).catch(() => {});
1182
+ else await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => {});
1183
+ if (packageLockBefore === undefined) await unlink(packageLockPath).catch(() => {});
1184
+ else await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => {});
1106
1185
  sendJson(response, 502, { error: errorText(error) });
1107
1186
  }
1108
1187
  } catch (error) {
@@ -1198,15 +1277,17 @@ export default {
1198
1277
  }
1199
1278
  const profileEntryId = entry.options.id;
1200
1279
  const profileBefore = await readFile(configPath, "utf8");
1201
- const packageJsonPath = join(services.launch.cwd, "package.json");
1202
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1280
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1281
+ const packageJsonPath = join(installDirectory, "package.json");
1282
+ const packageLockPath = join(installDirectory, "package-lock.json");
1203
1283
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
1204
1284
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
1205
1285
  await entry.parent.remove(entry.options.id);
1206
1286
  entry.parent.tree.write();
1207
1287
  try {
1208
1288
  await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
1209
- if (needsMarketplacePackageInstall(plugin)) await runProcess("npm", ["uninstall", "--package-lock=false", plugin.packageName], services.launch.cwd);
1289
+ if (needsMarketplacePackageInstall(plugin))
1290
+ await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
1210
1291
  sendJson(response, 200, { uninstalled: true, id: payload.id });
1211
1292
  } catch (error) {
1212
1293
  await writeFile(configPath, profileBefore, "utf8").catch(() => {});
@@ -1309,18 +1390,11 @@ export default {
1309
1390
  sendJson(response, 500, { error: errorText(error) });
1310
1391
  return;
1311
1392
  }
1312
- const items = status.output
1313
- .split("\n")
1314
- .map((line) => line.trimEnd())
1315
- .filter((line) => line.length > 0)
1316
- .map((line) => {
1317
- const status = line.slice(0, 2).trim() || "??";
1318
- return {
1319
- path: line.slice(3),
1320
- status,
1321
- label: status === "??" ? "untracked" : status.includes("D") ? "deleted" : status.includes("A") ? "added" : "modified",
1322
- };
1323
- });
1393
+ const items = status.entries.map((entry) => ({
1394
+ path: entry.path,
1395
+ status: entry.status,
1396
+ label: entry.status === "??" ? "untracked" : entry.status.includes("D") ? "deleted" : entry.status.includes("A") ? "added" : "modified",
1397
+ }));
1324
1398
  sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
1325
1399
  },
1326
1400
  });
@@ -1340,7 +1414,7 @@ export default {
1340
1414
  const root = resolve(activeCwd(services));
1341
1415
  const absolute = resolve(root, requested);
1342
1416
  const relativePath = relative(root, absolute);
1343
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep)) {
1417
+ if (escapesRoot(relativePath)) {
1344
1418
  sendJson(response, 400, { error: "path must stay inside the workspace" });
1345
1419
  return;
1346
1420
  }
@@ -1366,18 +1440,18 @@ export default {
1366
1440
  return;
1367
1441
  }
1368
1442
  const root = resolve(activeCwd(services));
1369
- const add = await gitCommand(root, ["add", "-A", "--", ...paths]);
1443
+ const add = await gitCommand(root, ["add", "-A", "--", ...paths], GIT_MUTATION_TIMEOUT_MS);
1370
1444
  if (add.terminated) {
1371
- sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated) });
1445
+ sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated, GIT_MUTATION_TIMEOUT_MS) });
1372
1446
  return;
1373
1447
  }
1374
1448
  if (add.code !== 0) {
1375
1449
  sendJson(response, 409, { error: add.stderr.trim() || "Unable to stage workspace files" });
1376
1450
  return;
1377
1451
  }
1378
- const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths]);
1452
+ const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths], GIT_MUTATION_TIMEOUT_MS);
1379
1453
  if (commit.terminated) {
1380
- sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated) });
1454
+ sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated, GIT_MUTATION_TIMEOUT_MS) });
1381
1455
  return;
1382
1456
  }
1383
1457
  if (commit.code !== 0) {
@@ -1429,9 +1503,9 @@ export default {
1429
1503
  return;
1430
1504
  }
1431
1505
  if (trackedPaths.length > 0) {
1432
- const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths]);
1506
+ const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths], GIT_MUTATION_TIMEOUT_MS);
1433
1507
  if (restore.terminated) {
1434
- sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated) });
1508
+ sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated, GIT_MUTATION_TIMEOUT_MS) });
1435
1509
  return;
1436
1510
  }
1437
1511
  if (restore.code !== 0) {
@@ -1803,20 +1877,44 @@ export default {
1803
1877
  sendJson(response, 409, { error: "Cannot batch-delete the active session" });
1804
1878
  return;
1805
1879
  }
1806
- await mutateSessionMetadata(manager, context.logger, async (metadata) => {
1880
+ if (action === "delete") {
1881
+ // 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.
1882
+ const removed: string[] = [];
1883
+ const failed: { path: string; error: string }[] = [];
1807
1884
  for (const path of paths) {
1808
- if (action === "delete") {
1885
+ try {
1809
1886
  await unlink(path);
1810
- delete metadata[path];
1811
- } else {
1812
- const current = metadata[path] ?? {};
1813
- metadata[path] = {
1814
- ...current,
1815
- ...(action === "archive" || action === "unarchive" ? { archived: action === "archive" } : {}),
1816
- ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1817
- };
1887
+ removed.push(path);
1888
+ } catch (error) {
1889
+ // A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
1890
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") removed.push(path);
1891
+ else failed.push({ path, error: errorText(error) });
1818
1892
  }
1819
1893
  }
1894
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1895
+ for (const path of removed) delete metadata[path];
1896
+ });
1897
+ if (failed.length > 0) {
1898
+ sendJson(response, 409, {
1899
+ error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
1900
+ action,
1901
+ count: removed.length,
1902
+ failed,
1903
+ });
1904
+ return;
1905
+ }
1906
+ sendJson(response, 200, { action, count: removed.length });
1907
+ return;
1908
+ }
1909
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1910
+ for (const path of paths) {
1911
+ const current = metadata[path] ?? {};
1912
+ metadata[path] = {
1913
+ ...current,
1914
+ ...(action === "archive" || action === "unarchive" ? { archived: action === "archive" } : {}),
1915
+ ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1916
+ };
1917
+ }
1820
1918
  });
1821
1919
  sendJson(response, 200, { action, count: paths.length });
1822
1920
  } catch (error) {
@@ -1955,7 +2053,7 @@ export default {
1955
2053
  const page = Math.max(0, Number.parseInt(url.searchParams.get("page") ?? "0", 10) || 0);
1956
2054
  const pageSize = Math.min(100, Math.max(1, Number.parseInt(url.searchParams.get("pageSize") ?? "50", 10) || 50));
1957
2055
  const includeArchived = url.searchParams.get("includeArchived") === "true";
1958
- const metadata = await readSessionMetadata(manager, context.logger);
2056
+ const metadata = await readSessionMetadataLocked(manager, context.logger);
1959
2057
  const items =
1960
2058
  typeof manager.isPersisted === "function" && manager.isPersisted() ? await SessionManager.list(services.launch.cwd, manager.getSessionDir()) : [];
1961
2059
  const filtered = items.filter((item) => includeArchived || metadata[item.path]?.archived !== true);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "agent-teams",
3
3
  "packageName": "@pi-harness/core/plugins/agent-teams",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Agent Team Board",
6
6
  "description": "Maintain a bounded, session-local collaboration ledger of named roles, dependency-aware tasks, and mailbox notes without spawning agents or sending external messages.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "anchored-standard",
3
3
  "packageName": "@pi-harness/core/plugins/anchored-standard",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Anchored Standard",
6
6
  "description": "Audit agent trajectories against an explicit tool-call allowlist and flag activity outside the anchored run.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "annotation",
3
3
  "packageName": "@pi-harness/core/plugins/annotation",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Annotations",
6
6
  "description": "Capture bounded selections and notes from the conversation for later agent context.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "archify",
3
3
  "packageName": "@pi-harness/core/plugins/archify",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Architecture Map",
6
6
  "description": "Build a bounded, read-only architecture map from workspace components and package dependencies.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "at-file",
3
3
  "packageName": "@pi-harness/core/plugins/at-file",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "@file Context",
6
6
  "description": "Attach a bounded 256 KiB strict UTF-8 workspace file using canonical workspace path confinement and no-follow regular-file reads, framing it as offline, read-only, untrusted model context with closing-tag neutralization and cancellable sequential execution.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "auto-mode",
3
3
  "packageName": "@pi-harness/core/plugins/auto-mode",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Auto Mode",
6
6
  "description": "Execute argv commands under a safe policy that blocks shell wrappers and requires confirmation for risky operations.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "better-sidebar",
3
3
  "packageName": "@pi-harness/core/plugins/better-sidebar",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Better Sidebar",
6
6
  "description": "Show a compact workspace, Git, and session overview beside the conversation without modifying files.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "browser-fetch",
3
3
  "packageName": "@pi-harness/core/plugins/browser-fetch",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Browser Fetch",
6
6
  "description": "Fetch public HTTP and HTTPS pages as bounded text with descriptor-safe inputs, DNS pinning, per-redirect private-network validation, textual MIME enforcement, cancellation, a whole-request timeout, and validated fail-closed panel reporting.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "browser-session",
3
3
  "packageName": "@pi-harness/core/plugins/browser-session",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Browser Session",
6
6
  "description": "Control a loopback-only Chrome DevTools session with descriptor-safe commands, bounded discovery, cancellable operations, screenshot metadata protection, and validated fail-closed panel previews.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "canvas-draw",
3
3
  "packageName": "@pi-harness/core/plugins/canvas-draw",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Canvas Draw",
6
6
  "description": "Generate validated, bounded Mermaid flowchart source from structured nodes and edges in the workspace UI.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "change-verifier",
3
3
  "packageName": "@pi-harness/core/plugins/change-verifier",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Change Verifier",
6
6
  "description": "Combine project tests and Git change review into one explicit verification gate before handoff.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "cleaner",
3
3
  "packageName": "@pi-harness/core/plugins/cleaner",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Cleaner",
6
6
  "description": "Strict descriptor-safe configuration and parameters, display-safe bounded Pi Harness Git capsule inventory, removal with explicit confirmation and retention controls, symlink and replacement defenses, cancellable serialization, and a fail-closed normalized panel.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "cli-notifier",
3
3
  "packageName": "@pi-harness/core/plugins/cli-notifier",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "CLI Notifier",
6
6
  "description": "Send local desktop notifications when an agent turn completes, fails, or is aborted, and when context compaction fails.",
7
7
  "author": "Pi Harness",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "code2skill",
3
3
  "packageName": "@pi-harness/core/plugins/code2skill",
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "name": "Code2Skill",
6
6
  "description": "Package selected workspace source files into a bounded local Pi skill with a generated SKILL.md manifest and preserved references.",
7
7
  "author": "Pi Harness",