@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,10 +1,10 @@
1
1
  import { execFile } from "node:child_process";
2
- import { mkdir, mkdtemp, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
3
- import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
2
+ import { mkdir, mkdtemp, readFile, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
  import { SessionManager } from "@earendil-works/pi-coding-agent";
6
6
  import { atomicWriteFile } from "@pi-harness/core";
7
- import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, sortMarketplaceByRecommendation, } from "./marketplace.js";
7
+ import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, needsMarketplacePackageInstall, marketplaceNpmPackageName, sortMarketplaceByRecommendation, } from "./marketplace.js";
8
8
  import { parseGitWorktrees } from "./workspaces.js";
9
9
  const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
10
10
  const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
@@ -14,6 +14,8 @@ const IMPORT_BODY_LIMIT_BYTES = 16 * 1024 * 1024;
14
14
  // Retained trajectory events are replayed to every SSE client and returned by /api/session, so the array must stay bounded.
15
15
  const MAX_RETAINED_EVENTS = 2000;
16
16
  const GIT_TIMEOUT_MS = 15_000;
17
+ // 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.
18
+ const GIT_MUTATION_TIMEOUT_MS = 120_000;
17
19
  class PayloadTooLargeError extends Error {
18
20
  constructor() {
19
21
  super("Request body is too large");
@@ -25,18 +27,19 @@ function sendJson(response, status, payload) {
25
27
  response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "content-length": Buffer.byteLength(body) });
26
28
  response.end(body);
27
29
  }
30
+ // 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.
28
31
  async function bodyText(request, maxBytes = DEFAULT_BODY_LIMIT_BYTES) {
29
32
  const chunks = [];
30
33
  let length = 0;
31
34
  for await (const chunk of request) {
32
35
  const input = chunk;
33
- const value = typeof input === "string" ? input : Buffer.from(input).toString("utf8");
34
- length += Buffer.byteLength(value);
36
+ const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.isBuffer(input) ? input : Buffer.from(input);
37
+ length += buffer.length;
35
38
  if (length > maxBytes)
36
39
  throw new PayloadTooLargeError();
37
- chunks.push(value);
40
+ chunks.push(buffer);
38
41
  }
39
- return chunks.join("");
42
+ return Buffer.concat(chunks).toString("utf8");
40
43
  }
41
44
  function errorText(error) {
42
45
  return error instanceof Error ? error.message : String(error);
@@ -56,10 +59,16 @@ function jsonSafe(value, seen = new WeakSet()) {
56
59
  return Object.prototype.toString.call(value);
57
60
  if (seen.has(value))
58
61
  return "[Circular]";
62
+ // 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.
59
63
  seen.add(value);
60
- if (Array.isArray(value))
61
- return value.map((item) => jsonSafe(item, seen));
62
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
64
+ try {
65
+ if (Array.isArray(value))
66
+ return value.map((item) => jsonSafe(item, seen));
67
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonSafe(item, seen)]));
68
+ }
69
+ finally {
70
+ seen.delete(value);
71
+ }
63
72
  }
64
73
  function createStatus(services, events) {
65
74
  const activeModel = services.runtime.session.model ?? services.models.model;
@@ -120,7 +129,7 @@ function piConfig(services) {
120
129
  function sessionMetadataFile(manager) {
121
130
  return resolve(manager.getSessionDir(), ".pi-harness-session-meta.json");
122
131
  }
123
- // 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.
132
+ // 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.
124
133
  async function readSessionMetadata(manager, logger) {
125
134
  const file = sessionMetadataFile(manager);
126
135
  let raw;
@@ -130,8 +139,7 @@ async function readSessionMetadata(manager, logger) {
130
139
  catch (error) {
131
140
  if (error.code === "ENOENT")
132
141
  return {};
133
- await quarantineSessionMetadata(file, logger, error);
134
- return {};
142
+ throw error;
135
143
  }
136
144
  try {
137
145
  const parsed = JSON.parse(raw);
@@ -158,25 +166,33 @@ async function writeSessionMetadata(manager, metadata) {
158
166
  await mkdir(manager.getSessionDir(), { recursive: true });
159
167
  await atomicWriteFile(sessionMetadataFile(manager), JSON.stringify(metadata, null, 2) + "\n", { encoding: "utf8" });
160
168
  }
161
- // Every read-modify-write of the metadata file runs through this chain so concurrent requests cannot interleave and drop each other's updates.
169
+ // 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.
162
170
  let sessionMetadataQueue = Promise.resolve();
171
+ function withSessionMetadataLock(operation) {
172
+ const next = sessionMetadataQueue.catch(() => undefined).then(operation);
173
+ sessionMetadataQueue = next;
174
+ return next;
175
+ }
176
+ function readSessionMetadataLocked(manager, logger) {
177
+ return withSessionMetadataLock(() => readSessionMetadata(manager, logger));
178
+ }
163
179
  function mutateSessionMetadata(manager, logger, mutate) {
164
- const operation = sessionMetadataQueue
165
- .catch(() => undefined)
166
- .then(async () => {
180
+ return withSessionMetadataLock(async () => {
167
181
  const metadata = await readSessionMetadata(manager, logger);
168
182
  const result = await mutate(metadata);
169
183
  await writeSessionMetadata(manager, metadata);
170
184
  return result;
171
185
  });
172
- sessionMetadataQueue = operation;
173
- return operation;
186
+ }
187
+ // 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.
188
+ function escapesRoot(relativePath) {
189
+ return isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep);
174
190
  }
175
191
  function sessionPathInDirectory(path, manager) {
176
192
  const root = resolve(manager.getSessionDir());
177
193
  const target = resolve(path);
178
194
  const relativePath = relative(root, target);
179
- return relativePath !== "" && !isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(".." + sep) && target.endsWith(".jsonl");
195
+ return relativePath !== "" && !escapesRoot(relativePath) && target.endsWith(".jsonl");
180
196
  }
181
197
  const webSessionUi = {
182
198
  select: (_title, options) => Promise.resolve(options[0]),
@@ -306,6 +322,7 @@ function sanitizeLogValue(value, state, depth = 0) {
306
322
  return "[Max Depth]";
307
323
  if (state.seen.has(value))
308
324
  return "[Circular]";
325
+ // Unmarked once its subtree is done, so an object logged twice as a sibling is rendered twice instead of being mistaken for a cycle.
309
326
  state.seen.add(value);
310
327
  try {
311
328
  if (value instanceof Date)
@@ -339,6 +356,9 @@ function sanitizeLogValue(value, state, depth = 0) {
339
356
  catch {
340
357
  return "[Unavailable]";
341
358
  }
359
+ finally {
360
+ state.seen.delete(value);
361
+ }
342
362
  }
343
363
  function sanitizeLogArguments(args) {
344
364
  const sanitized = sanitizeLogValue(args, { remaining: loggerPanelNodeLimit, seen: new WeakSet() });
@@ -473,22 +493,38 @@ function gitTermination(error) {
473
493
  return "timeout";
474
494
  return undefined;
475
495
  }
476
- function gitTerminationMessage(command, termination) {
496
+ function gitTerminationMessage(command, termination, timeoutMs = GIT_TIMEOUT_MS) {
477
497
  return termination === "timeout"
478
- ? `git ${command} timed out after ${GIT_TIMEOUT_MS / 1000} seconds`
498
+ ? `git ${command} timed out after ${timeoutMs / 1000} seconds`
479
499
  : `git ${command} produced more output than the buffer limit allows`;
480
500
  }
481
- function gitStatus(cwd) {
501
+ // `-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.
502
+ function parseGitStatus(output, prefix) {
503
+ const fields = output.split("\0");
504
+ const entries = [];
505
+ for (let index = 0; index < fields.length; index += 1) {
506
+ const field = fields[index] ?? "";
507
+ if (field.length < 4)
508
+ continue;
509
+ const code = field.slice(0, 2);
510
+ const path = field.slice(3);
511
+ if (code.startsWith("R") || code.startsWith("C"))
512
+ index += 1;
513
+ entries.push({ path: prefix === "" ? path : posix.relative("/" + prefix, "/" + path), status: code.trim() || "??" });
514
+ }
515
+ return entries;
516
+ }
517
+ function gitStatusOutput(cwd) {
482
518
  return new Promise((resolveStatus, rejectStatus) => {
483
- execFile("git", ["status", "--short", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
519
+ execFile("git", ["status", "--porcelain", "-z", "--untracked-files=all"], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 512 * 1024 }, (error, stdout, stderr) => {
484
520
  if (error === null) {
485
521
  resolveStatus({ output: stdout, truncated: false });
486
522
  return;
487
523
  }
488
524
  const termination = gitTermination(error);
489
525
  if (termination === "output-limit") {
490
- // Keep the complete lines that arrived before the kill; the final line may have been cut mid-path.
491
- resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\n") + 1), truncated: true });
526
+ // Keep the complete entries that arrived before the kill; the final entry may have been cut mid-path.
527
+ resolveStatus({ output: stdout.slice(0, stdout.lastIndexOf("\0") + 1), truncated: true });
492
528
  return;
493
529
  }
494
530
  if (termination === undefined && error.code === 128 && /not a git repository/i.test(stderr)) {
@@ -499,14 +535,21 @@ function gitStatus(cwd) {
499
535
  });
500
536
  });
501
537
  }
538
+ async function gitStatus(cwd) {
539
+ const { output, truncated } = await gitStatusOutput(cwd);
540
+ if (output === "")
541
+ return { entries: [], truncated };
542
+ const prefix = await gitCommand(cwd, ["rev-parse", "--show-prefix"]);
543
+ return { entries: parseGitStatus(output, prefix.code === 0 ? prefix.stdout.trim() : ""), truncated };
544
+ }
502
545
  function gitDiff(cwd, path) {
503
546
  return new Promise((resolveOutput) => {
504
547
  execFile("git", ["diff", "--no-ext-diff", "--", path], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 }, (error, stdout) => resolveOutput(error && stdout.length === 0 ? "" : stdout));
505
548
  });
506
549
  }
507
- function gitCommand(cwd, args) {
550
+ function gitCommand(cwd, args, timeoutMs = GIT_TIMEOUT_MS) {
508
551
  return new Promise((resolveResult) => {
509
- execFile("git", [...args], { cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
552
+ execFile("git", [...args], { cwd, timeout: timeoutMs, maxBuffer: 2 * 1024 * 1024 }, (error, stdout, stderr) => {
510
553
  const code = error && typeof error.code === "number" ? error.code : error ? 1 : 0;
511
554
  resolveResult({ stdout, stderr, code, terminated: gitTermination(error) });
512
555
  });
@@ -539,6 +582,26 @@ async function isDirectory(path) {
539
582
  return false;
540
583
  }
541
584
  }
585
+ async function isFile(path) {
586
+ try {
587
+ return (await stat(path)).isFile();
588
+ }
589
+ catch {
590
+ return false;
591
+ }
592
+ }
593
+ // 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.
594
+ async function marketplaceInstallDirectory(configPath, fallback) {
595
+ let directory = dirname(resolve(configPath));
596
+ let parent = dirname(directory);
597
+ while (parent !== directory) {
598
+ if (await isFile(join(directory, "package.json")))
599
+ return directory;
600
+ directory = parent;
601
+ parent = dirname(directory);
602
+ }
603
+ return fallback;
604
+ }
542
605
  function pickDirectory() {
543
606
  return new Promise((resolvePath, reject) => {
544
607
  execFile("osascript", ["-e", 'POSIX path of (choose folder with prompt "选择 Pi 工作区")'], { timeout: 120_000, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
@@ -562,7 +625,7 @@ function workspacePaths(root, paths) {
562
625
  const requested = path;
563
626
  const absolute = resolve(root, requested);
564
627
  const relativePath = relative(root, absolute);
565
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith("../"))
628
+ if (escapesRoot(relativePath))
566
629
  return new Error("path must stay inside the workspace");
567
630
  normalized.push(relativePath);
568
631
  }
@@ -725,8 +788,8 @@ export default {
725
788
  sendJson(response, 400, { error: "settings source must contain a JSON object" });
726
789
  return;
727
790
  }
728
- const path = join(services.launch.agentDir, "settings.json");
729
- // Replace the file atomically so an interrupted write never leaves a truncated settings.json behind; keep the existing permission bits.
791
+ // 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.
792
+ const path = await realpath(join(services.launch.agentDir, "settings.json")).catch(() => join(services.launch.agentDir, "settings.json"));
730
793
  const mode = await stat(path).then((info) => info.mode & 0o777, () => 0o644);
731
794
  await atomicWriteFile(path, JSON.stringify(parsed, null, 2) + "\n", { encoding: "utf8", mode });
732
795
  await services.runtime.session.settingsManager.reload();
@@ -989,15 +1052,17 @@ export default {
989
1052
  sendJson(response, 409, { error: "Plugin is already installed", plugin });
990
1053
  return;
991
1054
  }
992
- const packageJsonPath = join(services.launch.cwd, "package.json");
993
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1055
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1056
+ const packageJsonPath = join(installDirectory, "package.json");
1057
+ const packageLockPath = join(installDirectory, "package-lock.json");
994
1058
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
995
1059
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
996
1060
  let profileBefore;
997
1061
  let entryId;
998
1062
  try {
999
1063
  if (needsMarketplacePackageInstall(plugin)) {
1000
- await runProcess("npm", ["install", "--save-exact", "--package-lock=false", `${plugin.packageName}@${plugin.version}`], services.launch.cwd);
1064
+ const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
1065
+ await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
1001
1066
  }
1002
1067
  profileBefore = await appendMarketplaceProfile(configPath, plugin);
1003
1068
  if (!needsMarketplacePackageInstall(plugin)) {
@@ -1021,9 +1086,14 @@ export default {
1021
1086
  await loader.remove(entryId).catch(() => { });
1022
1087
  if (profileBefore !== undefined)
1023
1088
  await writeFile(configPath, profileBefore, "utf8").catch(() => { });
1024
- if (packageJsonBefore !== undefined)
1089
+ // A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
1090
+ if (packageJsonBefore === undefined)
1091
+ await unlink(packageJsonPath).catch(() => { });
1092
+ else
1025
1093
  await writeFile(packageJsonPath, packageJsonBefore, "utf8").catch(() => { });
1026
- if (packageLockBefore !== undefined)
1094
+ if (packageLockBefore === undefined)
1095
+ await unlink(packageLockPath).catch(() => { });
1096
+ else
1027
1097
  await writeFile(packageLockPath, packageLockBefore, "utf8").catch(() => { });
1028
1098
  sendJson(response, 502, { error: errorText(error) });
1029
1099
  }
@@ -1125,8 +1195,9 @@ export default {
1125
1195
  }
1126
1196
  const profileEntryId = entry.options.id;
1127
1197
  const profileBefore = await readFile(configPath, "utf8");
1128
- const packageJsonPath = join(services.launch.cwd, "package.json");
1129
- const packageLockPath = join(services.launch.cwd, "package-lock.json");
1198
+ const installDirectory = await marketplaceInstallDirectory(configPath, services.launch.cwd);
1199
+ const packageJsonPath = join(installDirectory, "package.json");
1200
+ const packageLockPath = join(installDirectory, "package-lock.json");
1130
1201
  const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
1131
1202
  const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
1132
1203
  await entry.parent.remove(entry.options.id);
@@ -1134,7 +1205,7 @@ export default {
1134
1205
  try {
1135
1206
  await updateMarketplaceProfile(configPath, profileEntryId, { remove: true });
1136
1207
  if (needsMarketplacePackageInstall(plugin))
1137
- await runProcess("npm", ["uninstall", "--package-lock=false", plugin.packageName], services.launch.cwd);
1208
+ await runProcess("npm", ["uninstall", "--package-lock=false", marketplaceNpmPackageName(plugin.packageName)], installDirectory);
1138
1209
  sendJson(response, 200, { uninstalled: true, id: payload.id });
1139
1210
  }
1140
1211
  catch (error) {
@@ -1246,18 +1317,11 @@ export default {
1246
1317
  sendJson(response, 500, { error: errorText(error) });
1247
1318
  return;
1248
1319
  }
1249
- const items = status.output
1250
- .split("\n")
1251
- .map((line) => line.trimEnd())
1252
- .filter((line) => line.length > 0)
1253
- .map((line) => {
1254
- const status = line.slice(0, 2).trim() || "??";
1255
- return {
1256
- path: line.slice(3),
1257
- status,
1258
- label: status === "??" ? "untracked" : status.includes("D") ? "deleted" : status.includes("A") ? "added" : "modified",
1259
- };
1260
- });
1320
+ const items = status.entries.map((entry) => ({
1321
+ path: entry.path,
1322
+ status: entry.status,
1323
+ label: entry.status === "??" ? "untracked" : entry.status.includes("D") ? "deleted" : entry.status.includes("A") ? "added" : "modified",
1324
+ }));
1261
1325
  sendJson(response, 200, { items, ...(status.truncated ? { truncated: true } : {}) });
1262
1326
  },
1263
1327
  });
@@ -1277,7 +1341,7 @@ export default {
1277
1341
  const root = resolve(activeCwd(services));
1278
1342
  const absolute = resolve(root, requested);
1279
1343
  const relativePath = relative(root, absolute);
1280
- if (isAbsolute(relativePath) || relativePath === ".." || relativePath.startsWith(".." + sep)) {
1344
+ if (escapesRoot(relativePath)) {
1281
1345
  sendJson(response, 400, { error: "path must stay inside the workspace" });
1282
1346
  return;
1283
1347
  }
@@ -1303,18 +1367,18 @@ export default {
1303
1367
  return;
1304
1368
  }
1305
1369
  const root = resolve(activeCwd(services));
1306
- const add = await gitCommand(root, ["add", "-A", "--", ...paths]);
1370
+ const add = await gitCommand(root, ["add", "-A", "--", ...paths], GIT_MUTATION_TIMEOUT_MS);
1307
1371
  if (add.terminated) {
1308
- sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated) });
1372
+ sendJson(response, 500, { error: gitTerminationMessage("add", add.terminated, GIT_MUTATION_TIMEOUT_MS) });
1309
1373
  return;
1310
1374
  }
1311
1375
  if (add.code !== 0) {
1312
1376
  sendJson(response, 409, { error: add.stderr.trim() || "Unable to stage workspace files" });
1313
1377
  return;
1314
1378
  }
1315
- const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths]);
1379
+ const commit = await gitCommand(root, ["commit", "-m", payload.message.trim(), "--", ...paths], GIT_MUTATION_TIMEOUT_MS);
1316
1380
  if (commit.terminated) {
1317
- sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated) });
1381
+ sendJson(response, 500, { error: gitTerminationMessage("commit", commit.terminated, GIT_MUTATION_TIMEOUT_MS) });
1318
1382
  return;
1319
1383
  }
1320
1384
  if (commit.code !== 0) {
@@ -1370,9 +1434,9 @@ export default {
1370
1434
  return;
1371
1435
  }
1372
1436
  if (trackedPaths.length > 0) {
1373
- const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths]);
1437
+ const restore = await gitCommand(root, ["restore", "--worktree", "--staged", "--", ...trackedPaths], GIT_MUTATION_TIMEOUT_MS);
1374
1438
  if (restore.terminated) {
1375
- sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated) });
1439
+ sendJson(response, 500, { error: gitTerminationMessage("restore", restore.terminated, GIT_MUTATION_TIMEOUT_MS) });
1376
1440
  return;
1377
1441
  }
1378
1442
  if (restore.code !== 0) {
@@ -1744,21 +1808,48 @@ export default {
1744
1808
  sendJson(response, 409, { error: "Cannot batch-delete the active session" });
1745
1809
  return;
1746
1810
  }
1747
- await mutateSessionMetadata(manager, context.logger, async (metadata) => {
1811
+ if (action === "delete") {
1812
+ // 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.
1813
+ const removed = [];
1814
+ const failed = [];
1748
1815
  for (const path of paths) {
1749
- if (action === "delete") {
1816
+ try {
1750
1817
  await unlink(path);
1751
- delete metadata[path];
1818
+ removed.push(path);
1752
1819
  }
1753
- else {
1754
- const current = metadata[path] ?? {};
1755
- metadata[path] = {
1756
- ...current,
1757
- ...(action === "archive" || action === "unarchive" ? { archived: action === "archive" } : {}),
1758
- ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1759
- };
1820
+ catch (error) {
1821
+ // A session another tab already deleted leaves nothing to unlink, but its metadata entry still has to go.
1822
+ if (error.code === "ENOENT")
1823
+ removed.push(path);
1824
+ else
1825
+ failed.push({ path, error: errorText(error) });
1760
1826
  }
1761
1827
  }
1828
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1829
+ for (const path of removed)
1830
+ delete metadata[path];
1831
+ });
1832
+ if (failed.length > 0) {
1833
+ sendJson(response, 409, {
1834
+ error: `Some sessions could not be deleted: ${failed.map((item) => item.path).join(", ")}`,
1835
+ action,
1836
+ count: removed.length,
1837
+ failed,
1838
+ });
1839
+ return;
1840
+ }
1841
+ sendJson(response, 200, { action, count: removed.length });
1842
+ return;
1843
+ }
1844
+ await mutateSessionMetadata(manager, context.logger, (metadata) => {
1845
+ for (const path of paths) {
1846
+ const current = metadata[path] ?? {};
1847
+ metadata[path] = {
1848
+ ...current,
1849
+ ...(action === "archive" || action === "unarchive" ? { archived: action === "archive" } : {}),
1850
+ ...(action === "pin" || action === "unpin" ? { pinned: action === "pin" } : {}),
1851
+ };
1852
+ }
1762
1853
  });
1763
1854
  sendJson(response, 200, { action, count: paths.length });
1764
1855
  }
@@ -1899,7 +1990,7 @@ export default {
1899
1990
  const page = Math.max(0, Number.parseInt(url.searchParams.get("page") ?? "0", 10) || 0);
1900
1991
  const pageSize = Math.min(100, Math.max(1, Number.parseInt(url.searchParams.get("pageSize") ?? "50", 10) || 50));
1901
1992
  const includeArchived = url.searchParams.get("includeArchived") === "true";
1902
- const metadata = await readSessionMetadata(manager, context.logger);
1993
+ const metadata = await readSessionMetadataLocked(manager, context.logger);
1903
1994
  const items = typeof manager.isPersisted === "function" && manager.isPersisted() ? await SessionManager.list(services.launch.cwd, manager.getSessionDir()) : [];
1904
1995
  const filtered = items.filter((item) => includeArchived || metadata[item.path]?.archived !== true);
1905
1996
  const sorted = filtered.sort((a, b) => Number(metadata[b.path]?.pinned === true) - Number(metadata[a.path]?.pinned === true) || b.modified.getTime() - a.modified.getTime());