@pi-harness/pi-harness 0.1.13 → 0.1.14

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 (1333) hide show
  1. package/README.md +678 -2
  2. package/apps/web/dist/assets/index-BVI_IS-R.css +2 -0
  3. package/apps/web/dist/assets/index-C98SJjJd.js +22 -0
  4. package/apps/web/dist/index.html +2 -2
  5. package/apps/web/package.json +6 -6
  6. package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
  7. package/node_modules/@pi-harness/api-gateway/dist/index.js +97 -9
  8. package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
  9. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/agent-teams.json +6 -6
  10. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  11. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/annotation.json +1 -2
  12. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  13. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/at-file.json +11 -5
  14. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  15. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  16. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-fetch.json +12 -5
  17. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/browser-session.json +14 -8
  18. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -4
  19. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  20. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cleaner.json +11 -6
  21. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cli-notifier.json +5 -5
  22. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/code2skill.json +5 -5
  23. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -2
  24. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context-doctor.json +12 -7
  25. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/context.json +10 -5
  26. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-group.json +2 -2
  27. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-logger-console.json +4 -4
  28. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cordis-timer.json +3 -3
  29. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/cost-meter.json +9 -6
  30. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/dependency-checker.json +13 -10
  31. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +10 -5
  32. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/fail-logger.json +11 -6
  33. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/genui.json +7 -5
  34. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +13 -8
  35. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/graph-memory.json +13 -7
  36. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  37. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
  38. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/i18n-pair.json +9 -6
  39. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  40. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  41. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-client.json +12 -7
  42. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  43. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  44. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  45. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  46. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/modlens.json +4 -4
  47. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  48. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  49. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/openpets.json +9 -5
  50. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  51. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  52. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-dev.json +8 -6
  53. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  54. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  55. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/plugin-stars.json +15 -7
  56. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  57. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  58. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/readme-gen.json +12 -8
  59. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/recall-unread.json +11 -6
  60. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  61. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  62. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  63. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  64. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  65. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-bridge.json +13 -8
  66. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  67. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  68. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-insights.json +12 -6
  69. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  70. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  71. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/skill-guard.json +9 -5
  72. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/sql-lens.json +13 -7
  73. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  74. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  75. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  76. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  77. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/test-harness.json +14 -7
  78. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  79. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/token-guard.json +12 -6
  80. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/turn-rewind.json +11 -6
  81. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  82. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +8 -5
  83. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  84. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  85. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  86. package/node_modules/@pi-harness/api-gateway/dist/marketplace-entries/official/yaml-validator.json +8 -5
  87. package/node_modules/@pi-harness/api-gateway/package.json +1 -1
  88. package/node_modules/@pi-harness/cli/dist/node-stdio.d.ts.map +1 -1
  89. package/node_modules/@pi-harness/cli/dist/node-stdio.js +83 -20
  90. package/node_modules/@pi-harness/cli/dist/node-stdio.js.map +1 -1
  91. package/node_modules/@pi-harness/cli/package.json +1 -1
  92. package/node_modules/@pi-harness/core/dist/atomic-write.d.ts +8 -0
  93. package/node_modules/@pi-harness/core/dist/atomic-write.d.ts.map +1 -0
  94. package/node_modules/@pi-harness/core/dist/atomic-write.js +40 -0
  95. package/node_modules/@pi-harness/core/dist/atomic-write.js.map +1 -0
  96. package/node_modules/@pi-harness/core/dist/boot.d.ts.map +1 -1
  97. package/node_modules/@pi-harness/core/dist/boot.js +132 -10
  98. package/node_modules/@pi-harness/core/dist/boot.js.map +1 -1
  99. package/node_modules/@pi-harness/core/dist/bounded-file.d.ts +7 -0
  100. package/node_modules/@pi-harness/core/dist/bounded-file.d.ts.map +1 -0
  101. package/node_modules/@pi-harness/core/dist/bounded-file.js +50 -0
  102. package/node_modules/@pi-harness/core/dist/bounded-file.js.map +1 -0
  103. package/node_modules/@pi-harness/core/dist/config.d.ts +2 -0
  104. package/node_modules/@pi-harness/core/dist/config.d.ts.map +1 -1
  105. package/node_modules/@pi-harness/core/dist/config.js +21 -0
  106. package/node_modules/@pi-harness/core/dist/config.js.map +1 -1
  107. package/node_modules/@pi-harness/core/dist/cordis-timer.d.ts +15 -0
  108. package/node_modules/@pi-harness/core/dist/cordis-timer.d.ts.map +1 -0
  109. package/node_modules/@pi-harness/core/dist/cordis-timer.js +99 -0
  110. package/node_modules/@pi-harness/core/dist/cordis-timer.js.map +1 -0
  111. package/node_modules/@pi-harness/core/dist/plugins/agent-teams.d.ts +5 -1
  112. package/node_modules/@pi-harness/core/dist/plugins/agent-teams.d.ts.map +1 -1
  113. package/node_modules/@pi-harness/core/dist/plugins/agent-teams.js +986 -105
  114. package/node_modules/@pi-harness/core/dist/plugins/agent-teams.js.map +1 -1
  115. package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.d.ts +1 -0
  116. package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.d.ts.map +1 -1
  117. package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.js +39 -11
  118. package/node_modules/@pi-harness/core/dist/plugins/anchored-standard.js.map +1 -1
  119. package/node_modules/@pi-harness/core/dist/plugins/annotation.d.ts +1 -0
  120. package/node_modules/@pi-harness/core/dist/plugins/annotation.d.ts.map +1 -1
  121. package/node_modules/@pi-harness/core/dist/plugins/annotation.js +35 -17
  122. package/node_modules/@pi-harness/core/dist/plugins/annotation.js.map +1 -1
  123. package/node_modules/@pi-harness/core/dist/plugins/archify.d.ts +1 -0
  124. package/node_modules/@pi-harness/core/dist/plugins/archify.d.ts.map +1 -1
  125. package/node_modules/@pi-harness/core/dist/plugins/archify.js +69 -22
  126. package/node_modules/@pi-harness/core/dist/plugins/archify.js.map +1 -1
  127. package/node_modules/@pi-harness/core/dist/plugins/at-file.d.ts +5 -1
  128. package/node_modules/@pi-harness/core/dist/plugins/at-file.d.ts.map +1 -1
  129. package/node_modules/@pi-harness/core/dist/plugins/at-file.js +120 -29
  130. package/node_modules/@pi-harness/core/dist/plugins/at-file.js.map +1 -1
  131. package/node_modules/@pi-harness/core/dist/plugins/auto-mode.d.ts.map +1 -1
  132. package/node_modules/@pi-harness/core/dist/plugins/auto-mode.js +89 -20
  133. package/node_modules/@pi-harness/core/dist/plugins/auto-mode.js.map +1 -1
  134. package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.d.ts +1 -0
  135. package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.d.ts.map +1 -1
  136. package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.js +14 -4
  137. package/node_modules/@pi-harness/core/dist/plugins/better-sidebar.js.map +1 -1
  138. package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.d.ts +1 -0
  139. package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.d.ts.map +1 -1
  140. package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.js +288 -96
  141. package/node_modules/@pi-harness/core/dist/plugins/browser-fetch.js.map +1 -1
  142. package/node_modules/@pi-harness/core/dist/plugins/browser-session.d.ts.map +1 -1
  143. package/node_modules/@pi-harness/core/dist/plugins/browser-session.js +445 -106
  144. package/node_modules/@pi-harness/core/dist/plugins/browser-session.js.map +1 -1
  145. package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.d.ts +1 -0
  146. package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.d.ts.map +1 -1
  147. package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.js +71 -19
  148. package/node_modules/@pi-harness/core/dist/plugins/canvas-draw.js.map +1 -1
  149. package/node_modules/@pi-harness/core/dist/plugins/change-verifier.d.ts +1 -0
  150. package/node_modules/@pi-harness/core/dist/plugins/change-verifier.d.ts.map +1 -1
  151. package/node_modules/@pi-harness/core/dist/plugins/change-verifier.js +30 -9
  152. package/node_modules/@pi-harness/core/dist/plugins/change-verifier.js.map +1 -1
  153. package/node_modules/@pi-harness/core/dist/plugins/cleaner.d.ts +5 -1
  154. package/node_modules/@pi-harness/core/dist/plugins/cleaner.d.ts.map +1 -1
  155. package/node_modules/@pi-harness/core/dist/plugins/cleaner.js +337 -40
  156. package/node_modules/@pi-harness/core/dist/plugins/cleaner.js.map +1 -1
  157. package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.d.ts +2 -1
  158. package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.d.ts.map +1 -1
  159. package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.js +99 -42
  160. package/node_modules/@pi-harness/core/dist/plugins/cli-notifier.js.map +1 -1
  161. package/node_modules/@pi-harness/core/dist/plugins/code2skill.d.ts +1 -0
  162. package/node_modules/@pi-harness/core/dist/plugins/code2skill.d.ts.map +1 -1
  163. package/node_modules/@pi-harness/core/dist/plugins/code2skill.js +178 -38
  164. package/node_modules/@pi-harness/core/dist/plugins/code2skill.js.map +1 -1
  165. package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.d.ts +2 -1
  166. package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.d.ts.map +1 -1
  167. package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.js +79 -49
  168. package/node_modules/@pi-harness/core/dist/plugins/colleague-skill.js.map +1 -1
  169. package/node_modules/@pi-harness/core/dist/plugins/context-doctor.d.ts +6 -1
  170. package/node_modules/@pi-harness/core/dist/plugins/context-doctor.d.ts.map +1 -1
  171. package/node_modules/@pi-harness/core/dist/plugins/context-doctor.js +403 -39
  172. package/node_modules/@pi-harness/core/dist/plugins/context-doctor.js.map +1 -1
  173. package/node_modules/@pi-harness/core/dist/plugins/context.d.ts +1 -0
  174. package/node_modules/@pi-harness/core/dist/plugins/context.d.ts.map +1 -1
  175. package/node_modules/@pi-harness/core/dist/plugins/context.js +169 -42
  176. package/node_modules/@pi-harness/core/dist/plugins/context.js.map +1 -1
  177. package/node_modules/@pi-harness/core/dist/plugins/cost-meter.d.ts +4 -0
  178. package/node_modules/@pi-harness/core/dist/plugins/cost-meter.d.ts.map +1 -1
  179. package/node_modules/@pi-harness/core/dist/plugins/cost-meter.js +319 -93
  180. package/node_modules/@pi-harness/core/dist/plugins/cost-meter.js.map +1 -1
  181. package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.d.ts +11 -2
  182. package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.d.ts.map +1 -1
  183. package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.js +463 -114
  184. package/node_modules/@pi-harness/core/dist/plugins/dependency-checker.js.map +1 -1
  185. package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.d.ts +1 -0
  186. package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.d.ts.map +1 -1
  187. package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.js +322 -80
  188. package/node_modules/@pi-harness/core/dist/plugins/docker-sandbox.js.map +1 -1
  189. package/node_modules/@pi-harness/core/dist/plugins/fail-logger.d.ts +1 -0
  190. package/node_modules/@pi-harness/core/dist/plugins/fail-logger.d.ts.map +1 -1
  191. package/node_modules/@pi-harness/core/dist/plugins/fail-logger.js +187 -20
  192. package/node_modules/@pi-harness/core/dist/plugins/fail-logger.js.map +1 -1
  193. package/node_modules/@pi-harness/core/dist/plugins/genui.d.ts +1 -0
  194. package/node_modules/@pi-harness/core/dist/plugins/genui.d.ts.map +1 -1
  195. package/node_modules/@pi-harness/core/dist/plugins/genui.js +103 -38
  196. package/node_modules/@pi-harness/core/dist/plugins/genui.js.map +1 -1
  197. package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.d.ts +8 -2
  198. package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.d.ts.map +1 -1
  199. package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.js +400 -54
  200. package/node_modules/@pi-harness/core/dist/plugins/git-time-capsule.js.map +1 -1
  201. package/node_modules/@pi-harness/core/dist/plugins/graph-memory.d.ts.map +1 -1
  202. package/node_modules/@pi-harness/core/dist/plugins/graph-memory.js +329 -42
  203. package/node_modules/@pi-harness/core/dist/plugins/graph-memory.js.map +1 -1
  204. package/node_modules/@pi-harness/core/dist/plugins/history-compressor.d.ts.map +1 -1
  205. package/node_modules/@pi-harness/core/dist/plugins/history-compressor.js +31 -12
  206. package/node_modules/@pi-harness/core/dist/plugins/history-compressor.js.map +1 -1
  207. package/node_modules/@pi-harness/core/dist/plugins/hol-guard.d.ts.map +1 -1
  208. package/node_modules/@pi-harness/core/dist/plugins/hol-guard.js +33 -13
  209. package/node_modules/@pi-harness/core/dist/plugins/hol-guard.js.map +1 -1
  210. package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.d.ts +5 -1
  211. package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.d.ts.map +1 -1
  212. package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.js +308 -52
  213. package/node_modules/@pi-harness/core/dist/plugins/i18n-pair.js.map +1 -1
  214. package/node_modules/@pi-harness/core/dist/plugins/image-compressor.d.ts +7 -0
  215. package/node_modules/@pi-harness/core/dist/plugins/image-compressor.d.ts.map +1 -1
  216. package/node_modules/@pi-harness/core/dist/plugins/image-compressor.js +98 -25
  217. package/node_modules/@pi-harness/core/dist/plugins/image-compressor.js.map +1 -1
  218. package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.d.ts.map +1 -1
  219. package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.js +21 -11
  220. package/node_modules/@pi-harness/core/dist/plugins/llm-verifier.js.map +1 -1
  221. package/node_modules/@pi-harness/core/dist/plugins/mcp-client.d.ts.map +1 -1
  222. package/node_modules/@pi-harness/core/dist/plugins/mcp-client.js +986 -232
  223. package/node_modules/@pi-harness/core/dist/plugins/mcp-client.js.map +1 -1
  224. package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.d.ts.map +1 -1
  225. package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.js +30 -16
  226. package/node_modules/@pi-harness/core/dist/plugins/mcp-panel.js.map +1 -1
  227. package/node_modules/@pi-harness/core/dist/plugins/memory.d.ts.map +1 -1
  228. package/node_modules/@pi-harness/core/dist/plugins/memory.js +292 -59
  229. package/node_modules/@pi-harness/core/dist/plugins/memory.js.map +1 -1
  230. package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.d.ts.map +1 -1
  231. package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.js +20 -10
  232. package/node_modules/@pi-harness/core/dist/plugins/mirage-bridge.js.map +1 -1
  233. package/node_modules/@pi-harness/core/dist/plugins/mock-server.d.ts.map +1 -1
  234. package/node_modules/@pi-harness/core/dist/plugins/mock-server.js +85 -54
  235. package/node_modules/@pi-harness/core/dist/plugins/mock-server.js.map +1 -1
  236. package/node_modules/@pi-harness/core/dist/plugins/model.d.ts +3 -0
  237. package/node_modules/@pi-harness/core/dist/plugins/model.d.ts.map +1 -1
  238. package/node_modules/@pi-harness/core/dist/plugins/model.js +3 -0
  239. package/node_modules/@pi-harness/core/dist/plugins/model.js.map +1 -1
  240. package/node_modules/@pi-harness/core/dist/plugins/models.d.ts.map +1 -1
  241. package/node_modules/@pi-harness/core/dist/plugins/models.js +10 -2
  242. package/node_modules/@pi-harness/core/dist/plugins/models.js.map +1 -1
  243. package/node_modules/@pi-harness/core/dist/plugins/modlens.d.ts +8 -1
  244. package/node_modules/@pi-harness/core/dist/plugins/modlens.d.ts.map +1 -1
  245. package/node_modules/@pi-harness/core/dist/plugins/modlens.js +438 -31
  246. package/node_modules/@pi-harness/core/dist/plugins/modlens.js.map +1 -1
  247. package/node_modules/@pi-harness/core/dist/plugins/module-search.d.ts +1 -0
  248. package/node_modules/@pi-harness/core/dist/plugins/module-search.d.ts.map +1 -1
  249. package/node_modules/@pi-harness/core/dist/plugins/module-search.js +35 -16
  250. package/node_modules/@pi-harness/core/dist/plugins/module-search.js.map +1 -1
  251. package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.d.ts.map +1 -1
  252. package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.js +20 -11
  253. package/node_modules/@pi-harness/core/dist/plugins/obsidian-sync.js.map +1 -1
  254. package/node_modules/@pi-harness/core/dist/plugins/openpets.d.ts.map +1 -1
  255. package/node_modules/@pi-harness/core/dist/plugins/openpets.js +203 -44
  256. package/node_modules/@pi-harness/core/dist/plugins/openpets.js.map +1 -1
  257. package/node_modules/@pi-harness/core/dist/plugins/plan-execute.d.ts +4 -1
  258. package/node_modules/@pi-harness/core/dist/plugins/plan-execute.d.ts.map +1 -1
  259. package/node_modules/@pi-harness/core/dist/plugins/plan-execute.js +42 -21
  260. package/node_modules/@pi-harness/core/dist/plugins/plan-execute.js.map +1 -1
  261. package/node_modules/@pi-harness/core/dist/plugins/plugin-check.d.ts +12 -3
  262. package/node_modules/@pi-harness/core/dist/plugins/plugin-check.d.ts.map +1 -1
  263. package/node_modules/@pi-harness/core/dist/plugins/plugin-check.js +83 -12
  264. package/node_modules/@pi-harness/core/dist/plugins/plugin-check.js.map +1 -1
  265. package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.d.ts +1 -0
  266. package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.d.ts.map +1 -1
  267. package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.js +156 -23
  268. package/node_modules/@pi-harness/core/dist/plugins/plugin-dev.js.map +1 -1
  269. package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.d.ts +1 -0
  270. package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.d.ts.map +1 -1
  271. package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.js +131 -13
  272. package/node_modules/@pi-harness/core/dist/plugins/plugin-finder.js.map +1 -1
  273. package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.d.ts +4 -0
  274. package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.d.ts.map +1 -1
  275. package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.js +137 -36
  276. package/node_modules/@pi-harness/core/dist/plugins/plugin-radar.js.map +1 -1
  277. package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.d.ts.map +1 -1
  278. package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.js +282 -81
  279. package/node_modules/@pi-harness/core/dist/plugins/plugin-stars.js.map +1 -1
  280. package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.d.ts +1 -0
  281. package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.d.ts.map +1 -1
  282. package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.js +60 -9
  283. package/node_modules/@pi-harness/core/dist/plugins/prompt-guard.js.map +1 -1
  284. package/node_modules/@pi-harness/core/dist/plugins/prompt-library.d.ts +1 -0
  285. package/node_modules/@pi-harness/core/dist/plugins/prompt-library.d.ts.map +1 -1
  286. package/node_modules/@pi-harness/core/dist/plugins/prompt-library.js +4 -1
  287. package/node_modules/@pi-harness/core/dist/plugins/prompt-library.js.map +1 -1
  288. package/node_modules/@pi-harness/core/dist/plugins/readme-gen.d.ts +6 -2
  289. package/node_modules/@pi-harness/core/dist/plugins/readme-gen.d.ts.map +1 -1
  290. package/node_modules/@pi-harness/core/dist/plugins/readme-gen.js +514 -73
  291. package/node_modules/@pi-harness/core/dist/plugins/readme-gen.js.map +1 -1
  292. package/node_modules/@pi-harness/core/dist/plugins/recall-unread.d.ts +1 -1
  293. package/node_modules/@pi-harness/core/dist/plugins/recall-unread.d.ts.map +1 -1
  294. package/node_modules/@pi-harness/core/dist/plugins/recall-unread.js +411 -68
  295. package/node_modules/@pi-harness/core/dist/plugins/recall-unread.js.map +1 -1
  296. package/node_modules/@pi-harness/core/dist/plugins/resources.d.ts.map +1 -1
  297. package/node_modules/@pi-harness/core/dist/plugins/resources.js +66 -3
  298. package/node_modules/@pi-harness/core/dist/plugins/resources.js.map +1 -1
  299. package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.d.ts.map +1 -1
  300. package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.js +2 -1
  301. package/node_modules/@pi-harness/core/dist/plugins/reverse-skill.js.map +1 -1
  302. package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.d.ts +1 -0
  303. package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.d.ts.map +1 -1
  304. package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.js +35 -16
  305. package/node_modules/@pi-harness/core/dist/plugins/reviewer-bot.js.map +1 -1
  306. package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.d.ts +1 -0
  307. package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.d.ts.map +1 -1
  308. package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.js +4 -1
  309. package/node_modules/@pi-harness/core/dist/plugins/runtime-doctor.js.map +1 -1
  310. package/node_modules/@pi-harness/core/dist/plugins/runtime.d.ts.map +1 -1
  311. package/node_modules/@pi-harness/core/dist/plugins/runtime.js +104 -70
  312. package/node_modules/@pi-harness/core/dist/plugins/runtime.js.map +1 -1
  313. package/node_modules/@pi-harness/core/dist/plugins/secure-audit.d.ts +1 -0
  314. package/node_modules/@pi-harness/core/dist/plugins/secure-audit.d.ts.map +1 -1
  315. package/node_modules/@pi-harness/core/dist/plugins/secure-audit.js +30 -12
  316. package/node_modules/@pi-harness/core/dist/plugins/secure-audit.js.map +1 -1
  317. package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.d.ts +1 -0
  318. package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.d.ts.map +1 -1
  319. package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.js +4 -1
  320. package/node_modules/@pi-harness/core/dist/plugins/session-bookmarks.js.map +1 -1
  321. package/node_modules/@pi-harness/core/dist/plugins/session-bridge.d.ts +1 -0
  322. package/node_modules/@pi-harness/core/dist/plugins/session-bridge.d.ts.map +1 -1
  323. package/node_modules/@pi-harness/core/dist/plugins/session-bridge.js +401 -88
  324. package/node_modules/@pi-harness/core/dist/plugins/session-bridge.js.map +1 -1
  325. package/node_modules/@pi-harness/core/dist/plugins/session-compare.d.ts +1 -0
  326. package/node_modules/@pi-harness/core/dist/plugins/session-compare.d.ts.map +1 -1
  327. package/node_modules/@pi-harness/core/dist/plugins/session-compare.js +4 -1
  328. package/node_modules/@pi-harness/core/dist/plugins/session-compare.js.map +1 -1
  329. package/node_modules/@pi-harness/core/dist/plugins/session-export.d.ts +1 -0
  330. package/node_modules/@pi-harness/core/dist/plugins/session-export.d.ts.map +1 -1
  331. package/node_modules/@pi-harness/core/dist/plugins/session-export.js +6 -6
  332. package/node_modules/@pi-harness/core/dist/plugins/session-export.js.map +1 -1
  333. package/node_modules/@pi-harness/core/dist/plugins/session-insights.d.ts +37 -1
  334. package/node_modules/@pi-harness/core/dist/plugins/session-insights.d.ts.map +1 -1
  335. package/node_modules/@pi-harness/core/dist/plugins/session-insights.js +406 -21
  336. package/node_modules/@pi-harness/core/dist/plugins/session-insights.js.map +1 -1
  337. package/node_modules/@pi-harness/core/dist/plugins/session-search.d.ts +1 -0
  338. package/node_modules/@pi-harness/core/dist/plugins/session-search.d.ts.map +1 -1
  339. package/node_modules/@pi-harness/core/dist/plugins/session-search.js +4 -1
  340. package/node_modules/@pi-harness/core/dist/plugins/session-search.js.map +1 -1
  341. package/node_modules/@pi-harness/core/dist/plugins/session.d.ts.map +1 -1
  342. package/node_modules/@pi-harness/core/dist/plugins/session.js +8 -1
  343. package/node_modules/@pi-harness/core/dist/plugins/session.js.map +1 -1
  344. package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.d.ts +1 -0
  345. package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.d.ts.map +1 -1
  346. package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.js +6 -5
  347. package/node_modules/@pi-harness/core/dist/plugins/skill-catalog.js.map +1 -1
  348. package/node_modules/@pi-harness/core/dist/plugins/skill-guard.d.ts +2 -1
  349. package/node_modules/@pi-harness/core/dist/plugins/skill-guard.d.ts.map +1 -1
  350. package/node_modules/@pi-harness/core/dist/plugins/skill-guard.js +241 -61
  351. package/node_modules/@pi-harness/core/dist/plugins/skill-guard.js.map +1 -1
  352. package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.d.ts +2 -0
  353. package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.d.ts.map +1 -0
  354. package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.js +206 -0
  355. package/node_modules/@pi-harness/core/dist/plugins/sql-lens-worker.js.map +1 -0
  356. package/node_modules/@pi-harness/core/dist/plugins/sql-lens.d.ts +7 -1
  357. package/node_modules/@pi-harness/core/dist/plugins/sql-lens.d.ts.map +1 -1
  358. package/node_modules/@pi-harness/core/dist/plugins/sql-lens.js +301 -68
  359. package/node_modules/@pi-harness/core/dist/plugins/sql-lens.js.map +1 -1
  360. package/node_modules/@pi-harness/core/dist/plugins/stdio.d.ts +3 -0
  361. package/node_modules/@pi-harness/core/dist/plugins/stdio.d.ts.map +1 -1
  362. package/node_modules/@pi-harness/core/dist/plugins/stdio.js +62 -4
  363. package/node_modules/@pi-harness/core/dist/plugins/stdio.js.map +1 -1
  364. package/node_modules/@pi-harness/core/dist/plugins/synapse.d.ts.map +1 -1
  365. package/node_modules/@pi-harness/core/dist/plugins/synapse.js +17 -9
  366. package/node_modules/@pi-harness/core/dist/plugins/synapse.js.map +1 -1
  367. package/node_modules/@pi-harness/core/dist/plugins/tab-manager.d.ts +1 -0
  368. package/node_modules/@pi-harness/core/dist/plugins/tab-manager.d.ts.map +1 -1
  369. package/node_modules/@pi-harness/core/dist/plugins/tab-manager.js +167 -62
  370. package/node_modules/@pi-harness/core/dist/plugins/tab-manager.js.map +1 -1
  371. package/node_modules/@pi-harness/core/dist/plugins/taskboard.d.ts.map +1 -1
  372. package/node_modules/@pi-harness/core/dist/plugins/taskboard.js +50 -22
  373. package/node_modules/@pi-harness/core/dist/plugins/taskboard.js.map +1 -1
  374. package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.d.ts +4 -1
  375. package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.d.ts.map +1 -1
  376. package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.js +30 -11
  377. package/node_modules/@pi-harness/core/dist/plugins/telemetry-blocker.js.map +1 -1
  378. package/node_modules/@pi-harness/core/dist/plugins/test-harness.d.ts +7 -1
  379. package/node_modules/@pi-harness/core/dist/plugins/test-harness.d.ts.map +1 -1
  380. package/node_modules/@pi-harness/core/dist/plugins/test-harness.js +255 -43
  381. package/node_modules/@pi-harness/core/dist/plugins/test-harness.js.map +1 -1
  382. package/node_modules/@pi-harness/core/dist/plugins/theme-studio.d.ts.map +1 -1
  383. package/node_modules/@pi-harness/core/dist/plugins/theme-studio.js +4 -2
  384. package/node_modules/@pi-harness/core/dist/plugins/theme-studio.js.map +1 -1
  385. package/node_modules/@pi-harness/core/dist/plugins/token-guard.d.ts.map +1 -1
  386. package/node_modules/@pi-harness/core/dist/plugins/token-guard.js +164 -32
  387. package/node_modules/@pi-harness/core/dist/plugins/token-guard.js.map +1 -1
  388. package/node_modules/@pi-harness/core/dist/plugins/tools.d.ts.map +1 -1
  389. package/node_modules/@pi-harness/core/dist/plugins/tools.js +4 -1
  390. package/node_modules/@pi-harness/core/dist/plugins/tools.js.map +1 -1
  391. package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.d.ts +17 -0
  392. package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.d.ts.map +1 -1
  393. package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.js +340 -36
  394. package/node_modules/@pi-harness/core/dist/plugins/turn-rewind.js.map +1 -1
  395. package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.d.ts.map +1 -1
  396. package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.js +157 -51
  397. package/node_modules/@pi-harness/core/dist/plugins/undo-savepoint.js.map +1 -1
  398. package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.d.ts +22 -3
  399. package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.d.ts.map +1 -1
  400. package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.js +389 -66
  401. package/node_modules/@pi-harness/core/dist/plugins/vision-toolkit.js.map +1 -1
  402. package/node_modules/@pi-harness/core/dist/plugins/web-research.d.ts.map +1 -1
  403. package/node_modules/@pi-harness/core/dist/plugins/web-research.js +13 -4
  404. package/node_modules/@pi-harness/core/dist/plugins/web-research.js.map +1 -1
  405. package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.d.ts +8 -2
  406. package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.d.ts.map +1 -1
  407. package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.js +28 -10
  408. package/node_modules/@pi-harness/core/dist/plugins/workspace-navigator.js.map +1 -1
  409. package/node_modules/@pi-harness/core/dist/plugins/workspace-search.d.ts +7 -0
  410. package/node_modules/@pi-harness/core/dist/plugins/workspace-search.d.ts.map +1 -1
  411. package/node_modules/@pi-harness/core/dist/plugins/workspace-search.js +67 -34
  412. package/node_modules/@pi-harness/core/dist/plugins/workspace-search.js.map +1 -1
  413. package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.d.ts +1 -0
  414. package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.d.ts.map +1 -1
  415. package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.js +156 -47
  416. package/node_modules/@pi-harness/core/dist/plugins/yaml-validator.js.map +1 -1
  417. package/node_modules/@pi-harness/core/dist/runtime.d.ts.map +1 -1
  418. package/node_modules/@pi-harness/core/dist/runtime.js +8 -3
  419. package/node_modules/@pi-harness/core/dist/runtime.js.map +1 -1
  420. package/node_modules/@pi-harness/core/dist/services.d.ts.map +1 -1
  421. package/node_modules/@pi-harness/core/dist/services.js +22 -2
  422. package/node_modules/@pi-harness/core/dist/services.js.map +1 -1
  423. package/node_modules/@pi-harness/core/dist/stdio.d.ts +1 -0
  424. package/node_modules/@pi-harness/core/dist/stdio.d.ts.map +1 -1
  425. package/node_modules/@pi-harness/core/dist/stdio.js +241 -25
  426. package/node_modules/@pi-harness/core/dist/stdio.js.map +1 -1
  427. package/node_modules/@pi-harness/core/dist/workspace-path.d.ts +1 -0
  428. package/node_modules/@pi-harness/core/dist/workspace-path.d.ts.map +1 -1
  429. package/node_modules/@pi-harness/core/dist/workspace-path.js +9 -0
  430. package/node_modules/@pi-harness/core/dist/workspace-path.js.map +1 -1
  431. package/node_modules/@pi-harness/core/package.json +1 -1
  432. package/node_modules/@pi-harness/core/profiles/default/cordis.yml +4 -2
  433. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  434. package/node_modules/@pi-harness/web-app/package.json +1 -1
  435. package/package.json +12 -7
  436. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  437. package/packages/api-gateway/dist/index.js +97 -9
  438. package/packages/api-gateway/dist/index.js.map +1 -1
  439. package/packages/api-gateway/dist/marketplace-entries/official/agent-teams.json +6 -6
  440. package/packages/api-gateway/dist/marketplace-entries/official/anchored-standard.json +1 -1
  441. package/packages/api-gateway/dist/marketplace-entries/official/annotation.json +1 -2
  442. package/packages/api-gateway/dist/marketplace-entries/official/archify.json +1 -1
  443. package/packages/api-gateway/dist/marketplace-entries/official/at-file.json +11 -5
  444. package/packages/api-gateway/dist/marketplace-entries/official/auto-mode.json +1 -1
  445. package/packages/api-gateway/dist/marketplace-entries/official/better-sidebar.json +1 -1
  446. package/packages/api-gateway/dist/marketplace-entries/official/browser-fetch.json +12 -5
  447. package/packages/api-gateway/dist/marketplace-entries/official/browser-session.json +14 -8
  448. package/packages/api-gateway/dist/marketplace-entries/official/canvas-draw.json +3 -4
  449. package/packages/api-gateway/dist/marketplace-entries/official/change-verifier.json +1 -1
  450. package/packages/api-gateway/dist/marketplace-entries/official/cleaner.json +11 -6
  451. package/packages/api-gateway/dist/marketplace-entries/official/cli-notifier.json +5 -5
  452. package/packages/api-gateway/dist/marketplace-entries/official/code2skill.json +5 -5
  453. package/packages/api-gateway/dist/marketplace-entries/official/colleague-skill.json +1 -2
  454. package/packages/api-gateway/dist/marketplace-entries/official/context-doctor.json +12 -7
  455. package/packages/api-gateway/dist/marketplace-entries/official/context.json +10 -5
  456. package/packages/api-gateway/dist/marketplace-entries/official/cordis-group.json +2 -2
  457. package/packages/api-gateway/dist/marketplace-entries/official/cordis-logger-console.json +4 -4
  458. package/packages/api-gateway/dist/marketplace-entries/official/cordis-timer.json +3 -3
  459. package/packages/api-gateway/dist/marketplace-entries/official/cost-meter.json +9 -6
  460. package/packages/api-gateway/dist/marketplace-entries/official/dependency-checker.json +13 -10
  461. package/packages/api-gateway/dist/marketplace-entries/official/docker-sandbox.json +10 -5
  462. package/packages/api-gateway/dist/marketplace-entries/official/fail-logger.json +11 -6
  463. package/packages/api-gateway/dist/marketplace-entries/official/genui.json +7 -5
  464. package/packages/api-gateway/dist/marketplace-entries/official/git-time-capsule.json +13 -8
  465. package/packages/api-gateway/dist/marketplace-entries/official/graph-memory.json +13 -7
  466. package/packages/api-gateway/dist/marketplace-entries/official/history-compressor.json +1 -1
  467. package/packages/api-gateway/dist/marketplace-entries/official/hol-guard.json +1 -1
  468. package/packages/api-gateway/dist/marketplace-entries/official/i18n-pair.json +9 -6
  469. package/packages/api-gateway/dist/marketplace-entries/official/image-compressor.json +1 -1
  470. package/packages/api-gateway/dist/marketplace-entries/official/llm-verifier.json +1 -1
  471. package/packages/api-gateway/dist/marketplace-entries/official/mcp-client.json +12 -7
  472. package/packages/api-gateway/dist/marketplace-entries/official/mcp-panel.json +1 -1
  473. package/packages/api-gateway/dist/marketplace-entries/official/memory.json +1 -1
  474. package/packages/api-gateway/dist/marketplace-entries/official/mirage-bridge.json +1 -1
  475. package/packages/api-gateway/dist/marketplace-entries/official/mock-server.json +1 -1
  476. package/packages/api-gateway/dist/marketplace-entries/official/modlens.json +4 -4
  477. package/packages/api-gateway/dist/marketplace-entries/official/module-search.json +1 -1
  478. package/packages/api-gateway/dist/marketplace-entries/official/obsidian-sync.json +1 -1
  479. package/packages/api-gateway/dist/marketplace-entries/official/openpets.json +9 -5
  480. package/packages/api-gateway/dist/marketplace-entries/official/plan-execute.json +1 -1
  481. package/packages/api-gateway/dist/marketplace-entries/official/plugin-check.json +1 -1
  482. package/packages/api-gateway/dist/marketplace-entries/official/plugin-dev.json +8 -6
  483. package/packages/api-gateway/dist/marketplace-entries/official/plugin-finder.json +1 -1
  484. package/packages/api-gateway/dist/marketplace-entries/official/plugin-radar.json +1 -1
  485. package/packages/api-gateway/dist/marketplace-entries/official/plugin-stars.json +15 -7
  486. package/packages/api-gateway/dist/marketplace-entries/official/prompt-guard.json +1 -1
  487. package/packages/api-gateway/dist/marketplace-entries/official/prompt-library.json +1 -1
  488. package/packages/api-gateway/dist/marketplace-entries/official/readme-gen.json +12 -8
  489. package/packages/api-gateway/dist/marketplace-entries/official/recall-unread.json +11 -6
  490. package/packages/api-gateway/dist/marketplace-entries/official/reverse-skill.json +1 -1
  491. package/packages/api-gateway/dist/marketplace-entries/official/reviewer-bot.json +1 -1
  492. package/packages/api-gateway/dist/marketplace-entries/official/runtime-doctor.json +1 -1
  493. package/packages/api-gateway/dist/marketplace-entries/official/secure-audit.json +1 -1
  494. package/packages/api-gateway/dist/marketplace-entries/official/session-bookmarks.json +1 -1
  495. package/packages/api-gateway/dist/marketplace-entries/official/session-bridge.json +13 -8
  496. package/packages/api-gateway/dist/marketplace-entries/official/session-compare.json +1 -1
  497. package/packages/api-gateway/dist/marketplace-entries/official/session-export.json +1 -1
  498. package/packages/api-gateway/dist/marketplace-entries/official/session-insights.json +12 -6
  499. package/packages/api-gateway/dist/marketplace-entries/official/session-search.json +1 -1
  500. package/packages/api-gateway/dist/marketplace-entries/official/skill-catalog.json +1 -1
  501. package/packages/api-gateway/dist/marketplace-entries/official/skill-guard.json +9 -5
  502. package/packages/api-gateway/dist/marketplace-entries/official/sql-lens.json +13 -7
  503. package/packages/api-gateway/dist/marketplace-entries/official/synapse.json +1 -1
  504. package/packages/api-gateway/dist/marketplace-entries/official/tab-manager.json +1 -1
  505. package/packages/api-gateway/dist/marketplace-entries/official/taskboard.json +1 -1
  506. package/packages/api-gateway/dist/marketplace-entries/official/telemetry-blocker.json +1 -1
  507. package/packages/api-gateway/dist/marketplace-entries/official/test-harness.json +14 -7
  508. package/packages/api-gateway/dist/marketplace-entries/official/theme-studio.json +1 -1
  509. package/packages/api-gateway/dist/marketplace-entries/official/token-guard.json +12 -6
  510. package/packages/api-gateway/dist/marketplace-entries/official/turn-rewind.json +11 -6
  511. package/packages/api-gateway/dist/marketplace-entries/official/undo-savepoint.json +1 -1
  512. package/packages/api-gateway/dist/marketplace-entries/official/vision-toolkit.json +8 -5
  513. package/packages/api-gateway/dist/marketplace-entries/official/web-research.json +1 -1
  514. package/packages/api-gateway/dist/marketplace-entries/official/workspace-navigator.json +1 -1
  515. package/packages/api-gateway/dist/marketplace-entries/official/workspace-search.json +1 -1
  516. package/packages/api-gateway/dist/marketplace-entries/official/yaml-validator.json +8 -5
  517. package/packages/api-gateway/package.json +1 -1
  518. package/packages/api-gateway/src/index.ts +90 -9
  519. package/packages/api-gateway/src/marketplace-entries/official/agent-teams.json +6 -6
  520. package/packages/api-gateway/src/marketplace-entries/official/anchored-standard.json +1 -1
  521. package/packages/api-gateway/src/marketplace-entries/official/annotation.json +1 -2
  522. package/packages/api-gateway/src/marketplace-entries/official/archify.json +1 -1
  523. package/packages/api-gateway/src/marketplace-entries/official/at-file.json +11 -5
  524. package/packages/api-gateway/src/marketplace-entries/official/auto-mode.json +1 -1
  525. package/packages/api-gateway/src/marketplace-entries/official/better-sidebar.json +1 -1
  526. package/packages/api-gateway/src/marketplace-entries/official/browser-fetch.json +12 -5
  527. package/packages/api-gateway/src/marketplace-entries/official/browser-session.json +14 -8
  528. package/packages/api-gateway/src/marketplace-entries/official/canvas-draw.json +3 -4
  529. package/packages/api-gateway/src/marketplace-entries/official/change-verifier.json +1 -1
  530. package/packages/api-gateway/src/marketplace-entries/official/cleaner.json +11 -6
  531. package/packages/api-gateway/src/marketplace-entries/official/cli-notifier.json +5 -5
  532. package/packages/api-gateway/src/marketplace-entries/official/code2skill.json +5 -5
  533. package/packages/api-gateway/src/marketplace-entries/official/colleague-skill.json +1 -2
  534. package/packages/api-gateway/src/marketplace-entries/official/context-doctor.json +12 -7
  535. package/packages/api-gateway/src/marketplace-entries/official/context.json +10 -5
  536. package/packages/api-gateway/src/marketplace-entries/official/cordis-group.json +2 -2
  537. package/packages/api-gateway/src/marketplace-entries/official/cordis-logger-console.json +4 -4
  538. package/packages/api-gateway/src/marketplace-entries/official/cordis-timer.json +3 -3
  539. package/packages/api-gateway/src/marketplace-entries/official/cost-meter.json +9 -6
  540. package/packages/api-gateway/src/marketplace-entries/official/dependency-checker.json +13 -10
  541. package/packages/api-gateway/src/marketplace-entries/official/docker-sandbox.json +10 -5
  542. package/packages/api-gateway/src/marketplace-entries/official/fail-logger.json +11 -6
  543. package/packages/api-gateway/src/marketplace-entries/official/genui.json +7 -5
  544. package/packages/api-gateway/src/marketplace-entries/official/git-time-capsule.json +13 -8
  545. package/packages/api-gateway/src/marketplace-entries/official/graph-memory.json +13 -7
  546. package/packages/api-gateway/src/marketplace-entries/official/history-compressor.json +1 -1
  547. package/packages/api-gateway/src/marketplace-entries/official/hol-guard.json +1 -1
  548. package/packages/api-gateway/src/marketplace-entries/official/i18n-pair.json +9 -6
  549. package/packages/api-gateway/src/marketplace-entries/official/image-compressor.json +1 -1
  550. package/packages/api-gateway/src/marketplace-entries/official/llm-verifier.json +1 -1
  551. package/packages/api-gateway/src/marketplace-entries/official/mcp-client.json +12 -7
  552. package/packages/api-gateway/src/marketplace-entries/official/mcp-panel.json +1 -1
  553. package/packages/api-gateway/src/marketplace-entries/official/memory.json +1 -1
  554. package/packages/api-gateway/src/marketplace-entries/official/mirage-bridge.json +1 -1
  555. package/packages/api-gateway/src/marketplace-entries/official/mock-server.json +1 -1
  556. package/packages/api-gateway/src/marketplace-entries/official/modlens.json +4 -4
  557. package/packages/api-gateway/src/marketplace-entries/official/module-search.json +1 -1
  558. package/packages/api-gateway/src/marketplace-entries/official/obsidian-sync.json +1 -1
  559. package/packages/api-gateway/src/marketplace-entries/official/openpets.json +9 -5
  560. package/packages/api-gateway/src/marketplace-entries/official/plan-execute.json +1 -1
  561. package/packages/api-gateway/src/marketplace-entries/official/plugin-check.json +1 -1
  562. package/packages/api-gateway/src/marketplace-entries/official/plugin-dev.json +8 -6
  563. package/packages/api-gateway/src/marketplace-entries/official/plugin-finder.json +1 -1
  564. package/packages/api-gateway/src/marketplace-entries/official/plugin-radar.json +1 -1
  565. package/packages/api-gateway/src/marketplace-entries/official/plugin-stars.json +15 -7
  566. package/packages/api-gateway/src/marketplace-entries/official/prompt-guard.json +1 -1
  567. package/packages/api-gateway/src/marketplace-entries/official/prompt-library.json +1 -1
  568. package/packages/api-gateway/src/marketplace-entries/official/readme-gen.json +12 -8
  569. package/packages/api-gateway/src/marketplace-entries/official/recall-unread.json +11 -6
  570. package/packages/api-gateway/src/marketplace-entries/official/reverse-skill.json +1 -1
  571. package/packages/api-gateway/src/marketplace-entries/official/reviewer-bot.json +1 -1
  572. package/packages/api-gateway/src/marketplace-entries/official/runtime-doctor.json +1 -1
  573. package/packages/api-gateway/src/marketplace-entries/official/secure-audit.json +1 -1
  574. package/packages/api-gateway/src/marketplace-entries/official/session-bookmarks.json +1 -1
  575. package/packages/api-gateway/src/marketplace-entries/official/session-bridge.json +13 -8
  576. package/packages/api-gateway/src/marketplace-entries/official/session-compare.json +1 -1
  577. package/packages/api-gateway/src/marketplace-entries/official/session-export.json +1 -1
  578. package/packages/api-gateway/src/marketplace-entries/official/session-insights.json +12 -6
  579. package/packages/api-gateway/src/marketplace-entries/official/session-search.json +1 -1
  580. package/packages/api-gateway/src/marketplace-entries/official/skill-catalog.json +1 -1
  581. package/packages/api-gateway/src/marketplace-entries/official/skill-guard.json +9 -5
  582. package/packages/api-gateway/src/marketplace-entries/official/sql-lens.json +13 -7
  583. package/packages/api-gateway/src/marketplace-entries/official/synapse.json +1 -1
  584. package/packages/api-gateway/src/marketplace-entries/official/tab-manager.json +1 -1
  585. package/packages/api-gateway/src/marketplace-entries/official/taskboard.json +1 -1
  586. package/packages/api-gateway/src/marketplace-entries/official/telemetry-blocker.json +1 -1
  587. package/packages/api-gateway/src/marketplace-entries/official/test-harness.json +14 -7
  588. package/packages/api-gateway/src/marketplace-entries/official/theme-studio.json +1 -1
  589. package/packages/api-gateway/src/marketplace-entries/official/token-guard.json +12 -6
  590. package/packages/api-gateway/src/marketplace-entries/official/turn-rewind.json +11 -6
  591. package/packages/api-gateway/src/marketplace-entries/official/undo-savepoint.json +1 -1
  592. package/packages/api-gateway/src/marketplace-entries/official/vision-toolkit.json +8 -5
  593. package/packages/api-gateway/src/marketplace-entries/official/web-research.json +1 -1
  594. package/packages/api-gateway/src/marketplace-entries/official/workspace-navigator.json +1 -1
  595. package/packages/api-gateway/src/marketplace-entries/official/workspace-search.json +1 -1
  596. package/packages/api-gateway/src/marketplace-entries/official/yaml-validator.json +8 -5
  597. package/packages/api-gateway/test/index.test.ts +1594 -2
  598. package/packages/api-gateway/test/marketplace.test.ts +389 -2
  599. package/packages/api-gateway/test/release-package.test.ts +1 -0
  600. package/packages/bundle-web-app/package.json +1 -1
  601. package/packages/cli/dist/node-stdio.d.ts.map +1 -1
  602. package/packages/cli/dist/node-stdio.js +83 -20
  603. package/packages/cli/dist/node-stdio.js.map +1 -1
  604. package/packages/cli/package.json +1 -1
  605. package/packages/cli/src/node-stdio.ts +77 -20
  606. package/packages/cli/test/node-stdio.test.ts +118 -2
  607. package/packages/client-web/dist/agent-teams-view.d.ts +70 -0
  608. package/packages/client-web/dist/agent-teams-view.d.ts.map +1 -0
  609. package/packages/client-web/dist/agent-teams-view.js +159 -0
  610. package/packages/client-web/dist/agent-teams-view.js.map +1 -0
  611. package/packages/client-web/dist/at-file-view.d.ts +10 -0
  612. package/packages/client-web/dist/at-file-view.d.ts.map +1 -0
  613. package/packages/client-web/dist/at-file-view.js +65 -0
  614. package/packages/client-web/dist/at-file-view.js.map +1 -0
  615. package/packages/client-web/dist/browser-fetch-view.d.ts +22 -0
  616. package/packages/client-web/dist/browser-fetch-view.d.ts.map +1 -0
  617. package/packages/client-web/dist/browser-fetch-view.js +101 -0
  618. package/packages/client-web/dist/browser-fetch-view.js.map +1 -0
  619. package/packages/client-web/dist/browser-session-view.d.ts +32 -0
  620. package/packages/client-web/dist/browser-session-view.d.ts.map +1 -1
  621. package/packages/client-web/dist/browser-session-view.js +206 -3
  622. package/packages/client-web/dist/browser-session-view.js.map +1 -1
  623. package/packages/client-web/dist/cleaner-view.d.ts +29 -0
  624. package/packages/client-web/dist/cleaner-view.d.ts.map +1 -0
  625. package/packages/client-web/dist/cleaner-view.js +191 -0
  626. package/packages/client-web/dist/cleaner-view.js.map +1 -0
  627. package/packages/client-web/dist/context-doctor-view.d.ts +35 -0
  628. package/packages/client-web/dist/context-doctor-view.d.ts.map +1 -0
  629. package/packages/client-web/dist/context-doctor-view.js +129 -0
  630. package/packages/client-web/dist/context-doctor-view.js.map +1 -0
  631. package/packages/client-web/dist/context-insights-view.d.ts +31 -0
  632. package/packages/client-web/dist/context-insights-view.d.ts.map +1 -0
  633. package/packages/client-web/dist/context-insights-view.js +90 -0
  634. package/packages/client-web/dist/context-insights-view.js.map +1 -0
  635. package/packages/client-web/dist/cost-meter-view.d.ts +16 -0
  636. package/packages/client-web/dist/cost-meter-view.d.ts.map +1 -0
  637. package/packages/client-web/dist/cost-meter-view.js +43 -0
  638. package/packages/client-web/dist/cost-meter-view.js.map +1 -0
  639. package/packages/client-web/dist/dependency-checker-view.d.ts +24 -0
  640. package/packages/client-web/dist/dependency-checker-view.d.ts.map +1 -0
  641. package/packages/client-web/dist/dependency-checker-view.js +195 -0
  642. package/packages/client-web/dist/dependency-checker-view.js.map +1 -0
  643. package/packages/client-web/dist/docker-sandbox-view.d.ts +28 -0
  644. package/packages/client-web/dist/docker-sandbox-view.d.ts.map +1 -0
  645. package/packages/client-web/dist/docker-sandbox-view.js +152 -0
  646. package/packages/client-web/dist/docker-sandbox-view.js.map +1 -0
  647. package/packages/client-web/dist/fail-logger-view.d.ts +16 -0
  648. package/packages/client-web/dist/fail-logger-view.d.ts.map +1 -0
  649. package/packages/client-web/dist/fail-logger-view.js +97 -0
  650. package/packages/client-web/dist/fail-logger-view.js.map +1 -0
  651. package/packages/client-web/dist/genui-view.d.ts +28 -0
  652. package/packages/client-web/dist/genui-view.d.ts.map +1 -0
  653. package/packages/client-web/dist/genui-view.js +62 -0
  654. package/packages/client-web/dist/genui-view.js.map +1 -0
  655. package/packages/client-web/dist/git-time-capsule-view.d.ts +31 -0
  656. package/packages/client-web/dist/git-time-capsule-view.d.ts.map +1 -0
  657. package/packages/client-web/dist/git-time-capsule-view.js +203 -0
  658. package/packages/client-web/dist/git-time-capsule-view.js.map +1 -0
  659. package/packages/client-web/dist/graph-memory-view.d.ts +39 -0
  660. package/packages/client-web/dist/graph-memory-view.d.ts.map +1 -0
  661. package/packages/client-web/dist/graph-memory-view.js +82 -0
  662. package/packages/client-web/dist/graph-memory-view.js.map +1 -0
  663. package/packages/client-web/dist/i18n-pair-view.d.ts +32 -0
  664. package/packages/client-web/dist/i18n-pair-view.d.ts.map +1 -0
  665. package/packages/client-web/dist/i18n-pair-view.js +182 -0
  666. package/packages/client-web/dist/i18n-pair-view.js.map +1 -0
  667. package/packages/client-web/dist/mcp-client-view.d.ts +58 -0
  668. package/packages/client-web/dist/mcp-client-view.d.ts.map +1 -0
  669. package/packages/client-web/dist/mcp-client-view.js +121 -0
  670. package/packages/client-web/dist/mcp-client-view.js.map +1 -0
  671. package/packages/client-web/dist/modlens-view.d.ts +43 -0
  672. package/packages/client-web/dist/modlens-view.d.ts.map +1 -0
  673. package/packages/client-web/dist/modlens-view.js +176 -0
  674. package/packages/client-web/dist/modlens-view.js.map +1 -0
  675. package/packages/client-web/dist/openpets-view.d.ts +26 -0
  676. package/packages/client-web/dist/openpets-view.d.ts.map +1 -0
  677. package/packages/client-web/dist/openpets-view.js +62 -0
  678. package/packages/client-web/dist/openpets-view.js.map +1 -0
  679. package/packages/client-web/dist/panel-safe.d.ts +5 -0
  680. package/packages/client-web/dist/panel-safe.d.ts.map +1 -0
  681. package/packages/client-web/dist/panel-safe.js +58 -0
  682. package/packages/client-web/dist/panel-safe.js.map +1 -0
  683. package/packages/client-web/dist/plugin-dev-view.d.ts +15 -0
  684. package/packages/client-web/dist/plugin-dev-view.d.ts.map +1 -0
  685. package/packages/client-web/dist/plugin-dev-view.js +33 -0
  686. package/packages/client-web/dist/plugin-dev-view.js.map +1 -0
  687. package/packages/client-web/dist/plugin-stars-view.d.ts +28 -0
  688. package/packages/client-web/dist/plugin-stars-view.d.ts.map +1 -1
  689. package/packages/client-web/dist/plugin-stars-view.js +209 -1
  690. package/packages/client-web/dist/plugin-stars-view.js.map +1 -1
  691. package/packages/client-web/dist/react-room.d.ts +5 -1
  692. package/packages/client-web/dist/react-room.d.ts.map +1 -1
  693. package/packages/client-web/dist/react-room.js +463 -219
  694. package/packages/client-web/dist/react-room.js.map +1 -1
  695. package/packages/client-web/dist/readme-gen-view.d.ts +30 -0
  696. package/packages/client-web/dist/readme-gen-view.d.ts.map +1 -0
  697. package/packages/client-web/dist/readme-gen-view.js +134 -0
  698. package/packages/client-web/dist/readme-gen-view.js.map +1 -0
  699. package/packages/client-web/dist/recall-unread-view.d.ts +46 -0
  700. package/packages/client-web/dist/recall-unread-view.d.ts.map +1 -0
  701. package/packages/client-web/dist/recall-unread-view.js +104 -0
  702. package/packages/client-web/dist/recall-unread-view.js.map +1 -0
  703. package/packages/client-web/dist/session-bridge-view.d.ts +50 -0
  704. package/packages/client-web/dist/session-bridge-view.d.ts.map +1 -0
  705. package/packages/client-web/dist/session-bridge-view.js +113 -0
  706. package/packages/client-web/dist/session-bridge-view.js.map +1 -0
  707. package/packages/client-web/dist/session-insights-view.d.ts +40 -0
  708. package/packages/client-web/dist/session-insights-view.d.ts.map +1 -0
  709. package/packages/client-web/dist/session-insights-view.js +157 -0
  710. package/packages/client-web/dist/session-insights-view.js.map +1 -0
  711. package/packages/client-web/dist/skill-guard-view.d.ts +47 -0
  712. package/packages/client-web/dist/skill-guard-view.d.ts.map +1 -0
  713. package/packages/client-web/dist/skill-guard-view.js +93 -0
  714. package/packages/client-web/dist/skill-guard-view.js.map +1 -0
  715. package/packages/client-web/dist/sql-lens-view.d.ts +37 -0
  716. package/packages/client-web/dist/sql-lens-view.d.ts.map +1 -0
  717. package/packages/client-web/dist/sql-lens-view.js +235 -0
  718. package/packages/client-web/dist/sql-lens-view.js.map +1 -0
  719. package/packages/client-web/dist/test-harness-view.d.ts +24 -0
  720. package/packages/client-web/dist/test-harness-view.d.ts.map +1 -0
  721. package/packages/client-web/dist/test-harness-view.js +149 -0
  722. package/packages/client-web/dist/test-harness-view.js.map +1 -0
  723. package/packages/client-web/dist/token-guard-view.d.ts +19 -0
  724. package/packages/client-web/dist/token-guard-view.d.ts.map +1 -0
  725. package/packages/client-web/dist/token-guard-view.js +56 -0
  726. package/packages/client-web/dist/token-guard-view.js.map +1 -0
  727. package/packages/client-web/dist/turn-rewind-view.d.ts +41 -0
  728. package/packages/client-web/dist/turn-rewind-view.d.ts.map +1 -0
  729. package/packages/client-web/dist/turn-rewind-view.js +117 -0
  730. package/packages/client-web/dist/turn-rewind-view.js.map +1 -0
  731. package/packages/client-web/dist/vision-toolkit-view.d.ts +66 -0
  732. package/packages/client-web/dist/vision-toolkit-view.d.ts.map +1 -0
  733. package/packages/client-web/dist/vision-toolkit-view.js +238 -0
  734. package/packages/client-web/dist/vision-toolkit-view.js.map +1 -0
  735. package/packages/client-web/dist/yaml-validator-view.d.ts +52 -0
  736. package/packages/client-web/dist/yaml-validator-view.d.ts.map +1 -0
  737. package/packages/client-web/dist/yaml-validator-view.js +96 -0
  738. package/packages/client-web/dist/yaml-validator-view.js.map +1 -0
  739. package/packages/client-web/package.json +1 -1
  740. package/packages/client-web/src/agent-teams-view.ts +189 -0
  741. package/packages/client-web/src/at-file-view.ts +69 -0
  742. package/packages/client-web/src/browser-fetch-view.ts +114 -0
  743. package/packages/client-web/src/browser-session-view.ts +222 -3
  744. package/packages/client-web/src/cleaner-view.ts +207 -0
  745. package/packages/client-web/src/context-doctor-view.ts +140 -0
  746. package/packages/client-web/src/context-insights-view.ts +125 -0
  747. package/packages/client-web/src/cost-meter-view.ts +63 -0
  748. package/packages/client-web/src/dependency-checker-view.ts +215 -0
  749. package/packages/client-web/src/docker-sandbox-view.ts +177 -0
  750. package/packages/client-web/src/fail-logger-view.ts +112 -0
  751. package/packages/client-web/src/genui-view.ts +88 -0
  752. package/packages/client-web/src/git-time-capsule-view.ts +215 -0
  753. package/packages/client-web/src/graph-memory-view.ts +91 -0
  754. package/packages/client-web/src/i18n-pair-view.ts +194 -0
  755. package/packages/client-web/src/mcp-client-view.ts +122 -0
  756. package/packages/client-web/src/modlens-view.ts +210 -0
  757. package/packages/client-web/src/openpets-view.ts +67 -0
  758. package/packages/client-web/src/panel-safe.ts +47 -0
  759. package/packages/client-web/src/plugin-dev-view.ts +39 -0
  760. package/packages/client-web/src/plugin-stars-view.ts +239 -1
  761. package/packages/client-web/src/react-room.tsx +1770 -900
  762. package/packages/client-web/src/readme-gen-view.ts +145 -0
  763. package/packages/client-web/src/recall-unread-view.ts +105 -0
  764. package/packages/client-web/src/session-bridge-view.ts +118 -0
  765. package/packages/client-web/src/session-insights-view.ts +205 -0
  766. package/packages/client-web/src/skill-guard-view.ts +94 -0
  767. package/packages/client-web/src/sql-lens-view.ts +247 -0
  768. package/packages/client-web/src/test-harness-view.ts +170 -0
  769. package/packages/client-web/src/token-guard-view.ts +75 -0
  770. package/packages/client-web/src/turn-rewind-view.ts +120 -0
  771. package/packages/client-web/src/vision-toolkit-view.ts +280 -0
  772. package/packages/client-web/src/yaml-validator-view.ts +102 -0
  773. package/packages/client-web/test/agent-teams-view.test.ts +198 -0
  774. package/packages/client-web/test/at-file-panel.test.ts +76 -0
  775. package/packages/client-web/test/browser-fetch-panel.test.ts +44 -0
  776. package/packages/client-web/test/browser-fetch-view.test.ts +89 -0
  777. package/packages/client-web/test/browser-session-panel.test.ts +55 -0
  778. package/packages/client-web/test/browser-session-view.test.ts +99 -1
  779. package/packages/client-web/test/cleaner-panel.test.ts +66 -0
  780. package/packages/client-web/test/cleaner-view.test.ts +143 -0
  781. package/packages/client-web/test/context-doctor-view.test.ts +169 -0
  782. package/packages/client-web/test/context-insights-view.test.ts +120 -0
  783. package/packages/client-web/test/cost-meter-view.test.ts +53 -0
  784. package/packages/client-web/test/dependency-checker-panel.test.ts +51 -0
  785. package/packages/client-web/test/dependency-checker-view.test.ts +266 -0
  786. package/packages/client-web/test/docker-sandbox-panel.test.ts +44 -0
  787. package/packages/client-web/test/docker-sandbox-view.test.ts +127 -0
  788. package/packages/client-web/test/fail-logger-view.test.ts +155 -0
  789. package/packages/client-web/test/genui-view.test.ts +55 -0
  790. package/packages/client-web/test/git-time-capsule-view.test.ts +252 -0
  791. package/packages/client-web/test/graph-memory-view.test.ts +67 -0
  792. package/packages/client-web/test/i18n-pair-panel.test.ts +72 -0
  793. package/packages/client-web/test/i18n-pair-view.test.ts +164 -0
  794. package/packages/client-web/test/mcp-client-view.test.ts +106 -0
  795. package/packages/client-web/test/modlens-view.test.ts +198 -0
  796. package/packages/client-web/test/openpets-view.test.ts +57 -0
  797. package/packages/client-web/test/plugin-dev-view.test.ts +63 -0
  798. package/packages/client-web/test/plugin-stars-panel.test.ts +44 -0
  799. package/packages/client-web/test/plugin-stars-view.test.ts +88 -1
  800. package/packages/client-web/test/readme-gen-panel.test.ts +81 -0
  801. package/packages/client-web/test/readme-gen-view.test.ts +91 -0
  802. package/packages/client-web/test/recall-unread-view.test.ts +153 -0
  803. package/packages/client-web/test/session-bridge-view.test.ts +102 -0
  804. package/packages/client-web/test/session-insights-panel.test.ts +85 -0
  805. package/packages/client-web/test/session-insights-view.test.ts +152 -0
  806. package/packages/client-web/test/skill-guard-view.test.ts +138 -0
  807. package/packages/client-web/test/sql-lens-panel.test.ts +52 -0
  808. package/packages/client-web/test/sql-lens-view.test.ts +134 -0
  809. package/packages/client-web/test/test-harness-panel.test.ts +228 -0
  810. package/packages/client-web/test/token-guard-view.test.ts +93 -0
  811. package/packages/client-web/test/turn-rewind-view.test.ts +159 -0
  812. package/packages/client-web/test/vision-toolkit-view.test.ts +152 -0
  813. package/packages/client-web/test/yaml-validator-view.test.ts +100 -0
  814. package/packages/core/dist/atomic-write.d.ts +8 -0
  815. package/packages/core/dist/atomic-write.d.ts.map +1 -0
  816. package/packages/core/dist/atomic-write.js +40 -0
  817. package/packages/core/dist/atomic-write.js.map +1 -0
  818. package/packages/core/dist/boot.d.ts.map +1 -1
  819. package/packages/core/dist/boot.js +132 -10
  820. package/packages/core/dist/boot.js.map +1 -1
  821. package/packages/core/dist/bounded-file.d.ts +7 -0
  822. package/packages/core/dist/bounded-file.d.ts.map +1 -0
  823. package/packages/core/dist/bounded-file.js +50 -0
  824. package/packages/core/dist/bounded-file.js.map +1 -0
  825. package/packages/core/dist/config.d.ts +2 -0
  826. package/packages/core/dist/config.d.ts.map +1 -1
  827. package/packages/core/dist/config.js +21 -0
  828. package/packages/core/dist/config.js.map +1 -1
  829. package/packages/core/dist/cordis-timer.d.ts +15 -0
  830. package/packages/core/dist/cordis-timer.d.ts.map +1 -0
  831. package/packages/core/dist/cordis-timer.js +99 -0
  832. package/packages/core/dist/cordis-timer.js.map +1 -0
  833. package/packages/core/dist/plugins/agent-teams.d.ts +5 -1
  834. package/packages/core/dist/plugins/agent-teams.d.ts.map +1 -1
  835. package/packages/core/dist/plugins/agent-teams.js +986 -105
  836. package/packages/core/dist/plugins/agent-teams.js.map +1 -1
  837. package/packages/core/dist/plugins/anchored-standard.d.ts +1 -0
  838. package/packages/core/dist/plugins/anchored-standard.d.ts.map +1 -1
  839. package/packages/core/dist/plugins/anchored-standard.js +39 -11
  840. package/packages/core/dist/plugins/anchored-standard.js.map +1 -1
  841. package/packages/core/dist/plugins/annotation.d.ts +1 -0
  842. package/packages/core/dist/plugins/annotation.d.ts.map +1 -1
  843. package/packages/core/dist/plugins/annotation.js +35 -17
  844. package/packages/core/dist/plugins/annotation.js.map +1 -1
  845. package/packages/core/dist/plugins/archify.d.ts +1 -0
  846. package/packages/core/dist/plugins/archify.d.ts.map +1 -1
  847. package/packages/core/dist/plugins/archify.js +69 -22
  848. package/packages/core/dist/plugins/archify.js.map +1 -1
  849. package/packages/core/dist/plugins/at-file.d.ts +5 -1
  850. package/packages/core/dist/plugins/at-file.d.ts.map +1 -1
  851. package/packages/core/dist/plugins/at-file.js +120 -29
  852. package/packages/core/dist/plugins/at-file.js.map +1 -1
  853. package/packages/core/dist/plugins/auto-mode.d.ts.map +1 -1
  854. package/packages/core/dist/plugins/auto-mode.js +89 -20
  855. package/packages/core/dist/plugins/auto-mode.js.map +1 -1
  856. package/packages/core/dist/plugins/better-sidebar.d.ts +1 -0
  857. package/packages/core/dist/plugins/better-sidebar.d.ts.map +1 -1
  858. package/packages/core/dist/plugins/better-sidebar.js +14 -4
  859. package/packages/core/dist/plugins/better-sidebar.js.map +1 -1
  860. package/packages/core/dist/plugins/browser-fetch.d.ts +1 -0
  861. package/packages/core/dist/plugins/browser-fetch.d.ts.map +1 -1
  862. package/packages/core/dist/plugins/browser-fetch.js +288 -96
  863. package/packages/core/dist/plugins/browser-fetch.js.map +1 -1
  864. package/packages/core/dist/plugins/browser-session.d.ts.map +1 -1
  865. package/packages/core/dist/plugins/browser-session.js +445 -106
  866. package/packages/core/dist/plugins/browser-session.js.map +1 -1
  867. package/packages/core/dist/plugins/canvas-draw.d.ts +1 -0
  868. package/packages/core/dist/plugins/canvas-draw.d.ts.map +1 -1
  869. package/packages/core/dist/plugins/canvas-draw.js +71 -19
  870. package/packages/core/dist/plugins/canvas-draw.js.map +1 -1
  871. package/packages/core/dist/plugins/change-verifier.d.ts +1 -0
  872. package/packages/core/dist/plugins/change-verifier.d.ts.map +1 -1
  873. package/packages/core/dist/plugins/change-verifier.js +30 -9
  874. package/packages/core/dist/plugins/change-verifier.js.map +1 -1
  875. package/packages/core/dist/plugins/cleaner.d.ts +5 -1
  876. package/packages/core/dist/plugins/cleaner.d.ts.map +1 -1
  877. package/packages/core/dist/plugins/cleaner.js +337 -40
  878. package/packages/core/dist/plugins/cleaner.js.map +1 -1
  879. package/packages/core/dist/plugins/cli-notifier.d.ts +2 -1
  880. package/packages/core/dist/plugins/cli-notifier.d.ts.map +1 -1
  881. package/packages/core/dist/plugins/cli-notifier.js +99 -42
  882. package/packages/core/dist/plugins/cli-notifier.js.map +1 -1
  883. package/packages/core/dist/plugins/code2skill.d.ts +1 -0
  884. package/packages/core/dist/plugins/code2skill.d.ts.map +1 -1
  885. package/packages/core/dist/plugins/code2skill.js +178 -38
  886. package/packages/core/dist/plugins/code2skill.js.map +1 -1
  887. package/packages/core/dist/plugins/colleague-skill.d.ts +2 -1
  888. package/packages/core/dist/plugins/colleague-skill.d.ts.map +1 -1
  889. package/packages/core/dist/plugins/colleague-skill.js +79 -49
  890. package/packages/core/dist/plugins/colleague-skill.js.map +1 -1
  891. package/packages/core/dist/plugins/context-doctor.d.ts +6 -1
  892. package/packages/core/dist/plugins/context-doctor.d.ts.map +1 -1
  893. package/packages/core/dist/plugins/context-doctor.js +403 -39
  894. package/packages/core/dist/plugins/context-doctor.js.map +1 -1
  895. package/packages/core/dist/plugins/context.d.ts +1 -0
  896. package/packages/core/dist/plugins/context.d.ts.map +1 -1
  897. package/packages/core/dist/plugins/context.js +169 -42
  898. package/packages/core/dist/plugins/context.js.map +1 -1
  899. package/packages/core/dist/plugins/cost-meter.d.ts +4 -0
  900. package/packages/core/dist/plugins/cost-meter.d.ts.map +1 -1
  901. package/packages/core/dist/plugins/cost-meter.js +319 -93
  902. package/packages/core/dist/plugins/cost-meter.js.map +1 -1
  903. package/packages/core/dist/plugins/dependency-checker.d.ts +11 -2
  904. package/packages/core/dist/plugins/dependency-checker.d.ts.map +1 -1
  905. package/packages/core/dist/plugins/dependency-checker.js +463 -114
  906. package/packages/core/dist/plugins/dependency-checker.js.map +1 -1
  907. package/packages/core/dist/plugins/docker-sandbox.d.ts +1 -0
  908. package/packages/core/dist/plugins/docker-sandbox.d.ts.map +1 -1
  909. package/packages/core/dist/plugins/docker-sandbox.js +322 -80
  910. package/packages/core/dist/plugins/docker-sandbox.js.map +1 -1
  911. package/packages/core/dist/plugins/fail-logger.d.ts +1 -0
  912. package/packages/core/dist/plugins/fail-logger.d.ts.map +1 -1
  913. package/packages/core/dist/plugins/fail-logger.js +187 -20
  914. package/packages/core/dist/plugins/fail-logger.js.map +1 -1
  915. package/packages/core/dist/plugins/genui.d.ts +1 -0
  916. package/packages/core/dist/plugins/genui.d.ts.map +1 -1
  917. package/packages/core/dist/plugins/genui.js +103 -38
  918. package/packages/core/dist/plugins/genui.js.map +1 -1
  919. package/packages/core/dist/plugins/git-time-capsule.d.ts +8 -2
  920. package/packages/core/dist/plugins/git-time-capsule.d.ts.map +1 -1
  921. package/packages/core/dist/plugins/git-time-capsule.js +400 -54
  922. package/packages/core/dist/plugins/git-time-capsule.js.map +1 -1
  923. package/packages/core/dist/plugins/graph-memory.d.ts.map +1 -1
  924. package/packages/core/dist/plugins/graph-memory.js +329 -42
  925. package/packages/core/dist/plugins/graph-memory.js.map +1 -1
  926. package/packages/core/dist/plugins/history-compressor.d.ts.map +1 -1
  927. package/packages/core/dist/plugins/history-compressor.js +31 -12
  928. package/packages/core/dist/plugins/history-compressor.js.map +1 -1
  929. package/packages/core/dist/plugins/hol-guard.d.ts.map +1 -1
  930. package/packages/core/dist/plugins/hol-guard.js +33 -13
  931. package/packages/core/dist/plugins/hol-guard.js.map +1 -1
  932. package/packages/core/dist/plugins/i18n-pair.d.ts +5 -1
  933. package/packages/core/dist/plugins/i18n-pair.d.ts.map +1 -1
  934. package/packages/core/dist/plugins/i18n-pair.js +308 -52
  935. package/packages/core/dist/plugins/i18n-pair.js.map +1 -1
  936. package/packages/core/dist/plugins/image-compressor.d.ts +7 -0
  937. package/packages/core/dist/plugins/image-compressor.d.ts.map +1 -1
  938. package/packages/core/dist/plugins/image-compressor.js +98 -25
  939. package/packages/core/dist/plugins/image-compressor.js.map +1 -1
  940. package/packages/core/dist/plugins/llm-verifier.d.ts.map +1 -1
  941. package/packages/core/dist/plugins/llm-verifier.js +21 -11
  942. package/packages/core/dist/plugins/llm-verifier.js.map +1 -1
  943. package/packages/core/dist/plugins/mcp-client.d.ts.map +1 -1
  944. package/packages/core/dist/plugins/mcp-client.js +986 -232
  945. package/packages/core/dist/plugins/mcp-client.js.map +1 -1
  946. package/packages/core/dist/plugins/mcp-panel.d.ts.map +1 -1
  947. package/packages/core/dist/plugins/mcp-panel.js +30 -16
  948. package/packages/core/dist/plugins/mcp-panel.js.map +1 -1
  949. package/packages/core/dist/plugins/memory.d.ts.map +1 -1
  950. package/packages/core/dist/plugins/memory.js +292 -59
  951. package/packages/core/dist/plugins/memory.js.map +1 -1
  952. package/packages/core/dist/plugins/mirage-bridge.d.ts.map +1 -1
  953. package/packages/core/dist/plugins/mirage-bridge.js +20 -10
  954. package/packages/core/dist/plugins/mirage-bridge.js.map +1 -1
  955. package/packages/core/dist/plugins/mock-server.d.ts.map +1 -1
  956. package/packages/core/dist/plugins/mock-server.js +85 -54
  957. package/packages/core/dist/plugins/mock-server.js.map +1 -1
  958. package/packages/core/dist/plugins/model.d.ts +3 -0
  959. package/packages/core/dist/plugins/model.d.ts.map +1 -1
  960. package/packages/core/dist/plugins/model.js +3 -0
  961. package/packages/core/dist/plugins/model.js.map +1 -1
  962. package/packages/core/dist/plugins/models.d.ts.map +1 -1
  963. package/packages/core/dist/plugins/models.js +10 -2
  964. package/packages/core/dist/plugins/models.js.map +1 -1
  965. package/packages/core/dist/plugins/modlens.d.ts +8 -1
  966. package/packages/core/dist/plugins/modlens.d.ts.map +1 -1
  967. package/packages/core/dist/plugins/modlens.js +438 -31
  968. package/packages/core/dist/plugins/modlens.js.map +1 -1
  969. package/packages/core/dist/plugins/module-search.d.ts +1 -0
  970. package/packages/core/dist/plugins/module-search.d.ts.map +1 -1
  971. package/packages/core/dist/plugins/module-search.js +35 -16
  972. package/packages/core/dist/plugins/module-search.js.map +1 -1
  973. package/packages/core/dist/plugins/obsidian-sync.d.ts.map +1 -1
  974. package/packages/core/dist/plugins/obsidian-sync.js +20 -11
  975. package/packages/core/dist/plugins/obsidian-sync.js.map +1 -1
  976. package/packages/core/dist/plugins/openpets.d.ts.map +1 -1
  977. package/packages/core/dist/plugins/openpets.js +203 -44
  978. package/packages/core/dist/plugins/openpets.js.map +1 -1
  979. package/packages/core/dist/plugins/plan-execute.d.ts +4 -1
  980. package/packages/core/dist/plugins/plan-execute.d.ts.map +1 -1
  981. package/packages/core/dist/plugins/plan-execute.js +42 -21
  982. package/packages/core/dist/plugins/plan-execute.js.map +1 -1
  983. package/packages/core/dist/plugins/plugin-check.d.ts +12 -3
  984. package/packages/core/dist/plugins/plugin-check.d.ts.map +1 -1
  985. package/packages/core/dist/plugins/plugin-check.js +83 -12
  986. package/packages/core/dist/plugins/plugin-check.js.map +1 -1
  987. package/packages/core/dist/plugins/plugin-dev.d.ts +1 -0
  988. package/packages/core/dist/plugins/plugin-dev.d.ts.map +1 -1
  989. package/packages/core/dist/plugins/plugin-dev.js +156 -23
  990. package/packages/core/dist/plugins/plugin-dev.js.map +1 -1
  991. package/packages/core/dist/plugins/plugin-finder.d.ts +1 -0
  992. package/packages/core/dist/plugins/plugin-finder.d.ts.map +1 -1
  993. package/packages/core/dist/plugins/plugin-finder.js +131 -13
  994. package/packages/core/dist/plugins/plugin-finder.js.map +1 -1
  995. package/packages/core/dist/plugins/plugin-radar.d.ts +4 -0
  996. package/packages/core/dist/plugins/plugin-radar.d.ts.map +1 -1
  997. package/packages/core/dist/plugins/plugin-radar.js +137 -36
  998. package/packages/core/dist/plugins/plugin-radar.js.map +1 -1
  999. package/packages/core/dist/plugins/plugin-stars.d.ts.map +1 -1
  1000. package/packages/core/dist/plugins/plugin-stars.js +282 -81
  1001. package/packages/core/dist/plugins/plugin-stars.js.map +1 -1
  1002. package/packages/core/dist/plugins/prompt-guard.d.ts +1 -0
  1003. package/packages/core/dist/plugins/prompt-guard.d.ts.map +1 -1
  1004. package/packages/core/dist/plugins/prompt-guard.js +60 -9
  1005. package/packages/core/dist/plugins/prompt-guard.js.map +1 -1
  1006. package/packages/core/dist/plugins/prompt-library.d.ts +1 -0
  1007. package/packages/core/dist/plugins/prompt-library.d.ts.map +1 -1
  1008. package/packages/core/dist/plugins/prompt-library.js +4 -1
  1009. package/packages/core/dist/plugins/prompt-library.js.map +1 -1
  1010. package/packages/core/dist/plugins/readme-gen.d.ts +6 -2
  1011. package/packages/core/dist/plugins/readme-gen.d.ts.map +1 -1
  1012. package/packages/core/dist/plugins/readme-gen.js +514 -73
  1013. package/packages/core/dist/plugins/readme-gen.js.map +1 -1
  1014. package/packages/core/dist/plugins/recall-unread.d.ts +1 -1
  1015. package/packages/core/dist/plugins/recall-unread.d.ts.map +1 -1
  1016. package/packages/core/dist/plugins/recall-unread.js +411 -68
  1017. package/packages/core/dist/plugins/recall-unread.js.map +1 -1
  1018. package/packages/core/dist/plugins/resources.d.ts.map +1 -1
  1019. package/packages/core/dist/plugins/resources.js +66 -3
  1020. package/packages/core/dist/plugins/resources.js.map +1 -1
  1021. package/packages/core/dist/plugins/reverse-skill.d.ts.map +1 -1
  1022. package/packages/core/dist/plugins/reverse-skill.js +2 -1
  1023. package/packages/core/dist/plugins/reverse-skill.js.map +1 -1
  1024. package/packages/core/dist/plugins/reviewer-bot.d.ts +1 -0
  1025. package/packages/core/dist/plugins/reviewer-bot.d.ts.map +1 -1
  1026. package/packages/core/dist/plugins/reviewer-bot.js +35 -16
  1027. package/packages/core/dist/plugins/reviewer-bot.js.map +1 -1
  1028. package/packages/core/dist/plugins/runtime-doctor.d.ts +1 -0
  1029. package/packages/core/dist/plugins/runtime-doctor.d.ts.map +1 -1
  1030. package/packages/core/dist/plugins/runtime-doctor.js +4 -1
  1031. package/packages/core/dist/plugins/runtime-doctor.js.map +1 -1
  1032. package/packages/core/dist/plugins/runtime.d.ts.map +1 -1
  1033. package/packages/core/dist/plugins/runtime.js +104 -70
  1034. package/packages/core/dist/plugins/runtime.js.map +1 -1
  1035. package/packages/core/dist/plugins/secure-audit.d.ts +1 -0
  1036. package/packages/core/dist/plugins/secure-audit.d.ts.map +1 -1
  1037. package/packages/core/dist/plugins/secure-audit.js +30 -12
  1038. package/packages/core/dist/plugins/secure-audit.js.map +1 -1
  1039. package/packages/core/dist/plugins/session-bookmarks.d.ts +1 -0
  1040. package/packages/core/dist/plugins/session-bookmarks.d.ts.map +1 -1
  1041. package/packages/core/dist/plugins/session-bookmarks.js +4 -1
  1042. package/packages/core/dist/plugins/session-bookmarks.js.map +1 -1
  1043. package/packages/core/dist/plugins/session-bridge.d.ts +1 -0
  1044. package/packages/core/dist/plugins/session-bridge.d.ts.map +1 -1
  1045. package/packages/core/dist/plugins/session-bridge.js +401 -88
  1046. package/packages/core/dist/plugins/session-bridge.js.map +1 -1
  1047. package/packages/core/dist/plugins/session-compare.d.ts +1 -0
  1048. package/packages/core/dist/plugins/session-compare.d.ts.map +1 -1
  1049. package/packages/core/dist/plugins/session-compare.js +4 -1
  1050. package/packages/core/dist/plugins/session-compare.js.map +1 -1
  1051. package/packages/core/dist/plugins/session-export.d.ts +1 -0
  1052. package/packages/core/dist/plugins/session-export.d.ts.map +1 -1
  1053. package/packages/core/dist/plugins/session-export.js +6 -6
  1054. package/packages/core/dist/plugins/session-export.js.map +1 -1
  1055. package/packages/core/dist/plugins/session-insights.d.ts +37 -1
  1056. package/packages/core/dist/plugins/session-insights.d.ts.map +1 -1
  1057. package/packages/core/dist/plugins/session-insights.js +406 -21
  1058. package/packages/core/dist/plugins/session-insights.js.map +1 -1
  1059. package/packages/core/dist/plugins/session-search.d.ts +1 -0
  1060. package/packages/core/dist/plugins/session-search.d.ts.map +1 -1
  1061. package/packages/core/dist/plugins/session-search.js +4 -1
  1062. package/packages/core/dist/plugins/session-search.js.map +1 -1
  1063. package/packages/core/dist/plugins/session.d.ts.map +1 -1
  1064. package/packages/core/dist/plugins/session.js +8 -1
  1065. package/packages/core/dist/plugins/session.js.map +1 -1
  1066. package/packages/core/dist/plugins/skill-catalog.d.ts +1 -0
  1067. package/packages/core/dist/plugins/skill-catalog.d.ts.map +1 -1
  1068. package/packages/core/dist/plugins/skill-catalog.js +6 -5
  1069. package/packages/core/dist/plugins/skill-catalog.js.map +1 -1
  1070. package/packages/core/dist/plugins/skill-guard.d.ts +2 -1
  1071. package/packages/core/dist/plugins/skill-guard.d.ts.map +1 -1
  1072. package/packages/core/dist/plugins/skill-guard.js +241 -61
  1073. package/packages/core/dist/plugins/skill-guard.js.map +1 -1
  1074. package/packages/core/dist/plugins/sql-lens-worker.d.ts +2 -0
  1075. package/packages/core/dist/plugins/sql-lens-worker.d.ts.map +1 -0
  1076. package/packages/core/dist/plugins/sql-lens-worker.js +206 -0
  1077. package/packages/core/dist/plugins/sql-lens-worker.js.map +1 -0
  1078. package/packages/core/dist/plugins/sql-lens.d.ts +7 -1
  1079. package/packages/core/dist/plugins/sql-lens.d.ts.map +1 -1
  1080. package/packages/core/dist/plugins/sql-lens.js +301 -68
  1081. package/packages/core/dist/plugins/sql-lens.js.map +1 -1
  1082. package/packages/core/dist/plugins/stdio.d.ts +3 -0
  1083. package/packages/core/dist/plugins/stdio.d.ts.map +1 -1
  1084. package/packages/core/dist/plugins/stdio.js +62 -4
  1085. package/packages/core/dist/plugins/stdio.js.map +1 -1
  1086. package/packages/core/dist/plugins/synapse.d.ts.map +1 -1
  1087. package/packages/core/dist/plugins/synapse.js +17 -9
  1088. package/packages/core/dist/plugins/synapse.js.map +1 -1
  1089. package/packages/core/dist/plugins/tab-manager.d.ts +1 -0
  1090. package/packages/core/dist/plugins/tab-manager.d.ts.map +1 -1
  1091. package/packages/core/dist/plugins/tab-manager.js +167 -62
  1092. package/packages/core/dist/plugins/tab-manager.js.map +1 -1
  1093. package/packages/core/dist/plugins/taskboard.d.ts.map +1 -1
  1094. package/packages/core/dist/plugins/taskboard.js +50 -22
  1095. package/packages/core/dist/plugins/taskboard.js.map +1 -1
  1096. package/packages/core/dist/plugins/telemetry-blocker.d.ts +4 -1
  1097. package/packages/core/dist/plugins/telemetry-blocker.d.ts.map +1 -1
  1098. package/packages/core/dist/plugins/telemetry-blocker.js +30 -11
  1099. package/packages/core/dist/plugins/telemetry-blocker.js.map +1 -1
  1100. package/packages/core/dist/plugins/test-harness.d.ts +7 -1
  1101. package/packages/core/dist/plugins/test-harness.d.ts.map +1 -1
  1102. package/packages/core/dist/plugins/test-harness.js +255 -43
  1103. package/packages/core/dist/plugins/test-harness.js.map +1 -1
  1104. package/packages/core/dist/plugins/theme-studio.d.ts.map +1 -1
  1105. package/packages/core/dist/plugins/theme-studio.js +4 -2
  1106. package/packages/core/dist/plugins/theme-studio.js.map +1 -1
  1107. package/packages/core/dist/plugins/token-guard.d.ts.map +1 -1
  1108. package/packages/core/dist/plugins/token-guard.js +164 -32
  1109. package/packages/core/dist/plugins/token-guard.js.map +1 -1
  1110. package/packages/core/dist/plugins/tools.d.ts.map +1 -1
  1111. package/packages/core/dist/plugins/tools.js +4 -1
  1112. package/packages/core/dist/plugins/tools.js.map +1 -1
  1113. package/packages/core/dist/plugins/turn-rewind.d.ts +17 -0
  1114. package/packages/core/dist/plugins/turn-rewind.d.ts.map +1 -1
  1115. package/packages/core/dist/plugins/turn-rewind.js +340 -36
  1116. package/packages/core/dist/plugins/turn-rewind.js.map +1 -1
  1117. package/packages/core/dist/plugins/undo-savepoint.d.ts.map +1 -1
  1118. package/packages/core/dist/plugins/undo-savepoint.js +157 -51
  1119. package/packages/core/dist/plugins/undo-savepoint.js.map +1 -1
  1120. package/packages/core/dist/plugins/vision-toolkit.d.ts +22 -3
  1121. package/packages/core/dist/plugins/vision-toolkit.d.ts.map +1 -1
  1122. package/packages/core/dist/plugins/vision-toolkit.js +389 -66
  1123. package/packages/core/dist/plugins/vision-toolkit.js.map +1 -1
  1124. package/packages/core/dist/plugins/web-research.d.ts.map +1 -1
  1125. package/packages/core/dist/plugins/web-research.js +13 -4
  1126. package/packages/core/dist/plugins/web-research.js.map +1 -1
  1127. package/packages/core/dist/plugins/workspace-navigator.d.ts +8 -2
  1128. package/packages/core/dist/plugins/workspace-navigator.d.ts.map +1 -1
  1129. package/packages/core/dist/plugins/workspace-navigator.js +28 -10
  1130. package/packages/core/dist/plugins/workspace-navigator.js.map +1 -1
  1131. package/packages/core/dist/plugins/workspace-search.d.ts +7 -0
  1132. package/packages/core/dist/plugins/workspace-search.d.ts.map +1 -1
  1133. package/packages/core/dist/plugins/workspace-search.js +67 -34
  1134. package/packages/core/dist/plugins/workspace-search.js.map +1 -1
  1135. package/packages/core/dist/plugins/yaml-validator.d.ts +1 -0
  1136. package/packages/core/dist/plugins/yaml-validator.d.ts.map +1 -1
  1137. package/packages/core/dist/plugins/yaml-validator.js +156 -47
  1138. package/packages/core/dist/plugins/yaml-validator.js.map +1 -1
  1139. package/packages/core/dist/runtime.d.ts.map +1 -1
  1140. package/packages/core/dist/runtime.js +8 -3
  1141. package/packages/core/dist/runtime.js.map +1 -1
  1142. package/packages/core/dist/services.d.ts.map +1 -1
  1143. package/packages/core/dist/services.js +22 -2
  1144. package/packages/core/dist/services.js.map +1 -1
  1145. package/packages/core/dist/stdio.d.ts +1 -0
  1146. package/packages/core/dist/stdio.d.ts.map +1 -1
  1147. package/packages/core/dist/stdio.js +241 -25
  1148. package/packages/core/dist/stdio.js.map +1 -1
  1149. package/packages/core/dist/workspace-path.d.ts +1 -0
  1150. package/packages/core/dist/workspace-path.d.ts.map +1 -1
  1151. package/packages/core/dist/workspace-path.js +9 -0
  1152. package/packages/core/dist/workspace-path.js.map +1 -1
  1153. package/packages/core/package.json +1 -1
  1154. package/packages/core/profiles/default/cordis.yml +4 -2
  1155. package/packages/core/src/atomic-write.ts +45 -0
  1156. package/packages/core/src/boot.ts +123 -11
  1157. package/packages/core/src/bounded-file.ts +41 -0
  1158. package/packages/core/src/config.ts +18 -0
  1159. package/packages/core/src/cordis-timer.ts +107 -0
  1160. package/packages/core/src/plugins/agent-teams.ts +1003 -103
  1161. package/packages/core/src/plugins/anchored-standard.ts +41 -14
  1162. package/packages/core/src/plugins/annotation.ts +36 -19
  1163. package/packages/core/src/plugins/archify.ts +80 -33
  1164. package/packages/core/src/plugins/at-file.ts +110 -26
  1165. package/packages/core/src/plugins/auto-mode.ts +91 -24
  1166. package/packages/core/src/plugins/better-sidebar.ts +15 -4
  1167. package/packages/core/src/plugins/browser-fetch.ts +286 -95
  1168. package/packages/core/src/plugins/browser-session.ts +421 -102
  1169. package/packages/core/src/plugins/canvas-draw.ts +74 -21
  1170. package/packages/core/src/plugins/change-verifier.ts +33 -9
  1171. package/packages/core/src/plugins/cleaner.ts +339 -37
  1172. package/packages/core/src/plugins/cli-notifier.ts +110 -41
  1173. package/packages/core/src/plugins/code2skill.ts +175 -34
  1174. package/packages/core/src/plugins/colleague-skill.ts +72 -47
  1175. package/packages/core/src/plugins/context-doctor.ts +410 -40
  1176. package/packages/core/src/plugins/context.ts +164 -39
  1177. package/packages/core/src/plugins/cost-meter.ts +360 -113
  1178. package/packages/core/src/plugins/dependency-checker.ts +456 -119
  1179. package/packages/core/src/plugins/docker-sandbox.ts +312 -76
  1180. package/packages/core/src/plugins/fail-logger.ts +169 -18
  1181. package/packages/core/src/plugins/genui.ts +104 -42
  1182. package/packages/core/src/plugins/git-time-capsule.ts +424 -55
  1183. package/packages/core/src/plugins/graph-memory.ts +333 -60
  1184. package/packages/core/src/plugins/history-compressor.ts +29 -12
  1185. package/packages/core/src/plugins/hol-guard.ts +31 -13
  1186. package/packages/core/src/plugins/i18n-pair.ts +296 -52
  1187. package/packages/core/src/plugins/image-compressor.ts +98 -25
  1188. package/packages/core/src/plugins/llm-verifier.ts +39 -21
  1189. package/packages/core/src/plugins/mcp-client.ts +1069 -249
  1190. package/packages/core/src/plugins/mcp-panel.ts +37 -21
  1191. package/packages/core/src/plugins/memory.ts +262 -57
  1192. package/packages/core/src/plugins/mirage-bridge.ts +22 -10
  1193. package/packages/core/src/plugins/mock-server.ts +91 -60
  1194. package/packages/core/src/plugins/model.ts +4 -0
  1195. package/packages/core/src/plugins/models.ts +10 -2
  1196. package/packages/core/src/plugins/modlens.ts +463 -29
  1197. package/packages/core/src/plugins/module-search.ts +40 -19
  1198. package/packages/core/src/plugins/obsidian-sync.ts +26 -15
  1199. package/packages/core/src/plugins/openpets.ts +193 -42
  1200. package/packages/core/src/plugins/plan-execute.ts +49 -21
  1201. package/packages/core/src/plugins/plugin-check.ts +100 -16
  1202. package/packages/core/src/plugins/plugin-dev.ts +167 -23
  1203. package/packages/core/src/plugins/plugin-finder.ts +119 -12
  1204. package/packages/core/src/plugins/plugin-radar.ts +130 -39
  1205. package/packages/core/src/plugins/plugin-stars.ts +252 -80
  1206. package/packages/core/src/plugins/prompt-guard.ts +51 -12
  1207. package/packages/core/src/plugins/prompt-library.ts +14 -8
  1208. package/packages/core/src/plugins/readme-gen.ts +476 -79
  1209. package/packages/core/src/plugins/recall-unread.ts +412 -68
  1210. package/packages/core/src/plugins/resources.ts +70 -2
  1211. package/packages/core/src/plugins/reverse-skill.ts +9 -5
  1212. package/packages/core/src/plugins/reviewer-bot.ts +34 -16
  1213. package/packages/core/src/plugins/runtime-doctor.ts +4 -1
  1214. package/packages/core/src/plugins/runtime.ts +98 -68
  1215. package/packages/core/src/plugins/secure-audit.ts +33 -12
  1216. package/packages/core/src/plugins/session-bookmarks.ts +12 -6
  1217. package/packages/core/src/plugins/session-bridge.ts +404 -89
  1218. package/packages/core/src/plugins/session-compare.ts +10 -4
  1219. package/packages/core/src/plugins/session-export.ts +12 -9
  1220. package/packages/core/src/plugins/session-insights.ts +454 -31
  1221. package/packages/core/src/plugins/session-search.ts +4 -1
  1222. package/packages/core/src/plugins/session.ts +8 -1
  1223. package/packages/core/src/plugins/skill-catalog.ts +13 -8
  1224. package/packages/core/src/plugins/skill-guard.ts +254 -61
  1225. package/packages/core/src/plugins/sql-lens-worker.ts +202 -0
  1226. package/packages/core/src/plugins/sql-lens.ts +323 -71
  1227. package/packages/core/src/plugins/stdio.ts +58 -3
  1228. package/packages/core/src/plugins/synapse.ts +16 -9
  1229. package/packages/core/src/plugins/tab-manager.ts +163 -65
  1230. package/packages/core/src/plugins/taskboard.ts +81 -47
  1231. package/packages/core/src/plugins/telemetry-blocker.ts +27 -11
  1232. package/packages/core/src/plugins/test-harness.ts +275 -42
  1233. package/packages/core/src/plugins/theme-studio.ts +7 -2
  1234. package/packages/core/src/plugins/token-guard.ts +171 -31
  1235. package/packages/core/src/plugins/tools.ts +4 -1
  1236. package/packages/core/src/plugins/turn-rewind.ts +351 -37
  1237. package/packages/core/src/plugins/undo-savepoint.ts +160 -59
  1238. package/packages/core/src/plugins/vision-toolkit.ts +420 -63
  1239. package/packages/core/src/plugins/web-research.ts +19 -7
  1240. package/packages/core/src/plugins/workspace-navigator.ts +39 -14
  1241. package/packages/core/src/plugins/workspace-search.ts +75 -37
  1242. package/packages/core/src/plugins/yaml-validator.ts +165 -46
  1243. package/packages/core/src/runtime.ts +8 -2
  1244. package/packages/core/src/services.ts +22 -2
  1245. package/packages/core/src/stdio.ts +220 -25
  1246. package/packages/core/src/workspace-path.ts +10 -0
  1247. package/packages/core/test/agent-teams.test.ts +495 -2
  1248. package/packages/core/test/anchored-standard.test.ts +60 -0
  1249. package/packages/core/test/annotation.test.ts +69 -0
  1250. package/packages/core/test/archify.test.ts +212 -1
  1251. package/packages/core/test/at-file.test.ts +666 -0
  1252. package/packages/core/test/atomic-write.test.ts +52 -0
  1253. package/packages/core/test/auto-mode.test.ts +629 -0
  1254. package/packages/core/test/better-sidebar.test.ts +108 -1
  1255. package/packages/core/test/boot.test.ts +129 -1
  1256. package/packages/core/test/bounded-file.test.ts +55 -0
  1257. package/packages/core/test/browser-fetch-rebinding.test.ts +93 -0
  1258. package/packages/core/test/browser-fetch.test.ts +844 -0
  1259. package/packages/core/test/browser-session.test.ts +1236 -0
  1260. package/packages/core/test/canvas-draw.test.ts +164 -0
  1261. package/packages/core/test/change-verifier.test.ts +200 -0
  1262. package/packages/core/test/cleaner.test.ts +316 -0
  1263. package/packages/core/test/cli-notifier.test.ts +235 -0
  1264. package/packages/core/test/code2skill.test.ts +264 -0
  1265. package/packages/core/test/colleague-skill.test.ts +167 -0
  1266. package/packages/core/test/config.test.ts +8 -0
  1267. package/packages/core/test/context-doctor.test.ts +597 -2
  1268. package/packages/core/test/context-insights.test.ts +396 -0
  1269. package/packages/core/test/cordis-timer.test.ts +147 -0
  1270. package/packages/core/test/cost-meter.test.ts +362 -0
  1271. package/packages/core/test/dependency-checker.test.ts +576 -3
  1272. package/packages/core/test/docker-sandbox.test.ts +170 -0
  1273. package/packages/core/test/fail-logger.test.ts +363 -0
  1274. package/packages/core/test/genui.test.ts +162 -0
  1275. package/packages/core/test/git-time-capsule.test.ts +1364 -3
  1276. package/packages/core/test/graph-memory.test.ts +508 -0
  1277. package/packages/core/test/history-compressor.test.ts +47 -0
  1278. package/packages/core/test/hol-guard.test.ts +61 -0
  1279. package/packages/core/test/i18n-pair.test.ts +367 -0
  1280. package/packages/core/test/image-compressor.test.ts +59 -0
  1281. package/packages/core/test/llm-verifier.test.ts +4 -0
  1282. package/packages/core/test/mcp-client.test.ts +1037 -0
  1283. package/packages/core/test/mcp-panel.test.ts +91 -0
  1284. package/packages/core/test/memory.test.ts +125 -0
  1285. package/packages/core/test/mirage-bridge.test.ts +4 -0
  1286. package/packages/core/test/mock-server.test.ts +63 -0
  1287. package/packages/core/test/model.test.ts +62 -0
  1288. package/packages/core/test/models.test.ts +85 -0
  1289. package/packages/core/test/modlens.test.ts +615 -0
  1290. package/packages/core/test/module-search.test.ts +20 -0
  1291. package/packages/core/test/obsidian-sync.test.ts +57 -0
  1292. package/packages/core/test/openpets.test.ts +273 -0
  1293. package/packages/core/test/plan-execute.test.ts +86 -0
  1294. package/packages/core/test/plugin-dev.test.ts +246 -0
  1295. package/packages/core/test/plugin-finder.test.ts +105 -0
  1296. package/packages/core/test/plugin-radar.test.ts +87 -0
  1297. package/packages/core/test/plugin-stars.test.ts +429 -4
  1298. package/packages/core/test/profiles.integration.test.ts +451 -2
  1299. package/packages/core/test/prompt-guard.test.ts +78 -0
  1300. package/packages/core/test/readme-gen.test.ts +403 -3
  1301. package/packages/core/test/recall-unread.test.ts +559 -2
  1302. package/packages/core/test/registry.test.ts +16 -0
  1303. package/packages/core/test/resources.test.ts +310 -0
  1304. package/packages/core/test/reviewer-bot.test.ts +55 -0
  1305. package/packages/core/test/runtime-fixture.ts +10 -1
  1306. package/packages/core/test/runtime.test.ts +155 -2
  1307. package/packages/core/test/secure-audit.test.ts +14 -1
  1308. package/packages/core/test/services.test.ts +2772 -117
  1309. package/packages/core/test/session-bridge.test.ts +309 -1
  1310. package/packages/core/test/session-insights.test.ts +486 -0
  1311. package/packages/core/test/session.test.ts +153 -0
  1312. package/packages/core/test/skill-guard.test.ts +261 -2
  1313. package/packages/core/test/sql-lens.test.ts +244 -0
  1314. package/packages/core/test/stdio-args.test.ts +484 -1
  1315. package/packages/core/test/stdio.test.ts +159 -0
  1316. package/packages/core/test/synapse.test.ts +67 -0
  1317. package/packages/core/test/tab-manager.test.ts +58 -0
  1318. package/packages/core/test/taskboard.test.ts +73 -0
  1319. package/packages/core/test/telemetry-blocker.test.ts +61 -0
  1320. package/packages/core/test/test-harness.test.ts +297 -0
  1321. package/packages/core/test/token-guard.test.ts +362 -1
  1322. package/packages/core/test/tools.test.ts +118 -0
  1323. package/packages/core/test/turn-rewind.test.ts +532 -2
  1324. package/packages/core/test/undo-savepoint.test.ts +222 -0
  1325. package/packages/core/test/vision-toolkit.test.ts +480 -7
  1326. package/packages/core/test/web-research.test.ts +63 -0
  1327. package/packages/core/test/workspace-git-status.test.ts +19 -2
  1328. package/packages/core/test/workspace-navigator.test.ts +11 -1
  1329. package/packages/core/test/workspace-search.test.ts +88 -0
  1330. package/packages/core/test/yaml-validator.test.ts +195 -0
  1331. package/packages/host-webserver/package.json +1 -1
  1332. package/apps/web/dist/assets/index-C_AdODuM.js +0 -20
  1333. package/apps/web/dist/assets/index-De8Rm01Z.css +0 -2
@@ -1,16 +1,17 @@
1
1
  import { existsSync } from "node:fs";
2
- import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, writeFile } from "node:fs/promises";
3
3
  import { execFile, spawn, type ChildProcess } from "node:child_process";
4
- import { createServer } from "node:http";
4
+ import { createServer, Server } from "node:http";
5
5
  import { tmpdir } from "node:os";
6
- import { dirname, join } from "node:path";
6
+ import { delimiter, dirname, join } from "node:path";
7
7
  import { promisify } from "node:util";
8
+ import { deflateSync } from "node:zlib";
8
9
  import { DatabaseSync } from "node:sqlite";
9
10
  import { Context } from "@deepseek-ai/cordis";
10
11
  import { Type } from "@earendil-works/pi-ai";
11
12
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
12
13
  import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
13
- import { afterEach, describe, expect, test } from "vitest";
14
+ import { afterEach, describe, expect, test, vi } from "vitest";
14
15
  import { PiPluginUiRegistry, PiToolRegistry, provideLaunchContext } from "../src/services.js";
15
16
  import modelPlugin from "../src/plugins/model.js";
16
17
  import modelsPlugin from "../src/plugins/models.js";
@@ -66,10 +67,11 @@ import synapsePlugin from "../src/plugins/synapse.js";
66
67
  import { inspectGuardInput } from "../src/plugins/hol-guard.js";
67
68
  import holGuardPlugin from "../src/plugins/hol-guard.js";
68
69
  import pluginRadarPlugin from "../src/plugins/plugin-radar.js";
69
- import pluginCheckPlugin, { type PluginCheckScanReport } from "../src/plugins/plugin-check.js";
70
+ import pluginCheckPlugin, { type PluginCheckReport, type PluginCheckScanReport } from "../src/plugins/plugin-check.js";
70
71
  import annotationPlugin from "../src/plugins/annotation.js";
71
72
  import costMeterPlugin from "../src/plugins/cost-meter.js";
72
73
  import skillCatalogPlugin from "../src/plugins/skill-catalog.js";
74
+ import skillGuardPlugin from "../src/plugins/skill-guard.js";
73
75
  import undoSavepointPlugin from "../src/plugins/undo-savepoint.js";
74
76
  import mcpPanelPlugin from "../src/plugins/mcp-panel.js";
75
77
 
@@ -165,6 +167,32 @@ async function createContext(): Promise<{ context: Context; cwd: string; agentDi
165
167
  const modelConfig = { provider: "deepseek", model: "deepseek-v4-flash", refreshOnCreate: false } as const;
166
168
  const isolatedResources = { noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true } as const;
167
169
 
170
+ function testPngChunk(type: string, data: Buffer): Buffer {
171
+ const typeBytes = Buffer.from(type, "ascii");
172
+ let crc = 0xffffffff;
173
+ for (const byte of Buffer.concat([typeBytes, data])) {
174
+ crc ^= byte;
175
+ for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
176
+ }
177
+ const result = Buffer.allocUnsafe(12 + data.length);
178
+ result.writeUInt32BE(data.length, 0);
179
+ typeBytes.copy(result, 4);
180
+ data.copy(result, 8);
181
+ result.writeUInt32BE((crc ^ 0xffffffff) >>> 0, 8 + data.length);
182
+ return result;
183
+ }
184
+
185
+ function testPng(header: Buffer, pixels: Buffer, beforeIdat: Buffer[] = [], afterIdat: Buffer[] = []): Buffer {
186
+ return Buffer.concat([
187
+ Buffer.from("89504e470d0a1a0a", "hex"),
188
+ testPngChunk("IHDR", header),
189
+ ...beforeIdat,
190
+ testPngChunk("IDAT", deflateSync(pixels)),
191
+ ...afterIdat,
192
+ testPngChunk("IEND", Buffer.alloc(0)),
193
+ ]);
194
+ }
195
+
168
196
  describe("Pi domain plugins", () => {
169
197
  test("projects session lineage into an active graph without duplicating session history", () => {
170
198
  const sessions = [
@@ -372,125 +400,1512 @@ describe("Pi domain plugins", () => {
372
400
  read: () => ({ ready: true }),
373
401
  });
374
402
 
375
- await expect(panels.snapshot()).resolves.toEqual([{ id: "example-panel", pluginId: "example-plugin", title: "Example", data: { ready: true } }]);
376
- dispose();
377
- await expect(panels.snapshot()).resolves.toEqual([]);
403
+ await expect(panels.snapshot()).resolves.toEqual([{ id: "example-panel", pluginId: "example-plugin", title: "Example", data: { ready: true } }]);
404
+ dispose();
405
+ await expect(panels.snapshot()).resolves.toEqual([]);
406
+ });
407
+
408
+ test("detaches panel data before returning snapshots", async () => {
409
+ const panels = new PiPluginUiRegistry();
410
+ const state = { nested: { value: 1 }, items: ["a"] };
411
+ panels.register({
412
+ id: "detached-panel",
413
+ pluginId: "example-plugin",
414
+ title: "Detached",
415
+ read: () => state,
416
+ });
417
+
418
+ const first = (await panels.snapshot())[0];
419
+ (first?.data as { nested: { value: number }; items: string[] }).nested.value = 99;
420
+ (first?.data as { nested: { value: number }; items: string[] }).items.push("mutated");
421
+ await expect(panels.snapshot()).resolves.toMatchObject([{ data: { nested: { value: 1 }, items: ["a"] } }]);
422
+ });
423
+
424
+ test("publishes a live context insight panel from the session runtime", async () => {
425
+ const context = new Context();
426
+ contexts.push(context);
427
+ const panels = new PiPluginUiRegistry();
428
+ const toolRegistry = new PiToolRegistry();
429
+ context.provide("piRuntime", {
430
+ session: { messages: [{ role: "user" }], getContextUsage: () => ({ tokens: 1200, contextWindow: 8000, percent: 15 }) },
431
+ } as never);
432
+ context.provide("piPluginUi", panels);
433
+ context.provide("piTools", toolRegistry);
434
+
435
+ await context.plugin(contextPlugin);
436
+
437
+ context.emit("pi/session-event", { type: "message_start" } as never);
438
+ context.emit("pi/session-event", { type: "compaction_start" } as never);
439
+ await expect(panels.snapshot()).resolves.toMatchObject([
440
+ {
441
+ data: {
442
+ events: 2,
443
+ compactions: 1,
444
+ eventTypes: { message_start: 1, compaction_start: 1 },
445
+ recentEvents: [{ type: "message_start" }, { type: "compaction_start" }],
446
+ },
447
+ },
448
+ ]);
449
+
450
+ await expect(panels.snapshot()).resolves.toEqual([
451
+ {
452
+ id: "context-insight-panel",
453
+ pluginId: "@pi-harness/core/plugins/context",
454
+ title: "上下文洞察",
455
+ description: "查看当前上下文占用、消息规模和压缩事件。",
456
+ icon: "◒",
457
+ data: {
458
+ tokens: 1200,
459
+ contextWindow: 8000,
460
+ percent: 15,
461
+ messages: 1,
462
+ scannedMessages: 1,
463
+ messagesTruncated: false,
464
+ events: 2,
465
+ compactions: 1,
466
+ composition: { user: 1, assistant: 0, toolResult: 0, system: 0, other: 0 },
467
+ eventTypes: { message_start: 1, compaction_start: 1 },
468
+ recentEvents: [expect.objectContaining({ type: "message_start" }), expect.objectContaining({ type: "compaction_start" })],
469
+ limits: { scannedMessages: 10_000, recentEvents: 50, eventTypes: 64, eventTypeCharacters: 128 },
470
+ },
471
+ },
472
+ ]);
473
+
474
+ const inspect = toolRegistry.snapshot().customTools.find((entry) => entry.name === "context_inspect");
475
+ if (inspect === undefined) throw new Error("context_inspect was not registered");
476
+ await expect(inspect.execute("inspect-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
477
+ details: { composition: { user: 1 }, messages: 1 },
478
+ });
479
+ });
480
+
481
+ test("persists agent team tasks and exposes a live collaboration panel", async () => {
482
+ const context = new Context();
483
+ contexts.push(context);
484
+ const panels = new PiPluginUiRegistry();
485
+ const tools = new PiToolRegistry();
486
+ const entries: unknown[] = [];
487
+ context.provide("piSession", {
488
+ manager: {
489
+ getEntries: () => entries,
490
+ getBranch: () => entries,
491
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
492
+ },
493
+ } as never);
494
+ context.provide("piTools", tools);
495
+ context.provide("piPluginUi", panels);
496
+
497
+ await context.plugin(agentTeamsPlugin);
498
+ const tool = firstTool(tools);
499
+ await expect(
500
+ tool.execute("invalid-status", { action: "add_task", title: "Invalid", status: "almost_done" }, undefined, undefined, {} as never),
501
+ ).rejects.toThrow(/task status/iu);
502
+ await expect(tool.execute("invalid-member", { action: "add_member", name: "审阅者" }, undefined, undefined, {} as never)).rejects.toThrow(/member id/iu);
503
+ await expect(
504
+ tool.execute("invalid-member-status", { action: "add_member", id: "observer", name: "Observer", status: "offline" }, undefined, undefined, {} as never),
505
+ ).rejects.toThrow(/member status/iu);
506
+ await expect(tool.execute("long-title", { action: "add_task", title: "x".repeat(201) }, undefined, undefined, {} as never)).rejects.toThrow(/title.*200/iu);
507
+ expect(entries).toEqual([]);
508
+ await expect(tool.execute("call-1", { action: "add_task", title: "Review plugin manifest" }, undefined, undefined, {} as never)).resolves.toMatchObject({
509
+ content: [{ text: "Task task-1 created." }],
510
+ });
511
+ await expect(
512
+ tool.execute("call-2", { action: "add_task", title: "Run integration checks", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
513
+ ).resolves.toMatchObject({ details: { item: { id: "task-2", status: "blocked", dependsOn: ["task-1"] } } });
514
+ await expect(tool.execute("cycle", { action: "update_task", id: "task-1", dependsOn: ["task-2"] }, undefined, undefined, {} as never)).rejects.toThrow(
515
+ /dependency cycle/iu,
516
+ );
517
+ await expect(tool.execute("call-3", { action: "update_task", id: "task-1", status: "done" }, undefined, undefined, {} as never)).resolves.toMatchObject({
518
+ details: { item: { id: "task-1", status: "done" } },
519
+ });
520
+ await expect(panels.snapshot()).resolves.toMatchObject([
521
+ { id: "agent-teams-panel", data: { tasks: [{ status: "done" }, { id: "task-2", status: "todo" }] } },
522
+ ]);
523
+ await expect(tool.execute("call-4", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).resolves.toMatchObject({
524
+ details: { item: { id: "task-2", status: "in_progress", assignee: "builder" } },
525
+ });
526
+ await expect(
527
+ tool.execute("call-5", { action: "send_message", from: "builder", to: "reviewer", body: "实现已完成,请开始复核" }, undefined, undefined, {} as never),
528
+ ).resolves.toMatchObject({ details: { item: { from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: false } } });
529
+ await expect(tool.execute("call-6", { action: "read_messages", to: "reviewer" }, undefined, undefined, {} as never)).resolves.toMatchObject({
530
+ details: { messages: [{ from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: true }] },
531
+ });
532
+ await expect(
533
+ tool.execute("call-7", { action: "read_messages", to: "reviewer", unreadOnly: true }, undefined, undefined, {} as never),
534
+ ).resolves.toMatchObject({
535
+ details: { messages: [] },
536
+ });
537
+ await expect(
538
+ tool.execute("call-8", { action: "add_member", id: "observer", name: "观察员", role: "发布观察", status: "idle" }, undefined, undefined, {} as never),
539
+ ).resolves.toMatchObject({ details: { kind: "member", item: { id: "observer", name: "观察员", role: "发布观察", status: "idle" } } });
540
+ await expect(panels.snapshot()).resolves.toMatchObject([
541
+ {
542
+ id: "agent-teams-panel",
543
+ data: {
544
+ tasks: [
545
+ { title: "Review plugin manifest", status: "done" },
546
+ { title: "Run integration checks", status: "in_progress" },
547
+ ],
548
+ },
549
+ },
550
+ ]);
551
+ const panel = (await panels.snapshot())[0];
552
+ if (panel === undefined) throw new Error("agent-teams-panel was not registered");
553
+ const members = (panel.data as { members: { id: string; status: string }[] }).members;
554
+ expect(members.find((member) => member.id === "observer")).toMatchObject({ status: "idle" });
555
+ expect(members.find((member) => member.id === "builder")).toMatchObject({
556
+ status: "working",
557
+ });
558
+ expect(entries).toHaveLength(7);
559
+ });
560
+
561
+ test("publishes agent team input bounds in the tool parameter schema", async () => {
562
+ const context = new Context();
563
+ contexts.push(context);
564
+ context.provide("piSession", { manager: { getEntries: () => [], getBranch: () => [], appendCustomEntry() {} } } as never);
565
+ const tools = new PiToolRegistry();
566
+ context.provide("piTools", tools);
567
+ context.provide("piPluginUi", new PiPluginUiRegistry());
568
+ await context.plugin(agentTeamsPlugin);
569
+
570
+ const idPattern = "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$";
571
+ expect(namedTool(tools, "team_task").parameters).toMatchObject({
572
+ properties: {
573
+ action: {
574
+ anyOf: [
575
+ { const: "add_task" },
576
+ { const: "update_task" },
577
+ { const: "claim_task" },
578
+ { const: "remove_task" },
579
+ { const: "add_member" },
580
+ { const: "remove_member" },
581
+ { const: "send_message" },
582
+ { const: "read_messages" },
583
+ { const: "clear_messages" },
584
+ { const: "get_state" },
585
+ ],
586
+ },
587
+ id: { maxLength: 64, pattern: idPattern },
588
+ title: { maxLength: 200 },
589
+ assignee: { maxLength: 64, pattern: idPattern },
590
+ name: { maxLength: 200 },
591
+ role: { maxLength: 200 },
592
+ status: {
593
+ anyOf: [{ const: "todo" }, { const: "blocked" }, { const: "in_progress" }, { const: "done" }, { const: "idle" }, { const: "working" }],
594
+ },
595
+ dependsOn: { maxItems: 256, items: { maxLength: 64, pattern: idPattern } },
596
+ from: { maxLength: 64, pattern: idPattern },
597
+ to: { maxLength: 64, pattern: idPattern },
598
+ body: { maxLength: 4_000 },
599
+ confirm: { type: "boolean" },
600
+ },
601
+ additionalProperties: false,
602
+ });
603
+ });
604
+
605
+ test("updates all mutable agent team task fields and recalculates readiness", async () => {
606
+ const context = new Context();
607
+ contexts.push(context);
608
+ const entries: unknown[] = [];
609
+ context.provide("piSession", {
610
+ manager: {
611
+ getEntries: () => entries,
612
+ getBranch: () => entries,
613
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
614
+ },
615
+ } as never);
616
+ const tools = new PiToolRegistry();
617
+ context.provide("piTools", tools);
618
+ context.provide("piPluginUi", new PiPluginUiRegistry());
619
+ await context.plugin(agentTeamsPlugin);
620
+ const tool = namedTool(tools, "team_task");
621
+ await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
622
+ await tool.execute("target", { action: "add_task", title: "Original" }, undefined, undefined, {} as never);
623
+
624
+ await expect(
625
+ tool.execute(
626
+ "update",
627
+ { action: "update_task", id: "task-2", title: "Updated", assignee: "reviewer", status: "todo", dependsOn: [" task-1 ", "task-1"] },
628
+ undefined,
629
+ undefined,
630
+ {} as never,
631
+ ),
632
+ ).resolves.toMatchObject({
633
+ details: { item: { id: "task-2", title: "Updated", assignee: "reviewer", status: "blocked", dependsOn: ["task-1"] } },
634
+ });
635
+ expect(entries).toHaveLength(3);
636
+ });
637
+
638
+ test("rejects invalid agent team actions without persisting partial state", async () => {
639
+ const context = new Context();
640
+ contexts.push(context);
641
+ const entries: unknown[] = [];
642
+ context.provide("piSession", {
643
+ manager: {
644
+ getEntries: () => entries,
645
+ getBranch: () => entries,
646
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
647
+ },
648
+ } as never);
649
+ const tools = new PiToolRegistry();
650
+ context.provide("piTools", tools);
651
+ context.provide("piPluginUi", new PiPluginUiRegistry());
652
+ await context.plugin(agentTeamsPlugin);
653
+ const tool = namedTool(tools, "team_task");
654
+
655
+ await expect(tool.execute("missing-title", { action: "add_task" }, undefined, undefined, {} as never)).rejects.toThrow(/task title/iu);
656
+ await expect(
657
+ tool.execute("self-dependency", { action: "add_task", id: "self", title: "Self", dependsOn: ["self"] }, undefined, undefined, {} as never),
658
+ ).rejects.toThrow(/depend on itself/iu);
659
+ await expect(
660
+ tool.execute("missing-dependency", { action: "add_task", title: "Missing dependency", dependsOn: ["missing"] }, undefined, undefined, {} as never),
661
+ ).rejects.toThrow(/dependencies must already exist/iu);
662
+ await expect(tool.execute("missing-task", { action: "update_task", id: "missing" }, undefined, undefined, {} as never)).rejects.toThrow(/task not found/iu);
663
+ await expect(tool.execute("no-ready-task", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).rejects.toThrow(/no ready/iu);
664
+ await expect(tool.execute("missing-name", { action: "add_member", id: "missing-name" }, undefined, undefined, {} as never)).rejects.toThrow(
665
+ /member name/iu,
666
+ );
667
+ await expect(
668
+ tool.execute("duplicate-member", { action: "add_member", id: "planner", name: "Duplicate" }, undefined, undefined, {} as never),
669
+ ).rejects.toThrow(/member already exists/iu);
670
+ await expect(
671
+ tool.execute("unknown-sender", { action: "send_message", from: "missing", to: "planner", body: "Hello" }, undefined, undefined, {} as never),
672
+ ).rejects.toThrow(/unknown sender/iu);
673
+ await expect(
674
+ tool.execute("unknown-recipient", { action: "send_message", from: "planner", to: "missing", body: "Hello" }, undefined, undefined, {} as never),
675
+ ).rejects.toThrow(/unknown recipient/iu);
676
+ await expect(
677
+ tool.execute("oversized-message", { action: "send_message", from: "planner", to: "builder", body: "x".repeat(4_001) }, undefined, undefined, {} as never),
678
+ ).rejects.toThrow(/4000 characters/iu);
679
+ await expect(tool.execute("unknown-mailbox", { action: "read_messages", to: "missing" }, undefined, undefined, {} as never)).rejects.toThrow(
680
+ /unknown recipient/iu,
681
+ );
682
+ await expect(tool.execute("unknown-action", { action: "archive_task" }, undefined, undefined, {} as never)).rejects.toThrow(/unknown team action/iu);
683
+ expect(entries).toEqual([]);
684
+ });
685
+
686
+ test("unregisters the agent team tool and panel when the plugin context is disposed", async () => {
687
+ const context = new Context();
688
+ contexts.push(context);
689
+ context.provide("piSession", { manager: { getEntries: () => [], getBranch: () => [], appendCustomEntry() {} } } as never);
690
+ const tools = new PiToolRegistry();
691
+ const panels = new PiPluginUiRegistry();
692
+ context.provide("piTools", tools);
693
+ context.provide("piPluginUi", panels);
694
+ await context.plugin(agentTeamsPlugin);
695
+ expect(tools.snapshot().customTools.map((tool) => tool.name)).toEqual(["team_task"]);
696
+ await expect(panels.snapshot()).resolves.toMatchObject([{ id: "agent-teams-panel" }]);
697
+
698
+ await context.fiber.dispose();
699
+
700
+ expect(tools.snapshot().customTools).toEqual([]);
701
+ await expect(panels.snapshot()).resolves.toEqual([]);
702
+ });
703
+
704
+ test("recovers agent team operations from malformed persisted members", async () => {
705
+ const context = new Context();
706
+ contexts.push(context);
707
+ const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: { members: [null], tasks: [], messages: [] } }];
708
+ context.provide("piSession", {
709
+ manager: {
710
+ getEntries: () => entries,
711
+ getBranch: () => entries,
712
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
713
+ },
714
+ } as never);
715
+ const tools = new PiToolRegistry();
716
+ const panels = new PiPluginUiRegistry();
717
+ context.provide("piTools", tools);
718
+ context.provide("piPluginUi", panels);
719
+ await context.plugin(agentTeamsPlugin);
720
+
721
+ await expect(
722
+ namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "Recover safely" }, undefined, undefined, {} as never),
723
+ ).resolves.toMatchObject({
724
+ details: { item: { id: "task-1", status: "todo" } },
725
+ });
726
+ await expect(panels.snapshot()).resolves.toMatchObject([
727
+ { id: "agent-teams-panel", data: { members: [{ id: "planner" }, { id: "builder" }, { id: "reviewer" }] } },
728
+ ]);
729
+ });
730
+
731
+ test("restores the default role for a persisted agent team member", async () => {
732
+ const context = new Context();
733
+ contexts.push(context);
734
+ const entries: unknown[] = [
735
+ { type: "custom", customType: "pi-harness/agent-teams", data: { members: [{ id: "observer", name: "Observer" }], tasks: [], messages: [] } },
736
+ ];
737
+ context.provide("piSession", {
738
+ manager: {
739
+ getEntries: () => entries,
740
+ getBranch: () => entries,
741
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
742
+ },
743
+ } as never);
744
+ const panels = new PiPluginUiRegistry();
745
+ context.provide("piTools", new PiToolRegistry());
746
+ context.provide("piPluginUi", panels);
747
+ await context.plugin(agentTeamsPlugin);
748
+
749
+ await expect(panels.snapshot()).resolves.toMatchObject([{ data: { members: [{ id: "observer", name: "Observer", role: "协作成员", status: "idle" }] } }]);
750
+ });
751
+
752
+ test("recovers agent team state from a malformed persisted root value", async () => {
753
+ const context = new Context();
754
+ contexts.push(context);
755
+ const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: null }];
756
+ context.provide("piSession", {
757
+ manager: {
758
+ getEntries: () => entries,
759
+ getBranch: () => entries,
760
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
761
+ },
762
+ } as never);
763
+ const panels = new PiPluginUiRegistry();
764
+ context.provide("piTools", new PiToolRegistry());
765
+ context.provide("piPluginUi", panels);
766
+ await context.plugin(agentTeamsPlugin);
767
+
768
+ await expect(panels.snapshot()).resolves.toMatchObject([
769
+ { data: { members: [{ id: "planner" }, { id: "builder" }, { id: "reviewer" }], tasks: [], messages: [] } },
770
+ ]);
771
+ });
772
+
773
+ test("falls back to the latest valid agent team snapshot when the newest root is malformed", async () => {
774
+ const context = new Context();
775
+ contexts.push(context);
776
+ const entries: unknown[] = [
777
+ {
778
+ type: "custom",
779
+ customType: "pi-harness/agent-teams",
780
+ data: {
781
+ members: [{ id: "observer", name: "Observer", role: "Observe", status: "idle" }],
782
+ tasks: [{ id: "task-1", title: "Preserved", assignee: "observer", status: "todo", dependsOn: [] }],
783
+ messages: [],
784
+ },
785
+ },
786
+ { type: "custom", customType: "pi-harness/agent-teams", data: null },
787
+ ];
788
+ context.provide("piSession", {
789
+ manager: {
790
+ getEntries: () => entries,
791
+ getBranch: () => entries,
792
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
793
+ },
794
+ } as never);
795
+ const panels = new PiPluginUiRegistry();
796
+ context.provide("piTools", new PiToolRegistry());
797
+ context.provide("piPluginUi", panels);
798
+ await context.plugin(agentTeamsPlugin);
799
+
800
+ await expect(panels.snapshot()).resolves.toMatchObject([{ data: { members: [{ id: "observer" }], tasks: [{ id: "task-1", title: "Preserved" }] } }]);
801
+ });
802
+
803
+ test("deduplicates agent team identities loaded from persisted session state", async () => {
804
+ const context = new Context();
805
+ contexts.push(context);
806
+ const entries: unknown[] = [
807
+ {
808
+ type: "custom",
809
+ customType: "pi-harness/agent-teams",
810
+ data: {
811
+ members: [
812
+ { id: "builder", name: "Builder", role: "Build", status: "idle" },
813
+ { id: "builder", name: "Duplicate", role: "Duplicate", status: "working" },
814
+ ],
815
+ tasks: [
816
+ { id: "task-1", title: "First", assignee: "builder", status: "todo", dependsOn: [] },
817
+ { id: "task-1", title: "Duplicate", assignee: "builder", status: "done", dependsOn: [] },
818
+ ],
819
+ messages: [
820
+ { id: "message-1", from: "builder", to: "builder", body: "First", timestamp: "2026-09-05T00:00:00.000Z", read: false },
821
+ { id: "message-1", from: "builder", to: "builder", body: "Duplicate", timestamp: "2026-09-05T00:00:01.000Z", read: false },
822
+ ],
823
+ },
824
+ },
825
+ ];
826
+ context.provide("piSession", {
827
+ manager: {
828
+ getEntries: () => entries,
829
+ getBranch: () => entries,
830
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
831
+ },
832
+ } as never);
833
+ const panels = new PiPluginUiRegistry();
834
+ context.provide("piTools", new PiToolRegistry());
835
+ context.provide("piPluginUi", panels);
836
+ await context.plugin(agentTeamsPlugin);
837
+
838
+ await expect(panels.snapshot()).resolves.toMatchObject([
839
+ {
840
+ data: {
841
+ members: [{ id: "builder", name: "Builder" }],
842
+ tasks: [{ id: "task-1", title: "First" }],
843
+ messages: [{ id: "message-1", body: "First" }],
844
+ },
845
+ },
846
+ ]);
847
+ });
848
+
849
+ test("repairs dangling agent team references loaded from persisted session state", async () => {
850
+ const context = new Context();
851
+ contexts.push(context);
852
+ const entries: unknown[] = [
853
+ {
854
+ type: "custom",
855
+ customType: "pi-harness/agent-teams",
856
+ data: {
857
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
858
+ tasks: [{ id: "task-1", title: "Recovered", assignee: "missing-member", status: "todo", dependsOn: [] }],
859
+ messages: [
860
+ { id: "message-1", from: "missing-member", to: "builder", body: "Ghost", timestamp: "2026-09-05T00:00:00.000Z", read: false },
861
+ { id: "message-2", from: "builder", to: "builder", body: "Keep", timestamp: "2026-09-05T00:00:01.000Z", read: false },
862
+ ],
863
+ },
864
+ },
865
+ ];
866
+ context.provide("piSession", {
867
+ manager: {
868
+ getEntries: () => entries,
869
+ getBranch: () => entries,
870
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
871
+ },
872
+ } as never);
873
+ const panels = new PiPluginUiRegistry();
874
+ context.provide("piTools", new PiToolRegistry());
875
+ context.provide("piPluginUi", panels);
876
+ await context.plugin(agentTeamsPlugin);
877
+
878
+ await expect(panels.snapshot()).resolves.toMatchObject([
879
+ {
880
+ data: {
881
+ tasks: [{ id: "task-1", assignee: "unassigned" }],
882
+ messages: [{ id: "message-2", from: "builder", to: "builder", body: "Keep" }],
883
+ },
884
+ },
885
+ ]);
886
+ });
887
+
888
+ test("requeues an in-progress agent team task whose persisted assignee is missing", async () => {
889
+ const context = new Context();
890
+ contexts.push(context);
891
+ const entries: unknown[] = [
892
+ {
893
+ type: "custom",
894
+ customType: "pi-harness/agent-teams",
895
+ data: {
896
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
897
+ tasks: [{ id: "task-1", title: "Recover", assignee: "missing-member", status: "in_progress", dependsOn: [] }],
898
+ messages: [],
899
+ },
900
+ },
901
+ ];
902
+ context.provide("piSession", {
903
+ manager: {
904
+ getEntries: () => entries,
905
+ getBranch: () => entries,
906
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
907
+ },
908
+ } as never);
909
+ const panels = new PiPluginUiRegistry();
910
+ context.provide("piTools", new PiToolRegistry());
911
+ context.provide("piPluginUi", panels);
912
+ await context.plugin(agentTeamsPlugin);
913
+
914
+ await expect(panels.snapshot()).resolves.toMatchObject([{ data: { tasks: [{ id: "task-1", assignee: "unassigned", status: "todo" }] } }]);
915
+ });
916
+
917
+ test("reconciles agent team member statuses with persisted in-progress tasks", async () => {
918
+ const context = new Context();
919
+ contexts.push(context);
920
+ const entries: unknown[] = [
921
+ {
922
+ type: "custom",
923
+ customType: "pi-harness/agent-teams",
924
+ data: {
925
+ members: [
926
+ { id: "builder", name: "Builder", role: "Build", status: "working" },
927
+ { id: "reviewer", name: "Reviewer", role: "Review", status: "idle" },
928
+ ],
929
+ tasks: [{ id: "task-1", title: "Review", assignee: "reviewer", status: "in_progress", dependsOn: [] }],
930
+ messages: [],
931
+ },
932
+ },
933
+ ];
934
+ context.provide("piSession", {
935
+ manager: {
936
+ getEntries: () => entries,
937
+ getBranch: () => entries,
938
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
939
+ },
940
+ } as never);
941
+ const panels = new PiPluginUiRegistry();
942
+ context.provide("piTools", new PiToolRegistry());
943
+ context.provide("piPluginUi", panels);
944
+ await context.plugin(agentTeamsPlugin);
945
+
946
+ await expect(panels.snapshot()).resolves.toMatchObject([
947
+ {
948
+ data: {
949
+ members: [
950
+ { id: "builder", status: "idle" },
951
+ { id: "reviewer", status: "working" },
952
+ ],
953
+ },
954
+ },
955
+ ]);
956
+ });
957
+
958
+ test("normalizes whitespace around persisted agent team statuses", async () => {
959
+ const context = new Context();
960
+ contexts.push(context);
961
+ const entries: unknown[] = [
962
+ {
963
+ type: "custom",
964
+ customType: "pi-harness/agent-teams",
965
+ data: {
966
+ members: [{ id: "builder", name: "Builder", role: "Build", status: " working " }],
967
+ tasks: [{ id: "task-1", title: "Active", assignee: "builder", status: " in_progress ", dependsOn: [] }],
968
+ messages: [],
969
+ },
970
+ },
971
+ ];
972
+ context.provide("piSession", {
973
+ manager: {
974
+ getEntries: () => entries,
975
+ getBranch: () => entries,
976
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
977
+ },
978
+ } as never);
979
+ const panels = new PiPluginUiRegistry();
980
+ context.provide("piTools", new PiToolRegistry());
981
+ context.provide("piPluginUi", panels);
982
+ await context.plugin(agentTeamsPlugin);
983
+
984
+ await expect(panels.snapshot()).resolves.toMatchObject([
985
+ { data: { members: [{ id: "builder", status: "working" }], tasks: [{ id: "task-1", status: "in_progress" }] } },
986
+ ]);
987
+ });
988
+
989
+ test("reconciles persisted agent team task readiness before rendering state", async () => {
990
+ const context = new Context();
991
+ contexts.push(context);
992
+ const entries: unknown[] = [
993
+ {
994
+ type: "custom",
995
+ customType: "pi-harness/agent-teams",
996
+ data: {
997
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
998
+ tasks: [
999
+ { id: "waiting", title: "Waiting", assignee: "builder", status: "todo", dependsOn: ["dependency"] },
1000
+ { id: "ready", title: "Ready", assignee: "builder", status: "blocked", dependsOn: [] },
1001
+ ],
1002
+ messages: [],
1003
+ },
1004
+ },
1005
+ ];
1006
+ context.provide("piSession", {
1007
+ manager: {
1008
+ getEntries: () => entries,
1009
+ getBranch: () => entries,
1010
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1011
+ },
1012
+ } as never);
1013
+ const panels = new PiPluginUiRegistry();
1014
+ context.provide("piTools", new PiToolRegistry());
1015
+ context.provide("piPluginUi", panels);
1016
+ await context.plugin(agentTeamsPlugin);
1017
+
1018
+ await expect(panels.snapshot()).resolves.toMatchObject([
1019
+ {
1020
+ data: {
1021
+ tasks: [
1022
+ { id: "waiting", status: "blocked" },
1023
+ { id: "ready", status: "todo" },
1024
+ ],
1025
+ readyTasks: ["ready"],
1026
+ },
1027
+ },
1028
+ ]);
1029
+ });
1030
+
1031
+ test("reopens a persisted completed agent team task whose dependency is incomplete", async () => {
1032
+ const context = new Context();
1033
+ contexts.push(context);
1034
+ const entries: unknown[] = [
1035
+ {
1036
+ type: "custom",
1037
+ customType: "pi-harness/agent-teams",
1038
+ data: {
1039
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
1040
+ tasks: [
1041
+ { id: "dependency", title: "Dependency", assignee: "builder", status: "todo", dependsOn: [] },
1042
+ { id: "dependent", title: "Dependent", assignee: "builder", status: "done", dependsOn: ["dependency"] },
1043
+ ],
1044
+ messages: [],
1045
+ },
1046
+ },
1047
+ ];
1048
+ context.provide("piSession", {
1049
+ manager: {
1050
+ getEntries: () => entries,
1051
+ getBranch: () => entries,
1052
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1053
+ },
1054
+ } as never);
1055
+ const panels = new PiPluginUiRegistry();
1056
+ context.provide("piTools", new PiToolRegistry());
1057
+ context.provide("piPluginUi", panels);
1058
+ await context.plugin(agentTeamsPlugin);
1059
+
1060
+ await expect(panels.snapshot()).resolves.toMatchObject([
1061
+ {
1062
+ data: {
1063
+ tasks: [
1064
+ { id: "dependency", status: "todo" },
1065
+ { id: "dependent", status: "blocked" },
1066
+ ],
1067
+ },
1068
+ },
1069
+ ]);
1070
+ });
1071
+
1072
+ test("bounds agent team text loaded from persisted session state", async () => {
1073
+ const context = new Context();
1074
+ contexts.push(context);
1075
+ const entries: unknown[] = [
1076
+ {
1077
+ type: "custom",
1078
+ customType: "pi-harness/agent-teams",
1079
+ data: {
1080
+ members: [{ id: "builder", name: ` ${"n".repeat(201)} `, role: ` ${"r".repeat(201)} `, status: "idle" }],
1081
+ tasks: [{ id: "task-1", title: ` ${"t".repeat(201)} `, assignee: "builder", status: "todo", dependsOn: [] }],
1082
+ messages: [{ id: "message-1", from: "builder", to: "builder", body: ` ${"b".repeat(4_001)} `, timestamp: "2026-09-05T00:00:00.000Z", read: false }],
1083
+ },
1084
+ },
1085
+ ];
1086
+ context.provide("piSession", {
1087
+ manager: {
1088
+ getEntries: () => entries,
1089
+ getBranch: () => entries,
1090
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1091
+ },
1092
+ } as never);
1093
+ const panels = new PiPluginUiRegistry();
1094
+ context.provide("piTools", new PiToolRegistry());
1095
+ context.provide("piPluginUi", panels);
1096
+ await context.plugin(agentTeamsPlugin);
1097
+
1098
+ const panel = (await panels.snapshot())[0];
1099
+ if (panel === undefined) throw new Error("agent-teams-panel was not registered");
1100
+ const state = panel.data as { members: Array<{ name: string; role: string }>; tasks: Array<{ title: string }>; messages: Array<{ body: string }> };
1101
+ expect(state.members[0]).toMatchObject({ name: "n".repeat(200), role: "r".repeat(200) });
1102
+ expect(state.tasks[0]?.title).toBe("t".repeat(200));
1103
+ expect(state.messages[0]?.body).toBe("b".repeat(4_000));
1104
+ });
1105
+
1106
+ test("discards invalid agent team ids loaded from persisted session state", async () => {
1107
+ const context = new Context();
1108
+ contexts.push(context);
1109
+ const entries: unknown[] = [
1110
+ {
1111
+ type: "custom",
1112
+ customType: "pi-harness/agent-teams",
1113
+ data: {
1114
+ members: [
1115
+ { id: "builder", name: "Builder", role: "Build", status: "idle" },
1116
+ { id: "bad member", name: "Invalid", role: "Invalid", status: "idle" },
1117
+ ],
1118
+ tasks: [
1119
+ { id: "task-1", title: "Keep", assignee: "builder", status: "todo", dependsOn: [] },
1120
+ { id: "t".repeat(65), title: "Invalid", assignee: "builder", status: "todo", dependsOn: [] },
1121
+ ],
1122
+ messages: [
1123
+ { id: "message-1", from: "builder", to: "builder", body: "Keep", timestamp: "2026-09-05T00:00:00.000Z", read: false },
1124
+ { id: "bad message", from: "builder", to: "builder", body: "Invalid", timestamp: "2026-09-05T00:00:01.000Z", read: false },
1125
+ ],
1126
+ },
1127
+ },
1128
+ ];
1129
+ context.provide("piSession", {
1130
+ manager: {
1131
+ getEntries: () => entries,
1132
+ getBranch: () => entries,
1133
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1134
+ },
1135
+ } as never);
1136
+ const panels = new PiPluginUiRegistry();
1137
+ context.provide("piTools", new PiToolRegistry());
1138
+ context.provide("piPluginUi", panels);
1139
+ await context.plugin(agentTeamsPlugin);
1140
+
1141
+ await expect(panels.snapshot()).resolves.toMatchObject([
1142
+ {
1143
+ data: {
1144
+ members: [{ id: "builder" }],
1145
+ tasks: [{ id: "task-1" }],
1146
+ messages: [{ id: "message-1" }],
1147
+ },
1148
+ },
1149
+ ]);
1150
+ });
1151
+
1152
+ test("bounds and sanitizes agent team dependencies loaded from persisted session state", async () => {
1153
+ const context = new Context();
1154
+ contexts.push(context);
1155
+ const dependencies = [" bad reference ", ...Array.from({ length: 257 }, (_, index) => `dependency-${index}`), "dependency-0"];
1156
+ const entries: unknown[] = [
1157
+ {
1158
+ type: "custom",
1159
+ customType: "pi-harness/agent-teams",
1160
+ data: {
1161
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
1162
+ tasks: [{ id: "task-1", title: "Recovered", assignee: "builder", status: "todo", dependsOn: dependencies }],
1163
+ messages: [],
1164
+ },
1165
+ },
1166
+ ];
1167
+ context.provide("piSession", {
1168
+ manager: {
1169
+ getEntries: () => entries,
1170
+ getBranch: () => entries,
1171
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1172
+ },
1173
+ } as never);
1174
+ const panels = new PiPluginUiRegistry();
1175
+ context.provide("piTools", new PiToolRegistry());
1176
+ context.provide("piPluginUi", panels);
1177
+ await context.plugin(agentTeamsPlugin);
1178
+
1179
+ const panel = (await panels.snapshot())[0];
1180
+ if (panel === undefined) throw new Error("agent-teams-panel was not registered");
1181
+ const [task] = (panel.data as { tasks: Array<{ dependsOn: string[] }> }).tasks;
1182
+ expect(task?.dependsOn).toHaveLength(255);
1183
+ expect(task?.dependsOn[0]).toBe("dependency-0");
1184
+ expect(task?.dependsOn.at(-1)).toBe("dependency-254");
1185
+ });
1186
+
1187
+ test("allocates a unique agent team task id after loading sparse persisted ids", async () => {
1188
+ const context = new Context();
1189
+ contexts.push(context);
1190
+ const entries: unknown[] = [
1191
+ {
1192
+ type: "custom",
1193
+ customType: "pi-harness/agent-teams",
1194
+ data: {
1195
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
1196
+ tasks: [{ id: "task-2", title: "Existing", assignee: "builder", status: "todo", dependsOn: [] }],
1197
+ messages: [],
1198
+ },
1199
+ },
1200
+ ];
1201
+ context.provide("piSession", {
1202
+ manager: {
1203
+ getEntries: () => entries,
1204
+ getBranch: () => entries,
1205
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1206
+ },
1207
+ } as never);
1208
+ const tools = new PiToolRegistry();
1209
+ context.provide("piTools", tools);
1210
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1211
+ await context.plugin(agentTeamsPlugin);
1212
+
1213
+ await expect(
1214
+ namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "Recovered append" }, undefined, undefined, {} as never),
1215
+ ).resolves.toMatchObject({
1216
+ details: { item: { id: "task-1" } },
1217
+ });
1218
+ });
1219
+
1220
+ test("allocates a unique agent team message id after loading sparse persisted ids", async () => {
1221
+ const context = new Context();
1222
+ contexts.push(context);
1223
+ const entries: unknown[] = [
1224
+ {
1225
+ type: "custom",
1226
+ customType: "pi-harness/agent-teams",
1227
+ data: {
1228
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
1229
+ tasks: [],
1230
+ messages: [{ id: "message-2", from: "builder", to: "builder", body: "Existing", timestamp: "2026-09-05T00:00:00.000Z", read: false }],
1231
+ },
1232
+ },
1233
+ ];
1234
+ context.provide("piSession", {
1235
+ manager: {
1236
+ getEntries: () => entries,
1237
+ getBranch: () => entries,
1238
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1239
+ },
1240
+ } as never);
1241
+ const tools = new PiToolRegistry();
1242
+ context.provide("piTools", tools);
1243
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1244
+ await context.plugin(agentTeamsPlugin);
1245
+
1246
+ await expect(
1247
+ namedTool(tools, "team_task").execute(
1248
+ "call-1",
1249
+ { action: "send_message", from: "builder", to: "builder", body: "Recovered append" },
1250
+ undefined,
1251
+ undefined,
1252
+ {} as never,
1253
+ ),
1254
+ ).resolves.toMatchObject({ details: { item: { id: "message-1" } } });
1255
+ });
1256
+
1257
+ test("normalizes invalid agent team message timestamps loaded from persisted state", async () => {
1258
+ const context = new Context();
1259
+ contexts.push(context);
1260
+ const entries: unknown[] = [
1261
+ {
1262
+ type: "custom",
1263
+ customType: "pi-harness/agent-teams",
1264
+ data: {
1265
+ members: [{ id: "builder", name: "Builder", role: "Build", status: "idle" }],
1266
+ tasks: [],
1267
+ messages: [{ id: "message-1", from: "builder", to: "builder", body: "Recovered", timestamp: "not-a-date", read: false }],
1268
+ },
1269
+ },
1270
+ ];
1271
+ context.provide("piSession", {
1272
+ manager: {
1273
+ getEntries: () => entries,
1274
+ getBranch: () => entries,
1275
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1276
+ },
1277
+ } as never);
1278
+ const panels = new PiPluginUiRegistry();
1279
+ context.provide("piTools", new PiToolRegistry());
1280
+ context.provide("piPluginUi", panels);
1281
+ await context.plugin(agentTeamsPlugin);
1282
+
1283
+ await expect(panels.snapshot()).resolves.toMatchObject([{ data: { messages: [{ timestamp: "1970-01-01T00:00:00.000Z" }] } }]);
1284
+ });
1285
+
1286
+ test("rejects an unknown assignee when creating an agent team task", async () => {
1287
+ const context = new Context();
1288
+ contexts.push(context);
1289
+ const entries: unknown[] = [];
1290
+ context.provide("piSession", {
1291
+ manager: {
1292
+ getEntries: () => entries,
1293
+ getBranch: () => entries,
1294
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1295
+ },
1296
+ } as never);
1297
+ const tools = new PiToolRegistry();
1298
+ context.provide("piTools", tools);
1299
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1300
+ await context.plugin(agentTeamsPlugin);
1301
+
1302
+ await expect(
1303
+ namedTool(tools, "team_task").execute(
1304
+ "call-1",
1305
+ { action: "add_task", title: "Unowned work", assignee: "missing-member" },
1306
+ undefined,
1307
+ undefined,
1308
+ {} as never,
1309
+ ),
1310
+ ).rejects.toThrow(/unknown assignee/iu);
1311
+ expect(entries).toEqual([]);
1312
+ });
1313
+
1314
+ test("rejects an unknown assignee when updating an agent team task", async () => {
1315
+ const context = new Context();
1316
+ contexts.push(context);
1317
+ const entries: unknown[] = [];
1318
+ context.provide("piSession", {
1319
+ manager: {
1320
+ getEntries: () => entries,
1321
+ getBranch: () => entries,
1322
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1323
+ },
1324
+ } as never);
1325
+ const tools = new PiToolRegistry();
1326
+ context.provide("piTools", tools);
1327
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1328
+ await context.plugin(agentTeamsPlugin);
1329
+ const tool = namedTool(tools, "team_task");
1330
+ await tool.execute("setup", { action: "add_task", title: "Owned work" }, undefined, undefined, {} as never);
1331
+
1332
+ await expect(
1333
+ tool.execute("call-1", { action: "update_task", id: "task-1", assignee: "missing-member" }, undefined, undefined, {} as never),
1334
+ ).rejects.toThrow(/unknown assignee/iu);
1335
+ expect(entries).toHaveLength(1);
1336
+ });
1337
+
1338
+ test("rejects an unknown assignee when claiming an agent team task", async () => {
1339
+ const context = new Context();
1340
+ contexts.push(context);
1341
+ const entries: unknown[] = [];
1342
+ context.provide("piSession", {
1343
+ manager: {
1344
+ getEntries: () => entries,
1345
+ getBranch: () => entries,
1346
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1347
+ },
1348
+ } as never);
1349
+ const tools = new PiToolRegistry();
1350
+ context.provide("piTools", tools);
1351
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1352
+ await context.plugin(agentTeamsPlugin);
1353
+ const tool = namedTool(tools, "team_task");
1354
+ await tool.execute("setup", { action: "add_task", title: "Claimable work" }, undefined, undefined, {} as never);
1355
+
1356
+ await expect(tool.execute("call-1", { action: "claim_task", assignee: "missing-member" }, undefined, undefined, {} as never)).rejects.toThrow(
1357
+ /unknown assignee/iu,
1358
+ );
1359
+ expect(entries).toHaveLength(1);
1360
+ });
1361
+
1362
+ test("rejects an oversized agent team task id", async () => {
1363
+ const context = new Context();
1364
+ contexts.push(context);
1365
+ const entries: unknown[] = [];
1366
+ context.provide("piSession", {
1367
+ manager: {
1368
+ getEntries: () => entries,
1369
+ getBranch: () => entries,
1370
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1371
+ },
1372
+ } as never);
1373
+ const tools = new PiToolRegistry();
1374
+ context.provide("piTools", tools);
1375
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1376
+ await context.plugin(agentTeamsPlugin);
1377
+
1378
+ await expect(
1379
+ namedTool(tools, "team_task").execute(
1380
+ "call-1",
1381
+ { action: "add_task", id: "t".repeat(65), title: "Bounded identifier" },
1382
+ undefined,
1383
+ undefined,
1384
+ {} as never,
1385
+ ),
1386
+ ).rejects.toThrow(/task id.*1-64/iu);
1387
+ expect(entries).toEqual([]);
1388
+ });
1389
+
1390
+ test("rejects a completed agent team task whose dependencies are incomplete", async () => {
1391
+ const context = new Context();
1392
+ contexts.push(context);
1393
+ const entries: unknown[] = [];
1394
+ context.provide("piSession", {
1395
+ manager: {
1396
+ getEntries: () => entries,
1397
+ getBranch: () => entries,
1398
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1399
+ },
1400
+ } as never);
1401
+ const tools = new PiToolRegistry();
1402
+ context.provide("piTools", tools);
1403
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1404
+ await context.plugin(agentTeamsPlugin);
1405
+ const tool = namedTool(tools, "team_task");
1406
+ await tool.execute("setup", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
1407
+
1408
+ await expect(
1409
+ tool.execute("call-1", { action: "add_task", title: "Premature completion", status: "done", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
1410
+ ).rejects.toThrow(/cannot be completed before its dependencies/iu);
1411
+ expect(entries).toHaveLength(1);
1412
+ });
1413
+
1414
+ test("rejects an in-progress agent team task without a member assignee", async () => {
1415
+ const context = new Context();
1416
+ contexts.push(context);
1417
+ const entries: unknown[] = [];
1418
+ context.provide("piSession", {
1419
+ manager: {
1420
+ getEntries: () => entries,
1421
+ getBranch: () => entries,
1422
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1423
+ },
1424
+ } as never);
1425
+ const tools = new PiToolRegistry();
1426
+ context.provide("piTools", tools);
1427
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1428
+ await context.plugin(agentTeamsPlugin);
1429
+
1430
+ await expect(
1431
+ namedTool(tools, "team_task").execute(
1432
+ "call-1",
1433
+ { action: "add_task", title: "Unowned active work", status: "in_progress" },
1434
+ undefined,
1435
+ undefined,
1436
+ {} as never,
1437
+ ),
1438
+ ).rejects.toThrow(/in-progress.*assignee/iu);
1439
+ expect(entries).toEqual([]);
1440
+ });
1441
+
1442
+ test("rejects an in-progress agent team task whose dependencies are incomplete", async () => {
1443
+ const context = new Context();
1444
+ contexts.push(context);
1445
+ const entries: unknown[] = [];
1446
+ context.provide("piSession", {
1447
+ manager: {
1448
+ getEntries: () => entries,
1449
+ getBranch: () => entries,
1450
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1451
+ },
1452
+ } as never);
1453
+ const tools = new PiToolRegistry();
1454
+ context.provide("piTools", tools);
1455
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1456
+ await context.plugin(agentTeamsPlugin);
1457
+ const tool = namedTool(tools, "team_task");
1458
+ await tool.execute("setup", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
1459
+
1460
+ await expect(
1461
+ tool.execute(
1462
+ "call-1",
1463
+ { action: "add_task", title: "Premature work", assignee: "builder", status: "in_progress", dependsOn: ["task-1"] },
1464
+ undefined,
1465
+ undefined,
1466
+ {} as never,
1467
+ ),
1468
+ ).rejects.toThrow(/cannot be started before its dependencies/iu);
1469
+ expect(entries).toHaveLength(1);
1470
+ });
1471
+
1472
+ test("rejects moving an unassigned agent team task into progress", async () => {
1473
+ const context = new Context();
1474
+ contexts.push(context);
1475
+ const entries: unknown[] = [];
1476
+ context.provide("piSession", {
1477
+ manager: {
1478
+ getEntries: () => entries,
1479
+ getBranch: () => entries,
1480
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1481
+ },
1482
+ } as never);
1483
+ const tools = new PiToolRegistry();
1484
+ context.provide("piTools", tools);
1485
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1486
+ await context.plugin(agentTeamsPlugin);
1487
+ const tool = namedTool(tools, "team_task");
1488
+ await tool.execute("setup", { action: "add_task", title: "Unassigned" }, undefined, undefined, {} as never);
1489
+
1490
+ await expect(tool.execute("call-1", { action: "update_task", id: "task-1", status: "in_progress" }, undefined, undefined, {} as never)).rejects.toThrow(
1491
+ /in-progress.*assignee/iu,
1492
+ );
1493
+ expect(entries).toHaveLength(1);
1494
+ });
1495
+
1496
+ test("rejects removing the assignee from an in-progress agent team task", async () => {
1497
+ const context = new Context();
1498
+ contexts.push(context);
1499
+ const entries: unknown[] = [];
1500
+ context.provide("piSession", {
1501
+ manager: {
1502
+ getEntries: () => entries,
1503
+ getBranch: () => entries,
1504
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1505
+ },
1506
+ } as never);
1507
+ const tools = new PiToolRegistry();
1508
+ context.provide("piTools", tools);
1509
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1510
+ await context.plugin(agentTeamsPlugin);
1511
+ const tool = namedTool(tools, "team_task");
1512
+ await tool.execute("setup", { action: "add_task", title: "Active", assignee: "builder", status: "in_progress" }, undefined, undefined, {} as never);
1513
+
1514
+ await expect(tool.execute("call-1", { action: "update_task", id: "task-1", assignee: "unassigned" }, undefined, undefined, {} as never)).rejects.toThrow(
1515
+ /in-progress.*assignee/iu,
1516
+ );
1517
+ expect(entries).toHaveLength(1);
1518
+ });
1519
+
1520
+ test("rejects starting an agent team task whose dependencies are incomplete", async () => {
1521
+ const context = new Context();
1522
+ contexts.push(context);
1523
+ const entries: unknown[] = [];
1524
+ context.provide("piSession", {
1525
+ manager: {
1526
+ getEntries: () => entries,
1527
+ getBranch: () => entries,
1528
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1529
+ },
1530
+ } as never);
1531
+ const tools = new PiToolRegistry();
1532
+ context.provide("piTools", tools);
1533
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1534
+ await context.plugin(agentTeamsPlugin);
1535
+ const tool = namedTool(tools, "team_task");
1536
+ await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
1537
+ await tool.execute("dependent", { action: "add_task", title: "Dependent", assignee: "builder", dependsOn: ["task-1"] }, undefined, undefined, {} as never);
1538
+
1539
+ await expect(tool.execute("call-1", { action: "update_task", id: "task-2", status: "in_progress" }, undefined, undefined, {} as never)).rejects.toThrow(
1540
+ /cannot be started before its dependencies/iu,
1541
+ );
1542
+ expect(entries).toHaveLength(2);
1543
+ });
1544
+
1545
+ test("rejects adding an incomplete dependency to an in-progress agent team task", async () => {
1546
+ const context = new Context();
1547
+ contexts.push(context);
1548
+ const entries: unknown[] = [];
1549
+ context.provide("piSession", {
1550
+ manager: {
1551
+ getEntries: () => entries,
1552
+ getBranch: () => entries,
1553
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1554
+ },
1555
+ } as never);
1556
+ const tools = new PiToolRegistry();
1557
+ context.provide("piTools", tools);
1558
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1559
+ await context.plugin(agentTeamsPlugin);
1560
+ const tool = namedTool(tools, "team_task");
1561
+ await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
1562
+ await tool.execute("active", { action: "add_task", title: "Active", assignee: "builder", status: "in_progress" }, undefined, undefined, {} as never);
1563
+
1564
+ await expect(tool.execute("call-1", { action: "update_task", id: "task-2", dependsOn: ["task-1"] }, undefined, undefined, {} as never)).rejects.toThrow(
1565
+ /cannot be started before its dependencies/iu,
1566
+ );
1567
+ expect(entries).toHaveLength(2);
1568
+ });
1569
+
1570
+ test("rejects adding an incomplete dependency to a completed agent team task", async () => {
1571
+ const context = new Context();
1572
+ contexts.push(context);
1573
+ const entries: unknown[] = [];
1574
+ context.provide("piSession", {
1575
+ manager: {
1576
+ getEntries: () => entries,
1577
+ getBranch: () => entries,
1578
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1579
+ },
1580
+ } as never);
1581
+ const tools = new PiToolRegistry();
1582
+ context.provide("piTools", tools);
1583
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1584
+ await context.plugin(agentTeamsPlugin);
1585
+ const tool = namedTool(tools, "team_task");
1586
+ await tool.execute("dependency", { action: "add_task", title: "Dependency" }, undefined, undefined, {} as never);
1587
+ await tool.execute("completed", { action: "add_task", title: "Completed", status: "done" }, undefined, undefined, {} as never);
1588
+
1589
+ await expect(tool.execute("call-1", { action: "update_task", id: "task-2", dependsOn: ["task-1"] }, undefined, undefined, {} as never)).rejects.toThrow(
1590
+ /cannot be completed before its dependencies/iu,
1591
+ );
1592
+ expect(entries).toHaveLength(2);
1593
+ });
1594
+
1595
+ test("preserves a preassigned member when claiming an agent team task without an assignee parameter", async () => {
1596
+ const context = new Context();
1597
+ contexts.push(context);
1598
+ const entries: unknown[] = [];
1599
+ context.provide("piSession", {
1600
+ manager: {
1601
+ getEntries: () => entries,
1602
+ getBranch: () => entries,
1603
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1604
+ },
1605
+ } as never);
1606
+ const tools = new PiToolRegistry();
1607
+ context.provide("piTools", tools);
1608
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1609
+ await context.plugin(agentTeamsPlugin);
1610
+ const tool = namedTool(tools, "team_task");
1611
+ await tool.execute("setup", { action: "add_task", title: "Assigned work", assignee: "builder" }, undefined, undefined, {} as never);
1612
+
1613
+ await expect(tool.execute("call-1", { action: "claim_task" }, undefined, undefined, {} as never)).resolves.toMatchObject({
1614
+ details: { item: { status: "in_progress", assignee: "builder" } },
1615
+ });
1616
+ });
1617
+
1618
+ test("requires a member when claiming an unassigned agent team task", async () => {
1619
+ const context = new Context();
1620
+ contexts.push(context);
1621
+ const entries: unknown[] = [];
1622
+ context.provide("piSession", {
1623
+ manager: {
1624
+ getEntries: () => entries,
1625
+ getBranch: () => entries,
1626
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1627
+ },
1628
+ } as never);
1629
+ const tools = new PiToolRegistry();
1630
+ context.provide("piTools", tools);
1631
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1632
+ await context.plugin(agentTeamsPlugin);
1633
+ const tool = namedTool(tools, "team_task");
1634
+ await tool.execute("setup", { action: "add_task", title: "Unassigned work" }, undefined, undefined, {} as never);
1635
+
1636
+ await expect(tool.execute("call-1", { action: "claim_task" }, undefined, undefined, {} as never)).rejects.toThrow(/assignee.*required/iu);
1637
+ expect(entries).toHaveLength(1);
1638
+ });
1639
+
1640
+ test("rejects an oversized agent team dependency list before resolving references", async () => {
1641
+ const context = new Context();
1642
+ contexts.push(context);
1643
+ const entries: unknown[] = [];
1644
+ context.provide("piSession", {
1645
+ manager: {
1646
+ getEntries: () => entries,
1647
+ getBranch: () => entries,
1648
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1649
+ },
1650
+ } as never);
1651
+ const tools = new PiToolRegistry();
1652
+ context.provide("piTools", tools);
1653
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1654
+ await context.plugin(agentTeamsPlugin);
1655
+
1656
+ await expect(
1657
+ namedTool(tools, "team_task").execute(
1658
+ "call-1",
1659
+ { action: "add_task", title: "Too connected", dependsOn: Array.from({ length: 257 }, (_, index) => `dependency-${index}`) },
1660
+ undefined,
1661
+ undefined,
1662
+ {} as never,
1663
+ ),
1664
+ ).rejects.toThrow(/at most 256 dependencies/iu);
1665
+ expect(entries).toEqual([]);
1666
+ });
1667
+
1668
+ test("rejects an oversized agent team dependency id before resolving references", async () => {
1669
+ const context = new Context();
1670
+ contexts.push(context);
1671
+ const entries: unknown[] = [];
1672
+ context.provide("piSession", {
1673
+ manager: {
1674
+ getEntries: () => entries,
1675
+ getBranch: () => entries,
1676
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1677
+ },
1678
+ } as never);
1679
+ const tools = new PiToolRegistry();
1680
+ context.provide("piTools", tools);
1681
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1682
+ await context.plugin(agentTeamsPlugin);
1683
+
1684
+ await expect(
1685
+ namedTool(tools, "team_task").execute(
1686
+ "call-1",
1687
+ { action: "add_task", title: "Invalid reference", dependsOn: ["d".repeat(65)] },
1688
+ undefined,
1689
+ undefined,
1690
+ {} as never,
1691
+ ),
1692
+ ).rejects.toThrow(/task dependency id.*1-64/iu);
1693
+ expect(entries).toEqual([]);
1694
+ });
1695
+
1696
+ test("rejects an oversized dependency list when updating an agent team task", async () => {
1697
+ const context = new Context();
1698
+ contexts.push(context);
1699
+ const entries: unknown[] = [];
1700
+ context.provide("piSession", {
1701
+ manager: {
1702
+ getEntries: () => entries,
1703
+ getBranch: () => entries,
1704
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1705
+ },
1706
+ } as never);
1707
+ const tools = new PiToolRegistry();
1708
+ context.provide("piTools", tools);
1709
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1710
+ await context.plugin(agentTeamsPlugin);
1711
+ const tool = namedTool(tools, "team_task");
1712
+ await tool.execute("setup", { action: "add_task", title: "Existing task" }, undefined, undefined, {} as never);
1713
+
1714
+ await expect(
1715
+ tool.execute(
1716
+ "call-1",
1717
+ { action: "update_task", id: "task-1", dependsOn: Array.from({ length: 257 }, (_, index) => `dependency-${index}`) },
1718
+ undefined,
1719
+ undefined,
1720
+ {} as never,
1721
+ ),
1722
+ ).rejects.toThrow(/at most 256 dependencies/iu);
1723
+ expect(entries).toHaveLength(1);
1724
+ });
1725
+
1726
+ test("rejects an oversized agent team member name", async () => {
1727
+ const context = new Context();
1728
+ contexts.push(context);
1729
+ const entries: unknown[] = [];
1730
+ context.provide("piSession", {
1731
+ manager: {
1732
+ getEntries: () => entries,
1733
+ getBranch: () => entries,
1734
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1735
+ },
1736
+ } as never);
1737
+ const tools = new PiToolRegistry();
1738
+ context.provide("piTools", tools);
1739
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1740
+ await context.plugin(agentTeamsPlugin);
1741
+
1742
+ await expect(
1743
+ namedTool(tools, "team_task").execute("call-1", { action: "add_member", id: "long-name", name: "n".repeat(201) }, undefined, undefined, {} as never),
1744
+ ).rejects.toThrow(/member name.*1-200/iu);
1745
+ expect(entries).toEqual([]);
1746
+ });
1747
+
1748
+ test("rejects an oversized agent team member role", async () => {
1749
+ const context = new Context();
1750
+ contexts.push(context);
1751
+ const entries: unknown[] = [];
1752
+ context.provide("piSession", {
1753
+ manager: {
1754
+ getEntries: () => entries,
1755
+ getBranch: () => entries,
1756
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1757
+ },
1758
+ } as never);
1759
+ const tools = new PiToolRegistry();
1760
+ context.provide("piTools", tools);
1761
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1762
+ await context.plugin(agentTeamsPlugin);
1763
+
1764
+ await expect(
1765
+ namedTool(tools, "team_task").execute(
1766
+ "call-1",
1767
+ { action: "add_member", id: "long-role", name: "Long role", role: "r".repeat(201) },
1768
+ undefined,
1769
+ undefined,
1770
+ {} as never,
1771
+ ),
1772
+ ).rejects.toThrow(/member role.*1-200/iu);
1773
+ expect(entries).toEqual([]);
378
1774
  });
379
1775
 
380
- test("publishes a live context insight panel from the session runtime", async () => {
1776
+ test("rejects a working agent team member without an in-progress task", async () => {
381
1777
  const context = new Context();
382
1778
  contexts.push(context);
383
- const panels = new PiPluginUiRegistry();
384
- const toolRegistry = new PiToolRegistry();
385
- context.provide("piRuntime", {
386
- session: { messages: [{ role: "user" }], getContextUsage: () => ({ tokens: 1200, contextWindow: 8000, percent: 15 }) },
1779
+ const entries: unknown[] = [];
1780
+ context.provide("piSession", {
1781
+ manager: {
1782
+ getEntries: () => entries,
1783
+ getBranch: () => entries,
1784
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1785
+ },
387
1786
  } as never);
388
- context.provide("piPluginUi", panels);
389
- context.provide("piTools", toolRegistry);
1787
+ const tools = new PiToolRegistry();
1788
+ context.provide("piTools", tools);
1789
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1790
+ await context.plugin(agentTeamsPlugin);
390
1791
 
391
- await context.plugin(contextPlugin);
1792
+ await expect(
1793
+ namedTool(tools, "team_task").execute("call-1", { action: "add_member", id: "busy", name: "Busy", status: "working" }, undefined, undefined, {} as never),
1794
+ ).rejects.toThrow(/working.*in-progress task/iu);
1795
+ expect(entries).toEqual([]);
1796
+ });
392
1797
 
393
- context.emit("pi/session-event", { type: "message_start" } as never);
394
- context.emit("pi/session-event", { type: "compaction_start" } as never);
395
- await expect(panels.snapshot()).resolves.toMatchObject([
1798
+ test("bounds the durable agent team task list before appending session state", async () => {
1799
+ const context = new Context();
1800
+ contexts.push(context);
1801
+ const tasks = Array.from({ length: 256 }, (_, index) => ({
1802
+ id: `task-${index + 1}`,
1803
+ title: `Task ${index + 1}`,
1804
+ assignee: "unassigned",
1805
+ status: "todo",
1806
+ dependsOn: [],
1807
+ }));
1808
+ const entries: unknown[] = [
396
1809
  {
397
- data: {
398
- events: 2,
399
- compactions: 1,
400
- eventTypes: { message_start: 1, compaction_start: 1 },
401
- recentEvents: [{ type: "message_start" }, { type: "compaction_start" }],
402
- },
1810
+ type: "custom",
1811
+ customType: "pi-harness/agent-teams",
1812
+ data: { members: [{ id: "planner", name: "Planner", role: "Plan", status: "idle" }], tasks, messages: [] },
403
1813
  },
404
- ]);
1814
+ ];
1815
+ context.provide("piSession", {
1816
+ manager: {
1817
+ getEntries: () => entries,
1818
+ getBranch: () => entries,
1819
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1820
+ },
1821
+ } as never);
1822
+ const tools = new PiToolRegistry();
1823
+ context.provide("piTools", tools);
1824
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1825
+ await context.plugin(agentTeamsPlugin);
405
1826
 
406
- await expect(panels.snapshot()).resolves.toEqual([
1827
+ await expect(
1828
+ namedTool(tools, "team_task").execute("call-1", { action: "add_task", title: "One too many" }, undefined, undefined, {} as never),
1829
+ ).rejects.toThrow(/at most 256 tasks/iu);
1830
+ expect(entries).toHaveLength(1);
1831
+ });
1832
+
1833
+ test("bounds the durable agent team mailbox before appending session state", async () => {
1834
+ const context = new Context();
1835
+ contexts.push(context);
1836
+ const messages = Array.from({ length: 1_000 }, (_, index) => ({
1837
+ id: `message-${index + 1}`,
1838
+ from: "builder",
1839
+ to: "reviewer",
1840
+ body: "done",
1841
+ timestamp: "2026-09-05T00:00:00.000Z",
1842
+ read: false,
1843
+ }));
1844
+ const entries: unknown[] = [
407
1845
  {
408
- id: "context-insight-panel",
409
- pluginId: "@pi-harness/core/plugins/context",
410
- title: "上下文洞察",
411
- description: "查看当前上下文占用、消息规模和压缩事件。",
412
- icon: "◒",
1846
+ type: "custom",
1847
+ customType: "pi-harness/agent-teams",
413
1848
  data: {
414
- tokens: 1200,
415
- contextWindow: 8000,
416
- percent: 15,
417
- messages: 1,
418
- events: 2,
419
- compactions: 1,
420
- composition: { user: 1, assistant: 0, toolResult: 0, system: 0, other: 0 },
421
- eventTypes: { message_start: 1, compaction_start: 1 },
422
- recentEvents: [expect.objectContaining({ type: "message_start" }), expect.objectContaining({ type: "compaction_start" })],
1849
+ members: [
1850
+ { id: "builder", name: "Builder", role: "Build", status: "idle" },
1851
+ { id: "reviewer", name: "Reviewer", role: "Review", status: "idle" },
1852
+ ],
1853
+ tasks: [],
1854
+ messages,
423
1855
  },
424
1856
  },
425
- ]);
1857
+ ];
1858
+ context.provide("piSession", {
1859
+ manager: {
1860
+ getEntries: () => entries,
1861
+ getBranch: () => entries,
1862
+ appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
1863
+ },
1864
+ } as never);
1865
+ const tools = new PiToolRegistry();
1866
+ context.provide("piTools", tools);
1867
+ context.provide("piPluginUi", new PiPluginUiRegistry());
1868
+ await context.plugin(agentTeamsPlugin);
426
1869
 
427
- const inspect = toolRegistry.snapshot().customTools.find((entry) => entry.name === "context_inspect");
428
- if (inspect === undefined) throw new Error("context_inspect was not registered");
429
- await expect(inspect.execute("inspect-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
430
- details: { composition: { user: 1 }, messages: 1 },
431
- });
1870
+ await expect(
1871
+ namedTool(tools, "team_task").execute(
1872
+ "call-1",
1873
+ { action: "send_message", from: "builder", to: "reviewer", body: "One too many" },
1874
+ undefined,
1875
+ undefined,
1876
+ {} as never,
1877
+ ),
1878
+ ).rejects.toThrow(/at most 1000 messages/iu);
1879
+ expect(entries).toHaveLength(1);
432
1880
  });
433
1881
 
434
- test("persists agent team tasks and exposes a live collaboration panel", async () => {
1882
+ test("bounds the durable agent team member list before appending session state", async () => {
435
1883
  const context = new Context();
436
1884
  contexts.push(context);
437
- const panels = new PiPluginUiRegistry();
438
- const tools = new PiToolRegistry();
439
- const entries: unknown[] = [];
1885
+ const members = Array.from({ length: 64 }, (_, index) => ({ id: `member-${index + 1}`, name: `Member ${index + 1}`, role: "Worker", status: "idle" }));
1886
+ const entries: unknown[] = [{ type: "custom", customType: "pi-harness/agent-teams", data: { members, tasks: [], messages: [] } }];
440
1887
  context.provide("piSession", {
441
1888
  manager: {
442
1889
  getEntries: () => entries,
1890
+ getBranch: () => entries,
443
1891
  appendCustomEntry: (_type: string, data: unknown) => entries.push({ type: "custom", customType: "pi-harness/agent-teams", data }),
444
1892
  },
445
1893
  } as never);
1894
+ const tools = new PiToolRegistry();
446
1895
  context.provide("piTools", tools);
447
- context.provide("piPluginUi", panels);
448
-
1896
+ context.provide("piPluginUi", new PiPluginUiRegistry());
449
1897
  await context.plugin(agentTeamsPlugin);
450
- const tool = firstTool(tools);
451
- await expect(tool.execute("call-1", { action: "add_task", title: "Review plugin manifest" }, undefined, undefined, {} as never)).resolves.toMatchObject({
452
- content: [{ text: "Task task-1 created." }],
453
- });
454
- await expect(
455
- tool.execute("call-2", { action: "add_task", title: "Run integration checks", dependsOn: ["task-1"] }, undefined, undefined, {} as never),
456
- ).resolves.toMatchObject({ details: { item: { id: "task-2", status: "blocked", dependsOn: ["task-1"] } } });
457
- await expect(tool.execute("call-3", { action: "update_task", id: "task-1", status: "done" }, undefined, undefined, {} as never)).resolves.toMatchObject({
458
- details: { item: { id: "task-1", status: "done" } },
459
- });
460
- await expect(panels.snapshot()).resolves.toMatchObject([
461
- { id: "agent-teams-panel", data: { tasks: [{ status: "done" }, { id: "task-2", status: "todo" }] } },
462
- ]);
463
- await expect(tool.execute("call-4", { action: "claim_task", assignee: "builder" }, undefined, undefined, {} as never)).resolves.toMatchObject({
464
- details: { item: { id: "task-2", status: "in_progress", assignee: "builder" } },
465
- });
466
- await expect(
467
- tool.execute("call-5", { action: "send_message", from: "builder", to: "reviewer", body: "实现已完成,请开始复核" }, undefined, undefined, {} as never),
468
- ).resolves.toMatchObject({ details: { item: { from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: false } } });
469
- await expect(tool.execute("call-6", { action: "read_messages", to: "reviewer" }, undefined, undefined, {} as never)).resolves.toMatchObject({
470
- details: { messages: [{ from: "builder", to: "reviewer", body: "实现已完成,请开始复核", read: true }] },
471
- });
1898
+
472
1899
  await expect(
473
- tool.execute("call-7", { action: "read_messages", to: "reviewer", unreadOnly: true }, undefined, undefined, {} as never),
474
- ).resolves.toMatchObject({
475
- details: { messages: [] },
476
- });
477
- await expect(panels.snapshot()).resolves.toMatchObject([
478
- {
479
- id: "agent-teams-panel",
480
- data: {
481
- tasks: [
482
- { title: "Review plugin manifest", status: "done" },
483
- { title: "Run integration checks", status: "in_progress" },
484
- ],
485
- },
486
- },
487
- ]);
488
- const panel = (await panels.snapshot())[0];
489
- if (panel === undefined) throw new Error("agent-teams-panel was not registered");
490
- expect((panel.data as { members: { id: string; status: string }[] }).members.find((member) => member.id === "builder")).toMatchObject({
491
- status: "working",
492
- });
493
- expect(entries).toHaveLength(6);
1900
+ namedTool(tools, "team_task").execute(
1901
+ "call-1",
1902
+ { action: "add_member", id: "member-65", name: "Member 65", role: "Worker" },
1903
+ undefined,
1904
+ undefined,
1905
+ {} as never,
1906
+ ),
1907
+ ).rejects.toThrow(/at most 64 members/iu);
1908
+ expect(entries).toHaveLength(1);
494
1909
  });
495
1910
 
496
1911
  test("reloads the live Pi session through the plugin-dev bridge", async () => {
@@ -545,20 +1960,23 @@ describe("Pi domain plugins", () => {
545
1960
  });
546
1961
 
547
1962
  test("attaches an in-workspace image through the modlens tool", async () => {
548
- const { context, cwd } = await createContext();
1963
+ const { context, cwd, agentDir } = await createContext();
549
1964
  const panels = new PiPluginUiRegistry();
550
1965
  const tools = new PiToolRegistry();
551
- await writeFile(join(cwd, "screen.png"), "png-data", "utf8");
1966
+ await writeFile(join(cwd, "screen.png"), Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB", "base64"));
552
1967
  context.provide("piTools", tools);
553
1968
  context.provide("piPluginUi", panels);
554
1969
 
555
1970
  await context.plugin(modlensPlugin);
556
1971
  const tool = firstTool(tools);
557
- await expect(tool.execute("call-1", { path: "screen.png" }, undefined, undefined, {} as never)).resolves.toMatchObject({
1972
+ await expect(tool.execute("call-1", { path: "screen.png" }, undefined, undefined, { model: { input: ["image"] } } as never)).resolves.toMatchObject({
558
1973
  content: [{ type: "image", mimeType: "image/png" }],
559
1974
  });
560
- await expect(panels.snapshot()).resolves.toMatchObject([{ id: "modlens-panel", data: { attached: true, image: { path: "screen.png", bytes: 8 } } }]);
1975
+ await expect(panels.snapshot()).resolves.toMatchObject([{ id: "modlens-panel", data: { attached: true, image: { path: "screen.png", bytes: 24 } } }]);
561
1976
  await expect(tool.execute("call-2", { path: "../outside.png" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
1977
+ await writeFile(join(agentDir, "outside.png"), "outside-image", "utf8");
1978
+ await symlink(join(agentDir, "outside.png"), join(cwd, "linked.png"));
1979
+ await expect(tool.execute("call-3", { path: "linked.png" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
562
1980
  });
563
1981
 
564
1982
  test("stops a streaming run when the configured token budget is exceeded", async () => {
@@ -580,7 +1998,7 @@ describe("Pi domain plugins", () => {
580
1998
  expect(aborts).toBe(1);
581
1999
  });
582
2000
 
583
- test("writes a Git time capsule outside the workspace", async () => {
2001
+ test("writes a Git undo capsule outside the workspace", async () => {
584
2002
  const { context, cwd } = await createContext();
585
2003
  const panels = new PiPluginUiRegistry();
586
2004
  const tools = new PiToolRegistry();
@@ -597,7 +2015,7 @@ describe("Pi domain plugins", () => {
597
2015
  const result = await tool.execute("call-1", {}, undefined, undefined, {} as never);
598
2016
  const message = result.content[0];
599
2017
  expect(message?.type).toBe("text");
600
- expect(message?.type === "text" ? message.text : "").toMatch(/^Git snapshot saved:/);
2018
+ expect(message?.type === "text" ? message.text : "").toMatch(/^Git undo capsule saved:/);
601
2019
  const snapshot = await panels.snapshot();
602
2020
  expect(snapshot[0]?.id).toBe("git-time-capsule-panel");
603
2021
  const capsuleData = snapshot[0]?.data as
@@ -626,8 +2044,19 @@ describe("Pi domain plugins", () => {
626
2044
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "dependency-checker-panel", data: { report: { missing: ["missing-package"] } } }]);
627
2045
  });
628
2046
 
629
- test("attaches a bounded workspace file for @file context", async () => {
2047
+ test("rejects oversized dependency manifests before parsing them", async () => {
630
2048
  const { context, cwd } = await createContext();
2049
+ const tools = new PiToolRegistry();
2050
+ await writeFile(join(cwd, "package.json"), JSON.stringify({ dependencies: {}, padding: "x".repeat(1024 * 1024) }), "utf8");
2051
+ context.provide("piTools", tools);
2052
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2053
+ await context.plugin(dependencyCheckerPlugin);
2054
+
2055
+ await expect(namedTool(tools, "dependency_check").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB|too large/iu);
2056
+ });
2057
+
2058
+ test("attaches a bounded workspace file for @file context", async () => {
2059
+ const { context, cwd, agentDir } = await createContext();
631
2060
  const panels = new PiPluginUiRegistry();
632
2061
  const tools = new PiToolRegistry();
633
2062
  await writeFile(join(cwd, "notes.md"), "# Notes\ncontent", "utf8");
@@ -637,10 +2066,13 @@ describe("Pi domain plugins", () => {
637
2066
  await context.plugin(atFilePlugin);
638
2067
  const tool = firstTool(tools);
639
2068
  await expect(tool.execute("call-1", { path: "notes.md" }, undefined, undefined, {} as never)).resolves.toMatchObject({
640
- content: [{ text: '<file path="notes.md">\n# Notes\ncontent\n</file>' }],
2069
+ content: [{ text: '<file path="notes.md" untrusted="true">\n# Notes\ncontent\n</file>' }],
641
2070
  });
642
2071
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "at-file-panel", data: { lastFile: { path: "notes.md", bytes: 15 } } }]);
643
2072
  await expect(tool.execute("call-2", { path: "../notes.md" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
2073
+ await writeFile(join(agentDir, "outside-notes.md"), "outside", "utf8");
2074
+ await symlink(join(agentDir, "outside-notes.md"), join(cwd, "linked-notes.md"));
2075
+ await expect(tool.execute("call-3", { path: "linked-notes.md" }, undefined, undefined, {} as never)).rejects.toThrow(/inside the current workspace/);
644
2076
  });
645
2077
 
646
2078
  test("deduplicates extension failures in the live failure logger panel", async () => {
@@ -666,7 +2098,7 @@ describe("Pi domain plugins", () => {
666
2098
  context.provide("piPluginUi", panels);
667
2099
  await context.plugin(testHarnessPlugin);
668
2100
  const tool = firstTool(tools);
669
- await expect(tool.execute("call-1", { script: "rm -rf /" }, undefined, undefined, {} as never)).rejects.toThrow(/not allowed/);
2101
+ await expect(tool.execute("call-1", { script: "rm -rf /" }, undefined, undefined, {} as never)).rejects.toThrow(/not an approved verification script/iu);
670
2102
  const snapshot = await panels.snapshot();
671
2103
  expect(snapshot[0]?.id).toBe("test-harness-panel");
672
2104
  const allowedScripts = (snapshot[0]?.data as { allowedScripts?: unknown } | undefined)?.allowedScripts;
@@ -683,7 +2115,7 @@ describe("Pi domain plugins", () => {
683
2115
  assistantMessages: 2,
684
2116
  toolCalls: 1,
685
2117
  toolResults: 1,
686
- totalMessages: 4,
2118
+ totalMessages: 5,
687
2119
  tokens: { input: 10, output: 20, cacheRead: 0, cacheWrite: 0, total: 30 },
688
2120
  cost: 0.01,
689
2121
  contextUsage: { tokens: 30, contextWindow: 1000, percent: 3 },
@@ -717,6 +2149,17 @@ describe("Pi domain plugins", () => {
717
2149
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "readme-gen-panel", data: { generated: true, name: "demo", scripts: 2 } }]);
718
2150
  });
719
2151
 
2152
+ test("rejects oversized package manifests before generating a README", async () => {
2153
+ const { context, cwd } = await createContext();
2154
+ const tools = new PiToolRegistry();
2155
+ await writeFile(join(cwd, "package.json"), JSON.stringify({ name: "demo", padding: "x".repeat(1024 * 1024) }), "utf8");
2156
+ context.provide("piTools", tools);
2157
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2158
+ await context.plugin(readmeGenPlugin);
2159
+
2160
+ await expect(namedTool(tools, "readme_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB|too large/iu);
2161
+ });
2162
+
720
2163
  test("requires explicit confirmation before cleaning only generated capsules", async () => {
721
2164
  const { context, agentDir } = await createContext();
722
2165
  const panels = new PiPluginUiRegistry();
@@ -735,8 +2178,48 @@ describe("Pi domain plugins", () => {
735
2178
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "cleaner-panel", data: { capsules: [{ name: "202601.patch" }] } }]);
736
2179
  });
737
2180
 
2181
+ test("ignores symbolic links while cleaning capsule files", async () => {
2182
+ if (process.platform === "win32") return;
2183
+ const { context, cwd, agentDir } = await createContext();
2184
+ const panels = new PiPluginUiRegistry();
2185
+ const tools = new PiToolRegistry();
2186
+ const directory = join(agentDir, "capsules");
2187
+ const outside = join(cwd, "outside.patch");
2188
+ const link = join(directory, "linked.patch");
2189
+ await mkdir(directory, { recursive: true });
2190
+ await writeFile(join(directory, "generated.patch"), "generated", "utf8");
2191
+ await writeFile(outside, "outside", "utf8");
2192
+ await symlink(outside, link);
2193
+ context.provide("piTools", tools);
2194
+ context.provide("piPluginUi", panels);
2195
+ await context.plugin(cleanerPlugin);
2196
+
2197
+ await expect(
2198
+ namedTool(tools, "clean_harness_artifacts").execute("call-1", { confirm: true, keep: 0 }, undefined, undefined, {} as never),
2199
+ ).resolves.toMatchObject({
2200
+ details: { removed: 1, kept: 0 },
2201
+ });
2202
+ expect(existsSync(link)).toBe(true);
2203
+ await expect(readFile(outside, "utf8")).resolves.toBe("outside");
2204
+ await expect(panels.snapshot()).resolves.toMatchObject([{ id: "cleaner-panel", data: { capsules: [] } }]);
2205
+ });
2206
+
2207
+ test("rejects an excessive number of capsule files without materializing an unbounded list", async () => {
2208
+ const { context, agentDir } = await createContext();
2209
+ const directory = join(agentDir, "capsules");
2210
+ await mkdir(directory, { recursive: true });
2211
+ await Promise.all(Array.from({ length: 257 }, (_, index) => writeFile(join(directory, `${String(index).padStart(4, "0")}.patch`), "x", "utf8")));
2212
+ context.provide("piTools", new PiToolRegistry());
2213
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2214
+ await context.plugin(cleanerPlugin);
2215
+
2216
+ const snapshot = await context.piPluginUi.snapshot();
2217
+ expect(snapshot[0]?.id).toBe("cleaner-panel");
2218
+ expect(snapshot[0]?.error).toMatch(/exceeds.*256.*capsule/iu);
2219
+ });
2220
+
738
2221
  test("reports missing and extra keys between local locale files", async () => {
739
- const { context, cwd } = await createContext();
2222
+ const { context, cwd, agentDir } = await createContext();
740
2223
  const panels = new PiPluginUiRegistry();
741
2224
  const tools = new PiToolRegistry();
742
2225
  await mkdir(join(cwd, "locales"), { recursive: true });
@@ -745,8 +2228,43 @@ describe("Pi domain plugins", () => {
745
2228
  context.provide("piTools", tools);
746
2229
  context.provide("piPluginUi", panels);
747
2230
  await context.plugin(i18nPairPlugin);
748
- const result = await firstTool(tools).execute("call-1", {}, undefined, undefined, {} as never);
2231
+ const tool = firstTool(tools);
2232
+ const result = await tool.execute("call-1", {}, undefined, undefined, {} as never);
749
2233
  expect(result).toMatchObject({ details: { missing: ["save"], extra: ["onlyHere"] } });
2234
+ await writeFile(join(agentDir, "outside-locale.json"), JSON.stringify({ secret: "outside" }), "utf8");
2235
+ await symlink(join(agentDir, "outside-locale.json"), join(cwd, "locales", "linked.json"));
2236
+ await expect(tool.execute("call-2", { base: "locales/linked.json" }, undefined, undefined, {} as never)).rejects.toThrow(/symbolic link/iu);
2237
+ });
2238
+
2239
+ test("rejects oversized locale files before parsing them", async () => {
2240
+ const { context, cwd } = await createContext();
2241
+ const tools = new PiToolRegistry();
2242
+ await mkdir(join(cwd, "locales"), { recursive: true });
2243
+ await writeFile(join(cwd, "locales", "en.json"), JSON.stringify({ padding: "x".repeat(4 * 1024 * 1024) }), "utf8");
2244
+ await writeFile(join(cwd, "locales", "ja.json"), "{}", "utf8");
2245
+ context.provide("piTools", tools);
2246
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2247
+ await context.plugin(i18nPairPlugin);
2248
+
2249
+ await expect(
2250
+ namedTool(tools, "i18n_check").execute("call-1", { base: "locales/en.json", target: "locales/ja.json" }, undefined, undefined, {} as never),
2251
+ ).rejects.toThrow(/4 MiB|locale.*limit/iu);
2252
+ });
2253
+
2254
+ test("rejects locale objects deeper than the supported nesting limit", async () => {
2255
+ const { context, cwd } = await createContext();
2256
+ const tools = new PiToolRegistry();
2257
+ await mkdir(join(cwd, "locales"), { recursive: true });
2258
+ const deeplyNested = `${'{"nested":'.repeat(129)}"value"${"}".repeat(129)}`;
2259
+ await writeFile(join(cwd, "locales", "en.json"), deeplyNested, "utf8");
2260
+ await writeFile(join(cwd, "locales", "ja.json"), "{}", "utf8");
2261
+ context.provide("piTools", tools);
2262
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2263
+ await context.plugin(i18nPairPlugin);
2264
+
2265
+ await expect(
2266
+ namedTool(tools, "i18n_check").execute("call-1", { base: "locales/en.json", target: "locales/ja.json" }, undefined, undefined, {} as never),
2267
+ ).rejects.toThrow(/nesting|depth|128/iu);
750
2268
  });
751
2269
 
752
2270
  test("executes bounded read-only SQLite queries and rejects mutations", async () => {
@@ -809,6 +2327,36 @@ describe("Pi domain plugins", () => {
809
2327
  await expect(status!.execute("call-4", {}, undefined, undefined, {} as never)).resolves.toMatchObject({ details: { running: false } });
810
2328
  });
811
2329
 
2330
+ test("does not leak a rejected promise when mock server cleanup fails", async () => {
2331
+ const { context } = await createContext();
2332
+ const tools = new PiToolRegistry();
2333
+ context.provide("piTools", tools);
2334
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2335
+ await context.plugin(mockServerPlugin, { port: 0, routes: [] });
2336
+ await namedTool(tools, "mock_server_start").execute("call-1", {}, undefined, undefined, {} as never);
2337
+ let closeCalled = false;
2338
+ const closeSpy = vi.spyOn(Server.prototype, "close");
2339
+ closeSpy.mockImplementationOnce(function (this: Server, callback?: (error?: Error) => void) {
2340
+ closeCalled = true;
2341
+ closeSpy.mockRestore();
2342
+ return this.close(() => callback?.(new Error("forced close failure")));
2343
+ } as typeof Server.prototype.close);
2344
+ const unhandled: unknown[] = [];
2345
+ const onUnhandled = (reason: unknown): void => {
2346
+ unhandled.push(reason);
2347
+ };
2348
+ process.on("unhandledRejection", onUnhandled);
2349
+ try {
2350
+ await context.fiber.dispose();
2351
+ await vi.waitFor(() => expect(closeCalled).toBe(true));
2352
+ await new Promise<void>((resolve) => setImmediate(resolve));
2353
+ expect(unhandled).toEqual([]);
2354
+ } finally {
2355
+ process.off("unhandledRejection", onUnhandled);
2356
+ closeSpy.mockRestore();
2357
+ }
2358
+ });
2359
+
812
2360
  test("records CLI notifications and respects the disabled setting", async () => {
813
2361
  const { context } = await createContext();
814
2362
  const panels = new PiPluginUiRegistry();
@@ -829,6 +2377,50 @@ describe("Pi domain plugins", () => {
829
2377
  expect(notificationData?.notifications?.map((notification) => notification.message)).toEqual(["Agent turn completed.", "build finished"]);
830
2378
  });
831
2379
 
2380
+ test("bounds notifier messages produced from session error events", async () => {
2381
+ const { context } = await createContext();
2382
+ const panels = new PiPluginUiRegistry();
2383
+ context.provide("piTools", new PiToolRegistry());
2384
+ context.provide("piPluginUi", panels);
2385
+ await context.plugin(cliNotifierPlugin, { enabled: false });
2386
+
2387
+ context.emit("pi/session-event", {
2388
+ type: "agent_end",
2389
+ messages: [{ role: "assistant", stopReason: "error", errorMessage: "x".repeat(3_000) }],
2390
+ willRetry: false,
2391
+ } as never);
2392
+ await new Promise<void>((resolve) => setImmediate(resolve));
2393
+
2394
+ const snapshot = await panels.snapshot();
2395
+ const data = snapshot[0]?.data as { notifications?: Array<{ message: string }> } | undefined;
2396
+ expect(data?.notifications).toHaveLength(1);
2397
+ expect(data?.notifications?.[0]?.message.length).toBeLessThanOrEqual(2_048);
2398
+ });
2399
+
2400
+ test("times out a hung desktop notification process", async () => {
2401
+ if (process.platform === "win32") return;
2402
+ const { context, cwd } = await createContext();
2403
+ const bin = join(cwd, "bin");
2404
+ const executable = join(bin, process.platform === "darwin" ? "osascript" : "notify-send");
2405
+ await mkdir(bin);
2406
+ await writeFile(executable, "#!/bin/sh\nexec sleep 1\n", "utf8");
2407
+ await chmod(executable, 0o700);
2408
+ const originalPath = process.env.PATH;
2409
+ process.env.PATH = `${bin}${delimiter}${originalPath ?? ""}`;
2410
+ const tools = new PiToolRegistry();
2411
+ context.provide("piTools", tools);
2412
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2413
+ try {
2414
+ await context.plugin(cliNotifierPlugin, { enabled: true, timeoutMs: 500 });
2415
+ const result = await namedTool(tools, "cli_notify").execute("call-1", { message: "timeout fixture" }, undefined, undefined, {} as never);
2416
+ const details = result.details as { delivered?: unknown; reason?: unknown };
2417
+ expect(details.delivered).toBe(false);
2418
+ expect(details.reason).toMatch(/timed out/iu);
2419
+ } finally {
2420
+ process.env.PATH = originalPath;
2421
+ }
2422
+ });
2423
+
832
2424
  test("writes confirmed Markdown notes only inside the configured Obsidian vault", async () => {
833
2425
  const { context } = await createContext();
834
2426
  const vault = await mkdtemp(join(tmpdir(), "pi-harness-vault-"));
@@ -935,6 +2527,28 @@ describe("Pi domain plugins", () => {
935
2527
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "reviewer-bot-panel", data: { latest: { status: "warning", changedFiles: 1 } } }]);
936
2528
  });
937
2529
 
2530
+ test("times out a hung Git process while reviewing changes", async () => {
2531
+ if (process.platform === "win32") return;
2532
+ const { context, cwd } = await createContext();
2533
+ const bin = join(cwd, "bin");
2534
+ const executable = join(bin, "git");
2535
+ await mkdir(bin);
2536
+ await writeFile(executable, "#!/bin/sh\nexec sleep 1\n", "utf8");
2537
+ await chmod(executable, 0o700);
2538
+ const originalPath = process.env.PATH;
2539
+ process.env.PATH = `${bin}${delimiter}${originalPath ?? ""}`;
2540
+ const tools = new PiToolRegistry();
2541
+ context.provide("piTools", tools);
2542
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2543
+ try {
2544
+ await context.plugin(reviewerBotPlugin, { timeoutMs: 500 });
2545
+ const pending = namedTool(tools, "review_changes").execute("call-1", {}, undefined, undefined, {} as never);
2546
+ await expect(pending).rejects.toThrow(/timed out after 500 ms/iu);
2547
+ } finally {
2548
+ process.env.PATH = originalPath;
2549
+ }
2550
+ });
2551
+
938
2552
  test("executes safe argv commands and blocks risky auto-mode commands without confirmation", async () => {
939
2553
  const { context } = await createContext();
940
2554
  const panels = new PiPluginUiRegistry();
@@ -943,8 +2557,8 @@ describe("Pi domain plugins", () => {
943
2557
  context.provide("piPluginUi", panels);
944
2558
  await context.plugin(autoModePlugin, { mode: "safe", timeoutMs: 5_000 });
945
2559
  const tool = namedTool(tools, "auto_mode_exec");
946
- await expect(tool.execute("call-1", { command: ["node", "-e", "process.stdout.write('ok')"] }, undefined, undefined, {} as never)).resolves.toMatchObject({
947
- details: { allowed: true, exitCode: 0, stdout: "ok" },
2560
+ await expect(tool.execute("call-1", { command: ["git", "--version"] }, undefined, undefined, {} as never)).resolves.toMatchObject({
2561
+ details: { allowed: true, exitCode: 0 },
948
2562
  });
949
2563
  await expect(tool.execute("call-2", { command: ["rm", "-f", "file"] }, undefined, undefined, {} as never)).rejects.toThrow(/confirm=true/);
950
2564
  await expect(tool.execute("call-3", { command: ["sh", "-c", "echo bad"], confirm: true }, undefined, undefined, {} as never)).rejects.toThrow(
@@ -1073,6 +2687,96 @@ describe("Pi domain plugins", () => {
1073
2687
  await expect(secondPanels.snapshot()).resolves.toMatchObject([{ id: "memory-panel", data: { count: 0 } }]);
1074
2688
  });
1075
2689
 
2690
+ test("rolls back a failed memory write and recovers the mutation queue", async () => {
2691
+ const { context, agentDir } = await createContext();
2692
+ const memoryPath = join(agentDir, "memory.json");
2693
+ await writeFile(memoryPath, JSON.stringify({ version: 1, memories: [] }), "utf8");
2694
+ const tools = new PiToolRegistry();
2695
+ context.provide("piTools", tools);
2696
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2697
+ await context.plugin(memoryPlugin);
2698
+ const set = namedTool(tools, "memory_set");
2699
+ await namedTool(tools, "memory_search").execute("load", { query: "initial" }, undefined, undefined, {} as never);
2700
+ await rm(memoryPath);
2701
+ await mkdir(memoryPath);
2702
+
2703
+ await expect(set.execute("call-1", { key: "failed", value: "must roll back" }, undefined, undefined, {} as never)).rejects.toThrow();
2704
+ await rm(memoryPath, { recursive: true });
2705
+ await expect(set.execute("call-2", { key: "saved", value: "queue recovered" }, undefined, undefined, {} as never)).resolves.toMatchObject({
2706
+ details: { key: "saved" },
2707
+ });
2708
+ const persisted = JSON.parse(await readFile(memoryPath, "utf8")) as { memories: Array<{ key: string }> };
2709
+ expect(persisted.memories.map((memory) => memory.key)).toEqual(["saved"]);
2710
+ expect((await readdir(agentDir)).filter((name) => name.startsWith(".memory.json.") && name.endsWith(".tmp"))).toEqual([]);
2711
+ });
2712
+
2713
+ test("serializes concurrent contexts writing the same memory file", async () => {
2714
+ const first = await createContext();
2715
+ const second = new Context();
2716
+ contexts.push(second);
2717
+ provideLaunchContext(second, { cwd: first.cwd, agentDir: first.agentDir, args: [], requestExit() {} });
2718
+ const firstTools = new PiToolRegistry();
2719
+ const secondTools = new PiToolRegistry();
2720
+ first.context.provide("piTools", firstTools);
2721
+ first.context.provide("piPluginUi", new PiPluginUiRegistry());
2722
+ second.provide("piTools", secondTools);
2723
+ second.provide("piPluginUi", new PiPluginUiRegistry());
2724
+ await Promise.all([first.context.plugin(memoryPlugin), second.plugin(memoryPlugin)]);
2725
+
2726
+ await Promise.all([
2727
+ namedTool(firstTools, "memory_set").execute("call-a", { key: "concurrent-a", value: "first writer" }, undefined, undefined, {} as never),
2728
+ namedTool(secondTools, "memory_set").execute("call-b", { key: "concurrent-b", value: "second writer" }, undefined, undefined, {} as never),
2729
+ ]);
2730
+ const persisted = JSON.parse(await readFile(join(first.agentDir, "memory.json"), "utf8")) as { memories: Array<{ key: string }> };
2731
+ expect(persisted.memories.map((memory) => memory.key).sort()).toEqual(["concurrent-a", "concurrent-b"]);
2732
+ });
2733
+
2734
+ test("rejects malformed memory records instead of loading partial data", async () => {
2735
+ const { context, agentDir } = await createContext();
2736
+ await writeFile(join(agentDir, "memory.json"), JSON.stringify({ version: 1, memories: [{ key: "partial", value: "missing metadata" }] }), "utf8");
2737
+ const tools = new PiToolRegistry();
2738
+ context.provide("piTools", tools);
2739
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2740
+ await context.plugin(memoryPlugin);
2741
+
2742
+ await expect(namedTool(tools, "memory_search").execute("call-1", { query: "partial" }, undefined, undefined, {} as never)).rejects.toThrow(
2743
+ /invalid memories/iu,
2744
+ );
2745
+ });
2746
+
2747
+ test("does not read memory records through a symbolic link", async () => {
2748
+ const { context, cwd, agentDir } = await createContext();
2749
+ const now = new Date().toISOString();
2750
+ const outside = join(cwd, "outside-memory.json");
2751
+ await writeFile(
2752
+ outside,
2753
+ JSON.stringify({ version: 1, memories: [{ id: "secret", key: "secret", value: "outside value", tags: [], createdAt: now, updatedAt: now }] }),
2754
+ "utf8",
2755
+ );
2756
+ await symlink(outside, join(agentDir, "memory.json"));
2757
+ const tools = new PiToolRegistry();
2758
+ context.provide("piTools", tools);
2759
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2760
+ await context.plugin(memoryPlugin);
2761
+
2762
+ await expect(namedTool(tools, "memory_search").execute("call-1", { query: "secret" }, undefined, undefined, {} as never)).rejects.toThrow(
2763
+ /symbolic link|regular file/iu,
2764
+ );
2765
+ });
2766
+
2767
+ test("bounds the memory file size according to the configured entry limit", async () => {
2768
+ const { context, agentDir } = await createContext();
2769
+ await writeFile(join(agentDir, "memory.json"), Buffer.alloc(128 * 1024));
2770
+ const tools = new PiToolRegistry();
2771
+ context.provide("piTools", tools);
2772
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2773
+ await context.plugin(memoryPlugin, { maxEntries: 1 });
2774
+
2775
+ await expect(namedTool(tools, "memory_search").execute("call-1", { query: "memory" }, undefined, undefined, {} as never)).rejects.toThrow(
2776
+ /size|limit|exceeds/iu,
2777
+ );
2778
+ });
2779
+
1076
2780
  test("persists typed graph memory nodes and relations across plugin lifecycles", async () => {
1077
2781
  const first = await createContext();
1078
2782
  const panels = new PiPluginUiRegistry();
@@ -1149,6 +2853,30 @@ describe("Pi domain plugins", () => {
1149
2853
  expect(panel?.error).toMatch(/invalid nodes/iu);
1150
2854
  });
1151
2855
 
2856
+ test("does not read graph memory through a symbolic link", async () => {
2857
+ const { context, cwd, agentDir } = await createContext();
2858
+ const now = new Date().toISOString();
2859
+ const outside = join(cwd, "outside-graph.json");
2860
+ await writeFile(
2861
+ outside,
2862
+ JSON.stringify({
2863
+ version: 1,
2864
+ nodes: [{ id: "secret", kind: "event", label: "Secret event", summary: "outside value", createdAt: now, updatedAt: now }],
2865
+ relations: [],
2866
+ }),
2867
+ "utf8",
2868
+ );
2869
+ await symlink(outside, join(agentDir, "graph-memory.json"));
2870
+ const tools = new PiToolRegistry();
2871
+ context.provide("piTools", tools);
2872
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2873
+ await context.plugin(graphMemoryPlugin);
2874
+
2875
+ await expect(namedTool(tools, "graph_memory_search").execute("call-1", { query: "secret" }, undefined, undefined, {} as never)).rejects.toThrow(
2876
+ /symbolic link|regular file/iu,
2877
+ );
2878
+ });
2879
+
1152
2880
  test("serializes concurrent contexts writing the same graph memory file", async () => {
1153
2881
  const first = await createContext();
1154
2882
  const second = new Context();
@@ -1213,6 +2941,30 @@ describe("Pi domain plugins", () => {
1213
2941
  await expect(secondPanels.snapshot()).resolves.toMatchObject([{ id: "taskboard-panel", data: { total: 1, counts: { done: 1 } } }]);
1214
2942
  });
1215
2943
 
2944
+ test("rejects a taskboard database symlink before writing outside the agent directory", async () => {
2945
+ if (process.platform === "win32") return;
2946
+ const { context, cwd, agentDir } = await createContext();
2947
+ const outside = join(cwd, "outside.sqlite");
2948
+ const external = new DatabaseSync(outside);
2949
+ external.exec("CREATE TABLE sentinel (value TEXT NOT NULL); INSERT INTO sentinel VALUES ('unchanged')");
2950
+ external.close();
2951
+ await symlink(outside, join(agentDir, "taskboard.sqlite"));
2952
+ const tools = new PiToolRegistry();
2953
+ context.provide("piTools", tools);
2954
+ context.provide("piPluginUi", new PiPluginUiRegistry());
2955
+ await context.plugin(taskboardPlugin);
2956
+
2957
+ await expect(namedTool(tools, "taskboard_create").execute("call-1", { title: "Must stay local" }, undefined, undefined, {} as never)).rejects.toThrow(
2958
+ /symbolic link|regular file/iu,
2959
+ );
2960
+ const verified = new DatabaseSync(outside, { readOnly: true });
2961
+ try {
2962
+ expect(verified.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name").all()).toEqual([{ name: "sentinel" }]);
2963
+ } finally {
2964
+ verified.close();
2965
+ }
2966
+ });
2967
+
1216
2968
  test("generates validated Mermaid diagrams through the canvas draw plugin", async () => {
1217
2969
  const { context } = await createContext();
1218
2970
  const panels = new PiPluginUiRegistry();
@@ -1268,6 +3020,60 @@ describe("Pi domain plugins", () => {
1268
3020
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "image-compressor-panel", data: { last: { outputPath: "compressed.png", saved: true } } }]);
1269
3021
  });
1270
3022
 
3023
+ test("rejects PNG image data that expands beyond the IHDR-declared scanline size", async () => {
3024
+ const { context, cwd } = await createContext();
3025
+ const panels = new PiPluginUiRegistry();
3026
+ const tools = new PiToolRegistry();
3027
+ const header = Buffer.alloc(13);
3028
+ header.writeUInt32BE(1, 0);
3029
+ header.writeUInt32BE(1, 4);
3030
+ header[8] = 8;
3031
+ header[9] = 0;
3032
+ await writeFile(join(cwd, "bomb.png"), testPng(header, Buffer.alloc(1024 * 1024)));
3033
+ context.provide("piTools", tools);
3034
+ context.provide("piPluginUi", panels);
3035
+ await context.plugin(imageCompressorPlugin);
3036
+
3037
+ await expect(
3038
+ namedTool(tools, "image_compress").execute(
3039
+ "call-1",
3040
+ { path: "bomb.png", outputPath: "compressed.png", confirm: true },
3041
+ undefined,
3042
+ undefined,
3043
+ {} as never,
3044
+ ),
3045
+ ).rejects.toThrow(/image data|scanline|decompress/iu);
3046
+ await expect((await import("node:fs/promises")).stat(join(cwd, "compressed.png"))).rejects.toMatchObject({ code: "ENOENT" });
3047
+ });
3048
+
3049
+ test("preserves PNG ancillary chunks while recompressing image data", async () => {
3050
+ const { context, cwd } = await createContext();
3051
+ const panels = new PiPluginUiRegistry();
3052
+ const tools = new PiToolRegistry();
3053
+ const header = Buffer.alloc(13);
3054
+ header.writeUInt32BE(1, 0);
3055
+ header.writeUInt32BE(1, 4);
3056
+ header[8] = 8;
3057
+ header[9] = 0;
3058
+ const gamma = testPngChunk("gAMA", Buffer.from([0, 0, 177, 143]));
3059
+ const text = testPngChunk("tEXt", Buffer.from("Comment\0production metadata", "latin1"));
3060
+ await writeFile(join(cwd, "source.png"), testPng(header, Buffer.from([0, 127]), [gamma], [text]));
3061
+ context.provide("piTools", tools);
3062
+ context.provide("piPluginUi", panels);
3063
+ await context.plugin(imageCompressorPlugin);
3064
+
3065
+ await namedTool(tools, "image_compress").execute(
3066
+ "call-1",
3067
+ { path: "source.png", outputPath: "compressed.png", confirm: true },
3068
+ undefined,
3069
+ undefined,
3070
+ {} as never,
3071
+ );
3072
+ const output = await readFile(join(cwd, "compressed.png"));
3073
+ expect(output.includes(gamma)).toBe(true);
3074
+ expect(output.includes(text)).toBe(true);
3075
+ });
3076
+
1271
3077
  test("searches bounded workspace text and reports file locations", async () => {
1272
3078
  const { context, cwd } = await createContext();
1273
3079
  const panels = new PiPluginUiRegistry();
@@ -1324,7 +3130,7 @@ describe("Pi domain plugins", () => {
1324
3130
  });
1325
3131
 
1326
3132
  test("packages selected source files into a local skill directory", async () => {
1327
- const { context, cwd } = await createContext();
3133
+ const { context, cwd, agentDir } = await createContext();
1328
3134
  const panels = new PiPluginUiRegistry();
1329
3135
  const tools = new PiToolRegistry();
1330
3136
  await mkdir(join(cwd, "src"), { recursive: true });
@@ -1339,6 +3145,25 @@ describe("Pi domain plugins", () => {
1339
3145
  await expect(readFile(join(cwd, ".pi", "skills", "parser-guide", "SKILL.md"), "utf8")).resolves.toContain("Explain parser conventions");
1340
3146
  await expect(readFile(join(cwd, ".pi", "skills", "parser-guide", "references", "src", "parser.ts"), "utf8")).resolves.toContain("parse");
1341
3147
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "code2skill-panel", data: { generated: 1, latest: { slug: "parser-guide" } } }]);
3148
+ await expect(
3149
+ tool.execute("call-2", { name: "Parser Guide", description: "Do not overwrite", files: ["src/parser.ts"] }, undefined, undefined, {} as never),
3150
+ ).rejects.toThrow(/already exists/);
3151
+
3152
+ await writeFile(join(agentDir, "outside.ts"), "export const secret = true;\n", "utf8");
3153
+ await symlink(join(agentDir, "outside.ts"), join(cwd, "src", "outside.ts"));
3154
+ await expect(
3155
+ tool.execute("call-3", { name: "Outside Source", description: "Must stay bounded", files: ["src/outside.ts"] }, undefined, undefined, {} as never),
3156
+ ).rejects.toThrow(/inside the workspace/);
3157
+
3158
+ await expect(
3159
+ tool.execute("call-4", { name: "Directory Source", description: "Only regular files are allowed", files: ["src"] }, undefined, undefined, {} as never),
3160
+ ).rejects.toThrow(/regular file/iu);
3161
+
3162
+ await symlink(agentDir, join(cwd, ".pi", "skills", "linked-pack"));
3163
+ await expect(
3164
+ tool.execute("call-5", { name: "Linked Pack", description: "Must stay bounded", files: ["src/parser.ts"] }, undefined, undefined, {} as never),
3165
+ ).rejects.toThrow(/inside the workspace/);
3166
+ await expect(readFile(join(agentDir, "SKILL.md"), "utf8")).rejects.toMatchObject({ code: "ENOENT" });
1342
3167
  });
1343
3168
 
1344
3169
  test("persists named session tabs without deleting session files", async () => {
@@ -1358,11 +3183,110 @@ describe("Pi domain plugins", () => {
1358
3183
  details: { tabs: [{ label: "API 回归", pinned: true }] },
1359
3184
  });
1360
3185
  await expect(readFile(join(agentDir, "session-tabs.json"), "utf8")).resolves.toContain("API 回归");
3186
+ expect((await stat(join(agentDir, "session-tabs.json"))).mode & 0o777).toBe(0o600);
1361
3187
  await expect(panels.snapshot()).resolves.toMatchObject([
1362
3188
  { id: "tab-manager-panel", data: { activeId: "session-a", tabs: [{ label: "API 回归" }], writes: 2 } },
1363
3189
  ]);
1364
3190
  });
1365
3191
 
3192
+ test("rejects a corrupted session tab store instead of replacing it", async () => {
3193
+ const { context, agentDir } = await createContext();
3194
+ await writeFile(join(agentDir, "session-tabs.json"), "{not-json", "utf8");
3195
+ const tools = new PiToolRegistry();
3196
+ context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
3197
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3198
+ context.provide("piTools", tools);
3199
+ let activationError: unknown;
3200
+
3201
+ try {
3202
+ await context.plugin(tabManagerPlugin);
3203
+ } catch (error) {
3204
+ activationError = error;
3205
+ }
3206
+ expect(activationError).toBeInstanceOf(Error);
3207
+ if (!(activationError instanceof Error)) throw new Error("Expected corrupt tab store rejection");
3208
+ expect(activationError.message).toMatch(/session tab store.*invalid JSON/iu);
3209
+ await expect(readFile(join(agentDir, "session-tabs.json"), "utf8")).resolves.toBe("{not-json");
3210
+ });
3211
+
3212
+ test("does not load session tabs through a symbolic link", async () => {
3213
+ const { context, cwd, agentDir } = await createContext();
3214
+ const outside = join(cwd, "outside-tabs.json");
3215
+ await writeFile(outside, JSON.stringify({ tabs: [], activeId: null }), "utf8");
3216
+ await symlink(outside, join(agentDir, "session-tabs.json"));
3217
+ context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
3218
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3219
+ context.provide("piTools", new PiToolRegistry());
3220
+
3221
+ let activationError: unknown;
3222
+ try {
3223
+ await context.plugin(tabManagerPlugin);
3224
+ } catch (error) {
3225
+ activationError = error;
3226
+ }
3227
+ expect(activationError).toBeInstanceOf(Error);
3228
+ if (!(activationError instanceof Error)) throw new Error("Expected linked tab store rejection");
3229
+ expect(activationError.message).toMatch(/symbolic link|regular file/iu);
3230
+ });
3231
+
3232
+ test("rejects an oversized session tab store before parsing it", async () => {
3233
+ const { context, agentDir } = await createContext();
3234
+ await writeFile(join(agentDir, "session-tabs.json"), Buffer.alloc(1024 * 1024 + 1));
3235
+ context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
3236
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3237
+ context.provide("piTools", new PiToolRegistry());
3238
+
3239
+ let activationError: unknown;
3240
+ try {
3241
+ await context.plugin(tabManagerPlugin);
3242
+ } catch (error) {
3243
+ activationError = error;
3244
+ }
3245
+ expect(activationError).toBeInstanceOf(Error);
3246
+ if (!(activationError instanceof Error)) throw new Error("Expected oversized tab store rejection");
3247
+ expect(activationError.message).toMatch(/1 MiB|size|limit/iu);
3248
+ });
3249
+
3250
+ test("rejects oversized labels before changing the session tab store", async () => {
3251
+ const { context, agentDir } = await createContext();
3252
+ const tools = new PiToolRegistry();
3253
+ context.provide("piSession", { manager: { getSessionId: () => "session-a", getSessionFile: () => join(agentDir, "session-a.jsonl") } } as never);
3254
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3255
+ context.provide("piTools", tools);
3256
+ await context.plugin(tabManagerPlugin);
3257
+ const tool = namedTool(tools, "session_tab_manage");
3258
+ await tool.execute("call-1", { action: "pin", label: "Valid" }, undefined, undefined, {} as never);
3259
+
3260
+ await expect(tool.execute("call-2", { action: "pin", label: "x".repeat(121) }, undefined, undefined, {} as never)).rejects.toThrow(/label.*1 to 120/iu);
3261
+ const persisted = JSON.parse(await readFile(join(agentDir, "session-tabs.json"), "utf8")) as { tabs: Array<{ label: string }> };
3262
+ expect(persisted.tabs[0]?.label).toBe("Valid");
3263
+ });
3264
+
3265
+ test("serializes concurrent contexts writing the same session tab store", async () => {
3266
+ const first = await createContext();
3267
+ const second = new Context();
3268
+ contexts.push(second);
3269
+ provideLaunchContext(second, { cwd: first.cwd, agentDir: first.agentDir, args: [], requestExit() {} });
3270
+ const firstTools = new PiToolRegistry();
3271
+ const secondTools = new PiToolRegistry();
3272
+ first.context.provide("piSession", {
3273
+ manager: { getSessionId: () => "session-a", getSessionFile: () => join(first.agentDir, "session-a.jsonl") },
3274
+ } as never);
3275
+ second.provide("piSession", { manager: { getSessionId: () => "session-b", getSessionFile: () => join(first.agentDir, "session-b.jsonl") } } as never);
3276
+ first.context.provide("piPluginUi", new PiPluginUiRegistry());
3277
+ first.context.provide("piTools", firstTools);
3278
+ second.provide("piPluginUi", new PiPluginUiRegistry());
3279
+ second.provide("piTools", secondTools);
3280
+ await Promise.all([first.context.plugin(tabManagerPlugin), second.plugin(tabManagerPlugin)]);
3281
+
3282
+ await Promise.all([
3283
+ namedTool(firstTools, "session_tab_manage").execute("call-a", { action: "pin", label: "Session A" }, undefined, undefined, {} as never),
3284
+ namedTool(secondTools, "session_tab_manage").execute("call-b", { action: "pin", label: "Session B" }, undefined, undefined, {} as never),
3285
+ ]);
3286
+ const persisted = JSON.parse(await readFile(join(first.agentDir, "session-tabs.json"), "utf8")) as { tabs: Array<{ id: string }> };
3287
+ expect(persisted.tabs.map((tab) => tab.id).sort()).toEqual(["session-a", "session-b"]);
3288
+ });
3289
+
1366
3290
  test("renders structured GenUI blocks without accepting executable markup", async () => {
1367
3291
  const { context } = await createContext();
1368
3292
  const panels = new PiPluginUiRegistry();
@@ -1398,7 +3322,7 @@ describe("Pi domain plugins", () => {
1398
3322
  const tools = new PiToolRegistry();
1399
3323
  context.provide("piPluginUi", panels);
1400
3324
  context.provide("piTools", tools);
1401
- await context.plugin(anchoredStandardPlugin, { maxToolCalls: 2 });
3325
+ await context.plugin(anchoredStandardPlugin, { maxToolCalls: 2, allowedTools: [" read ", "read"] });
1402
3326
  const check = namedTool(tools, "trajectory_anchor_check");
1403
3327
  context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "orphan", toolName: "bash" } as never);
1404
3328
  context.emit("pi/session-event", { type: "agent_start" } as never);
@@ -1406,10 +3330,174 @@ describe("Pi domain plugins", () => {
1406
3330
  context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "two", toolName: "read" } as never);
1407
3331
  context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "three", toolName: "read" } as never);
1408
3332
  context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
1409
- await expect(check.execute("call-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({ details: { status: "violated", toolCalls: 3 } });
3333
+ const result = await check.execute("call-1", {}, undefined, undefined, {} as never);
3334
+ const details = result.details as { status: string; toolCalls: number; violations: Array<{ code: string }> };
3335
+ expect(details).toMatchObject({ status: "violated", toolCalls: 3, allowedTools: ["read"] });
3336
+ expect(details.violations.map((violation) => violation.code)).toContain("disallowed_tool");
1410
3337
  await expect(panels.snapshot()).resolves.toMatchObject([{ id: "anchored-standard-panel", data: { status: "violated", events: 6, toolCalls: 3 } }]);
1411
3338
  });
1412
3339
 
3340
+ test("reports clean anchored-standard lifecycle transitions", async () => {
3341
+ const { context } = await createContext();
3342
+ const panels = new PiPluginUiRegistry();
3343
+ const tools = new PiToolRegistry();
3344
+ context.provide("piPluginUi", panels);
3345
+ context.provide("piTools", tools);
3346
+ await context.plugin(anchoredStandardPlugin, { allowedTools: ["read"] });
3347
+ const check = namedTool(tools, "trajectory_anchor_check");
3348
+ await expect(check.execute("idle", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3349
+ details: { status: "idle", events: 0, toolCalls: 0, allowedTools: ["read"], violations: [] },
3350
+ });
3351
+
3352
+ context.emit("pi/session-event", { type: "agent_start" } as never);
3353
+ context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "one", toolName: "read" } as never);
3354
+ await expect(check.execute("anchored", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3355
+ details: { status: "anchored", events: 2, toolCalls: 1, violations: [] },
3356
+ });
3357
+
3358
+ context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
3359
+ await expect(check.execute("ended", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3360
+ details: { status: "idle", events: 3, toolCalls: 1, violations: [] },
3361
+ });
3362
+ });
3363
+
3364
+ test("reports nested and orphan anchored-standard lifecycle events once per violation code", async () => {
3365
+ const { context } = await createContext();
3366
+ const panels = new PiPluginUiRegistry();
3367
+ const tools = new PiToolRegistry();
3368
+ context.provide("piPluginUi", panels);
3369
+ context.provide("piTools", tools);
3370
+ await context.plugin(anchoredStandardPlugin);
3371
+ const check = namedTool(tools, "trajectory_anchor_check");
3372
+ context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
3373
+ context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false } as never);
3374
+ context.emit("pi/session-event", { type: "agent_start" } as never);
3375
+ context.emit("pi/session-event", { type: "agent_start" } as never);
3376
+
3377
+ const result = await check.execute("check", {}, undefined, undefined, {} as never);
3378
+ const details = result.details as { violations: Array<{ code: string }> };
3379
+ expect(details.violations.map((violation) => violation.code)).toEqual(["orphan_end", "nested_run"]);
3380
+ });
3381
+
3382
+ test("stops anchored-standard event inspection when its plugin context is disposed", async () => {
3383
+ const { context } = await createContext();
3384
+ const panels = new PiPluginUiRegistry();
3385
+ const tools = new PiToolRegistry();
3386
+ context.provide("piPluginUi", panels);
3387
+ context.provide("piTools", tools);
3388
+ await context.plugin(anchoredStandardPlugin);
3389
+ const check = namedTool(tools, "trajectory_anchor_check");
3390
+
3391
+ await context.fiber.dispose();
3392
+ context.emit("pi/session-event", { type: "agent_start" } as never);
3393
+
3394
+ await expect(check.execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({ details: { status: "idle", events: 0 } });
3395
+ expect(tools.snapshot().customTools).toEqual([]);
3396
+ await expect(panels.snapshot()).resolves.toEqual([]);
3397
+ });
3398
+
3399
+ test("normalizes a non-finite anchored-standard tool budget to the default", async () => {
3400
+ const { context } = await createContext();
3401
+ const panels = new PiPluginUiRegistry();
3402
+ const tools = new PiToolRegistry();
3403
+ context.provide("piPluginUi", panels);
3404
+ context.provide("piTools", tools);
3405
+ await context.plugin(anchoredStandardPlugin, { maxToolCalls: Number.NaN });
3406
+
3407
+ await expect(namedTool(tools, "trajectory_anchor_check").execute("call-1", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3408
+ details: { status: "idle", maxToolCalls: 64 },
3409
+ });
3410
+ });
3411
+
3412
+ test("clamps and truncates anchored-standard tool budgets", async () => {
3413
+ for (const [configured, expected] of [
3414
+ [0, 1],
3415
+ [3.9, 3],
3416
+ [999, 512],
3417
+ ] as const) {
3418
+ const { context } = await createContext();
3419
+ const tools = new PiToolRegistry();
3420
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3421
+ context.provide("piTools", tools);
3422
+ await context.plugin(anchoredStandardPlugin, { maxToolCalls: configured });
3423
+ await expect(namedTool(tools, "trajectory_anchor_check").execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3424
+ details: { maxToolCalls: expected },
3425
+ });
3426
+ }
3427
+ });
3428
+
3429
+ test("leaves tool names unrestricted when anchored-standard has no explicit allowlist", async () => {
3430
+ const { context } = await createContext();
3431
+ const tools = new PiToolRegistry();
3432
+ context.provide("piPluginUi", new PiPluginUiRegistry());
3433
+ context.provide("piTools", tools);
3434
+ await context.plugin(anchoredStandardPlugin);
3435
+ context.emit("pi/session-event", { type: "agent_start" } as never);
3436
+ context.emit("pi/session-event", { type: "tool_execution_start", toolCallId: "one", toolName: "custom_tool" } as never);
3437
+
3438
+ await expect(namedTool(tools, "trajectory_anchor_check").execute("check", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
3439
+ details: { status: "anchored", allowedTools: [], violations: [] },
3440
+ });
3441
+ });
3442
+
3443
+ test("rejects an oversized anchored-standard tool allowlist before registering", async () => {
3444
+ const { context } = await createContext();
3445
+ const panels = new PiPluginUiRegistry();
3446
+ const tools = new PiToolRegistry();
3447
+ context.provide("piPluginUi", panels);
3448
+ context.provide("piTools", tools);
3449
+
3450
+ let activationError: unknown;
3451
+ try {
3452
+ await context.plugin(anchoredStandardPlugin, { allowedTools: Array.from({ length: 513 }, (_, index) => `tool-${index}`) });
3453
+ } catch (error) {
3454
+ activationError = error;
3455
+ }
3456
+ expect(activationError).toBeInstanceOf(Error);
3457
+ if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject an oversized allowlist");
3458
+ expect(activationError.message).toMatch(/512/iu);
3459
+ expect(tools.snapshot().customTools).toEqual([]);
3460
+ await expect(panels.snapshot()).resolves.toEqual([]);
3461
+ });
3462
+
3463
+ test("rejects an oversized anchored-standard tool name before registering", async () => {
3464
+ const { context } = await createContext();
3465
+ const panels = new PiPluginUiRegistry();
3466
+ const tools = new PiToolRegistry();
3467
+ context.provide("piPluginUi", panels);
3468
+ context.provide("piTools", tools);
3469
+ let activationError: unknown;
3470
+ try {
3471
+ await context.plugin(anchoredStandardPlugin, { allowedTools: ["t".repeat(129)] });
3472
+ } catch (error) {
3473
+ activationError = error;
3474
+ }
3475
+ expect(activationError).toBeInstanceOf(Error);
3476
+ if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject an oversized tool name");
3477
+ expect(activationError.message).toMatch(/128/iu);
3478
+ expect(tools.snapshot().customTools).toEqual([]);
3479
+ await expect(panels.snapshot()).resolves.toEqual([]);
3480
+ });
3481
+
3482
+ test("rejects a blank anchored-standard tool name instead of disabling the allowlist", async () => {
3483
+ const { context } = await createContext();
3484
+ const panels = new PiPluginUiRegistry();
3485
+ const tools = new PiToolRegistry();
3486
+ context.provide("piPluginUi", panels);
3487
+ context.provide("piTools", tools);
3488
+ let activationError: unknown;
3489
+ try {
3490
+ await context.plugin(anchoredStandardPlugin, { allowedTools: [" "] });
3491
+ } catch (error) {
3492
+ activationError = error;
3493
+ }
3494
+ expect(activationError).toBeInstanceOf(Error);
3495
+ if (!(activationError instanceof Error)) throw new Error("Expected anchored-standard activation to reject a blank tool name");
3496
+ expect(activationError.message).toMatch(/match regexp|non-whitespace/iu);
3497
+ expect(tools.snapshot().customTools).toEqual([]);
3498
+ await expect(panels.snapshot()).resolves.toEqual([]);
3499
+ });
3500
+
1413
3501
  test("blocks telemetry by default and never stores event properties", async () => {
1414
3502
  const { context } = await createContext();
1415
3503
  const panels = new PiPluginUiRegistry();
@@ -1476,7 +3564,7 @@ describe("Pi domain plugins", () => {
1476
3564
  const server = join(cwd, "mcp-fixture.mjs");
1477
3565
  await writeFile(
1478
3566
  server,
1479
- `let buffer = Buffer.alloc(0); const handle = (message) => { let result = {}; if (message.method === "initialize") result = { protocolVersion: "2025-06-18", capabilities: { resources: {}, prompts: {} }, serverInfo: { name: "fixture", version: "1" } }; if (message.method === "tools/list") result = { tools: [{ name: "echo", description: "Echo text", inputSchema: { type: "object" } }] }; if (message.method === "tools/call") result = { content: [{ type: "text", text: String(message.params.arguments?.text ?? "") }], isError: false }; if (message.method === "resources/list") result = { resources: [{ uri: "fixture://readme", name: "Readme", mimeType: "text/plain" }] }; if (message.method === "resources/read") result = { contents: [{ uri: message.params.uri, mimeType: "text/plain", text: "resource body" }] }; if (message.method === "prompts/list") result = { prompts: [{ name: "review", description: "Review prompt", arguments: [] }] }; if (message.method === "prompts/get") result = { description: "Review prompt", messages: [{ role: "user", content: { type: "text", text: "Review this" } }] }; process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + "\\n"); }; process.stdin.on("data", (chunk) => { buffer = Buffer.concat([buffer, chunk]); while (true) { const end = buffer.indexOf("\\r\\n\\r\\n"); if (end < 0) break; const match = buffer.subarray(0, end).toString().match(/Content-Length: (\\d+)/i); if (!match) break; const length = Number(match[1]); if (buffer.length < end + 4 + length) break; const body = buffer.subarray(end + 4, end + 4 + length); buffer = buffer.subarray(end + 4 + length); handle(JSON.parse(body)); } });`,
3567
+ `let buffer = ""; const handle = (message) => { if (message.id === undefined) return; let result = {}; if (message.method === "initialize") result = { protocolVersion: "2025-06-18", capabilities: { resources: {}, prompts: {} }, serverInfo: { name: "fixture", version: "1" } }; if (message.method === "tools/list") result = { tools: [{ name: "echo", description: "Echo text", inputSchema: { type: "object" } }] }; if (message.method === "tools/call") result = { content: [{ type: "text", text: String(message.params.arguments?.text ?? "") }], isError: false }; if (message.method === "resources/list") result = { resources: [{ uri: "fixture://readme", name: "Readme", mimeType: "text/plain" }] }; if (message.method === "resources/read") result = { contents: [{ uri: message.params.uri, mimeType: "text/plain", text: "resource body" }] }; if (message.method === "prompts/list") result = { prompts: [{ name: "review", description: "Review prompt", arguments: [] }] }; if (message.method === "prompts/get") result = { description: "Review prompt", messages: [{ role: "user", content: { type: "text", text: "Review this" } }] }; process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + "\\n"); }; process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { buffer += chunk; while (true) { const newline = buffer.indexOf("\\n"); if (newline < 0) break; const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
1480
3568
  "utf8",
1481
3569
  );
1482
3570
  const panels = new PiPluginUiRegistry();
@@ -1541,6 +3629,88 @@ describe("Pi domain plugins", () => {
1541
3629
  );
1542
3630
  });
1543
3631
 
3632
+ test("initializes a spec-compliant MCP stdio server with current client metadata", async () => {
3633
+ const { context, cwd } = await createContext();
3634
+ const server = join(cwd, "mcp-newline-fixture.mjs");
3635
+ const packageMetadata = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as { version: string };
3636
+ await writeFile(
3637
+ server,
3638
+ `let buffer = ""; let clientVersion = ""; const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); process.stdin.setEncoding("utf8"); process.stdin.on("data", (chunk) => { buffer += chunk; while (true) { const newline = buffer.indexOf("\\n"); if (newline < 0) break; const line = buffer.slice(0, newline).trim(); buffer = buffer.slice(newline + 1); if (line === "") continue; if (/^content-length:/i.test(line)) { send({ jsonrpc: "2.0", id: 1, error: { code: -32600, message: "Content-Length framing is not valid MCP stdio" } }); continue; } const message = JSON.parse(line); if (message.method === "initialize") { clientVersion = String(message.params.clientInfo.version); send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "newline-fixture", version: "1" } } }); } else if (message.method === "tools/list") send({ jsonrpc: "2.0", id: message.id, result: { tools: [{ name: "metadata", description: clientVersion, inputSchema: { type: "object" } }] } }); } });`,
3639
+ "utf8",
3640
+ );
3641
+ const panels = new PiPluginUiRegistry();
3642
+ const tools = new PiToolRegistry();
3643
+ context.provide("piTools", tools);
3644
+ context.provide("piPluginUi", panels);
3645
+ await context.plugin(mcpClientPlugin);
3646
+
3647
+ await expect(
3648
+ namedTool(tools, "mcp_list_tools").execute("call-1", { command: [process.execPath, server] }, undefined, undefined, {} as never),
3649
+ ).resolves.toMatchObject({
3650
+ details: { tools: [{ name: "metadata", description: packageMetadata.version }] },
3651
+ });
3652
+ });
3653
+
3654
+ test("rejects MCP response frames larger than 1 MiB", async () => {
3655
+ const { context, cwd } = await createContext();
3656
+ const server = join(cwd, "mcp-oversized-fixture.mjs");
3657
+ await writeFile(
3658
+ server,
3659
+ `let buffer = Buffer.alloc(0); const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); const handle = (message) => { if (message.method === "initialize") { send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "oversized-fixture", version: "1" } } }); return; } if (message.method === "tools/list") { const body = Buffer.from(JSON.stringify({ jsonrpc: "2.0", id: message.id, result: { padding: "x".repeat(1024 * 1024) } })); process.stdout.write(Buffer.concat([Buffer.from("Content-Length: " + body.length + "\\r\\n\\r\\n"), body])); } }; process.stdin.on("data", (chunk) => { buffer = Buffer.concat([buffer, chunk]); while (buffer.length > 0) { const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (/^content-length:/i.test(buffer.toString("ascii", 0, Math.min(buffer.length, 32)))) { if (headerEnd < 0) return; const match = buffer.subarray(0, headerEnd).toString("ascii").match(/content-length:\\s*(\\d+)/i); if (!match) return; const length = Number(match[1]); if (buffer.length < headerEnd + 4 + length) return; const body = buffer.subarray(headerEnd + 4, headerEnd + 4 + length); buffer = buffer.subarray(headerEnd + 4 + length); handle(JSON.parse(body.toString("utf8"))); continue; } const newline = buffer.indexOf(10); if (newline < 0) return; const line = buffer.subarray(0, newline).toString("utf8").trim(); buffer = buffer.subarray(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
3660
+ "utf8",
3661
+ );
3662
+ const panels = new PiPluginUiRegistry();
3663
+ const tools = new PiToolRegistry();
3664
+ context.provide("piTools", tools);
3665
+ context.provide("piPluginUi", panels);
3666
+ await context.plugin(mcpClientPlugin);
3667
+
3668
+ await expect(
3669
+ namedTool(tools, "mcp_list_tools").execute("call-1", { command: [process.execPath, server] }, undefined, undefined, {} as never),
3670
+ ).rejects.toThrow(/1 MiB limit/iu);
3671
+ });
3672
+
3673
+ test("cancels an in-flight MCP request and notifies the stdio server", async () => {
3674
+ const { context, cwd } = await createContext();
3675
+ const server = join(cwd, "mcp-cancellation-fixture.mjs");
3676
+ const requestedMarker = join(cwd, "mcp-requested");
3677
+ const cancelledMarker = join(cwd, "mcp-cancelled");
3678
+ await writeFile(
3679
+ server,
3680
+ `import { writeFileSync } from "node:fs"; let buffer = Buffer.alloc(0); const send = (message) => process.stdout.write(JSON.stringify(message) + "\\n"); const handle = (message) => { if (message.method === "initialize") { send({ jsonrpc: "2.0", id: message.id, result: { protocolVersion: "2025-06-18", capabilities: {}, serverInfo: { name: "cancellation-fixture", version: "1" } } }); return; } if (message.method === "tools/list") writeFileSync(${JSON.stringify(requestedMarker)}, "requested"); if (message.method === "notifications/cancelled") writeFileSync(${JSON.stringify(cancelledMarker)}, String(message.params.requestId)); }; process.stdin.on("data", (chunk) => { buffer = Buffer.concat([buffer, chunk]); while (buffer.length > 0) { const headerEnd = buffer.indexOf("\\r\\n\\r\\n"); if (/^content-length:/i.test(buffer.toString("ascii", 0, Math.min(buffer.length, 32)))) { if (headerEnd < 0) return; const match = buffer.subarray(0, headerEnd).toString("ascii").match(/content-length:\\s*(\\d+)/i); if (!match) return; const length = Number(match[1]); if (buffer.length < headerEnd + 4 + length) return; const body = buffer.subarray(headerEnd + 4, headerEnd + 4 + length); buffer = buffer.subarray(headerEnd + 4 + length); handle(JSON.parse(body.toString("utf8"))); continue; } const newline = buffer.indexOf(10); if (newline < 0) return; const line = buffer.subarray(0, newline).toString("utf8").trim(); buffer = buffer.subarray(newline + 1); if (line !== "") handle(JSON.parse(line)); } });`,
3681
+ "utf8",
3682
+ );
3683
+ const panels = new PiPluginUiRegistry();
3684
+ const tools = new PiToolRegistry();
3685
+ context.provide("piTools", tools);
3686
+ context.provide("piPluginUi", panels);
3687
+ await context.plugin(mcpClientPlugin, { servers: [{ id: "cancellation", command: [process.execPath, server], autoStart: false }] });
3688
+ await namedTool(tools, "mcp_server_start").execute("call-1", { serverId: "cancellation" }, undefined, undefined, {} as never);
3689
+ const controller = new AbortController();
3690
+ let outcome: unknown;
3691
+ const pending = namedTool(tools, "mcp_list_tools")
3692
+ .execute("call-2", { serverId: "cancellation" }, controller.signal, undefined, {} as never)
3693
+ .then(
3694
+ (result) => {
3695
+ outcome = result;
3696
+ },
3697
+ (error: unknown) => {
3698
+ outcome = error;
3699
+ },
3700
+ );
3701
+ await vi.waitFor(() => expect(existsSync(requestedMarker)).toBe(true));
3702
+ controller.abort(new Error("test cancellation"));
3703
+ try {
3704
+ await vi.waitFor(() => expect(outcome).toBeInstanceOf(Error), { timeout: 500 });
3705
+ if (!(outcome instanceof Error)) throw new Error("Expected MCP cancellation rejection");
3706
+ expect(outcome.message).toMatch(/cancelled/iu);
3707
+ await vi.waitFor(() => expect(existsSync(cancelledMarker)).toBe(true));
3708
+ } finally {
3709
+ await context.fiber.dispose();
3710
+ await pending;
3711
+ }
3712
+ });
3713
+
1544
3714
  test("fetches bounded browser pages and blocks private targets by default", async () => {
1545
3715
  const server = createServer((_request, response) => {
1546
3716
  response.setHeader("content-type", "text/html; charset=utf-8");
@@ -1912,6 +4082,73 @@ describe("Pi domain plugins", () => {
1912
4082
  }
1913
4083
  });
1914
4084
 
4085
+ test("rejects an oversized plugin radar response before reading its body", async () => {
4086
+ const originalFetch = globalThis.fetch;
4087
+ let bodyRead = false;
4088
+ globalThis.fetch = () =>
4089
+ Promise.resolve({
4090
+ ok: true,
4091
+ status: 200,
4092
+ headers: new Headers({ "content-length": String(1024 * 1024 + 1) }),
4093
+ body: {
4094
+ getReader() {
4095
+ bodyRead = true;
4096
+ throw new Error("oversized body was read");
4097
+ },
4098
+ },
4099
+ text() {
4100
+ bodyRead = true;
4101
+ return Promise.reject(new Error("oversized body was read"));
4102
+ },
4103
+ } as unknown as Response);
4104
+ try {
4105
+ const { context } = await createContext();
4106
+ const panels = new PiPluginUiRegistry();
4107
+ const tools = new PiToolRegistry();
4108
+ context.provide("piTools", tools);
4109
+ context.provide("piPluginUi", panels);
4110
+ await context.plugin(pluginRadarPlugin, { apiUrl: "https://api.github.test" });
4111
+
4112
+ await expect(namedTool(tools, "plugin_radar_search").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/1 MiB limit/iu);
4113
+ expect(bodyRead).toBe(false);
4114
+ } finally {
4115
+ globalThis.fetch = originalFetch;
4116
+ }
4117
+ });
4118
+
4119
+ test("times out plugin radar requests independently of caller cancellation", async () => {
4120
+ const originalFetch = globalThis.fetch;
4121
+ const caller = new AbortController();
4122
+ globalThis.fetch = (_input, init) =>
4123
+ new Promise<Response>((_resolve, reject) => {
4124
+ const signal = init?.signal;
4125
+ if (signal?.aborted === true) reject(signal.reason instanceof Error ? signal.reason : new Error("Plugin radar request aborted"));
4126
+ else
4127
+ signal?.addEventListener("abort", () => reject(signal.reason instanceof Error ? signal.reason : new Error("Plugin radar request aborted")), {
4128
+ once: true,
4129
+ });
4130
+ });
4131
+ vi.useFakeTimers();
4132
+ try {
4133
+ const { context } = await createContext();
4134
+ const panels = new PiPluginUiRegistry();
4135
+ const tools = new PiToolRegistry();
4136
+ context.provide("piTools", tools);
4137
+ context.provide("piPluginUi", panels);
4138
+ await context.plugin(pluginRadarPlugin, { apiUrl: "https://api.github.test", timeoutMs: 1_000 });
4139
+ const pending = namedTool(tools, "plugin_radar_search").execute("call-1", {}, caller.signal, undefined, {} as never);
4140
+ const timedOut = expect(pending).rejects.toThrow(/timed out after 1000 ms/iu);
4141
+
4142
+ await vi.advanceTimersByTimeAsync(1_000);
4143
+ caller.abort(new Error("test fallback cancellation"));
4144
+ await timedOut;
4145
+ } finally {
4146
+ caller.abort();
4147
+ vi.useRealTimers();
4148
+ globalThis.fetch = originalFetch;
4149
+ }
4150
+ });
4151
+
1915
4152
  test("checks plugin manifests and patches without modifying repositories", async () => {
1916
4153
  const { context, cwd } = await createContext();
1917
4154
  const good = join(cwd, "dsh-good");
@@ -1947,6 +4184,79 @@ describe("Pi domain plugins", () => {
1947
4184
  expect(await readFile(join(good, "cordis.patch.yml"), "utf8")).toBe("- id: dsh-good\n name: dsh-good\n");
1948
4185
  });
1949
4186
 
4187
+ test("keeps plugin repository checks inside the current workspace", async () => {
4188
+ const { context, cwd, agentDir } = await createContext();
4189
+ const outside = join(agentDir, "dsh-outside");
4190
+ await mkdir(join(outside, "src"), { recursive: true });
4191
+ await writeFile(join(outside, "package.json"), JSON.stringify({ name: "dsh-outside", main: "dist/index.js" }), "utf8");
4192
+ await symlink(outside, join(cwd, "dsh-linked"));
4193
+ const tools = new PiToolRegistry();
4194
+ context.provide("piTools", tools);
4195
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4196
+ await context.plugin(pluginCheckPlugin, {});
4197
+
4198
+ await expect(
4199
+ namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-linked" }, undefined, undefined, {} as never),
4200
+ ).rejects.toThrow(/inside the current workspace/iu);
4201
+ });
4202
+
4203
+ test("starts plugin repository checks with default configuration", async () => {
4204
+ const { context } = await createContext();
4205
+ const tools = new PiToolRegistry();
4206
+ context.provide("piTools", tools);
4207
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4208
+ let activationError: unknown;
4209
+ try {
4210
+ await context.plugin(pluginCheckPlugin);
4211
+ } catch (error) {
4212
+ activationError = error;
4213
+ }
4214
+ expect(activationError).toBeUndefined();
4215
+ await expect(namedTool(tools, "plugin_check").execute("call-1", { action: "schema" }, undefined, undefined, {} as never)).resolves.toMatchObject({
4216
+ details: { verdict: "pass" },
4217
+ });
4218
+ });
4219
+
4220
+ test("bounds plugin source reads and reports oversized files as skipped", async () => {
4221
+ const { context, cwd } = await createContext();
4222
+ const repo = join(cwd, "dsh-large-source");
4223
+ await mkdir(join(repo, "src"), { recursive: true });
4224
+ await writeFile(join(repo, "package.json"), JSON.stringify({ name: "dsh-large-source", main: "dist/index.js", scripts: { build: "tsc" } }), "utf8");
4225
+ await writeFile(join(repo, "cordis.patch.yml"), "- id: dsh-large-source\n", "utf8");
4226
+ await writeFile(join(repo, "README.md"), "pi plugin --profile web add github:example/dsh-large-source\n", "utf8");
4227
+ await writeFile(join(repo, "src", "oversized.ts"), Buffer.alloc(1024 * 1024 + 1, 0x20));
4228
+ const tools = new PiToolRegistry();
4229
+ context.provide("piTools", tools);
4230
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4231
+ await context.plugin(pluginCheckPlugin);
4232
+
4233
+ const result = await namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-large-source" }, undefined, undefined, {} as never);
4234
+ const details = result.details as PluginCheckReport;
4235
+ expect(details.sourceScan).toEqual({ checked: 0, skipped: 1, truncated: false });
4236
+ expect(details.warnings.some((warning) => warning.code === "source-scan-incomplete")).toBe(true);
4237
+ });
4238
+
4239
+ test("rejects oversized plugin metadata files before parsing them", async () => {
4240
+ const { context, cwd } = await createContext();
4241
+ const repo = join(cwd, "dsh-large-manifest");
4242
+ await mkdir(join(repo, "src"), { recursive: true });
4243
+ await writeFile(
4244
+ join(repo, "package.json"),
4245
+ JSON.stringify({ name: "dsh-large-manifest", main: "dist/index.js", padding: "x".repeat(1024 * 1024) }),
4246
+ "utf8",
4247
+ );
4248
+ await writeFile(join(repo, "cordis.patch.yml"), "- id: dsh-large-manifest\n", "utf8");
4249
+ const tools = new PiToolRegistry();
4250
+ context.provide("piTools", tools);
4251
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4252
+ await context.plugin(pluginCheckPlugin);
4253
+
4254
+ const result = await namedTool(tools, "plugin_check").execute("call-1", { action: "check", path: "dsh-large-manifest" }, undefined, undefined, {} as never);
4255
+ const details = result.details as PluginCheckReport;
4256
+ const manifestError = details.errors.find((error) => error.code === "no-manifest");
4257
+ expect(manifestError?.message).toMatch(/1 MiB|large/iu);
4258
+ });
4259
+
1950
4260
  test("manages numbered annotations and renders a model-ready prompt block", async () => {
1951
4261
  const { context } = await createContext();
1952
4262
  const panels = new PiPluginUiRegistry();
@@ -1965,6 +4275,12 @@ describe("Pi domain plugins", () => {
1965
4275
  );
1966
4276
  expect(first.details).toMatchObject({ id: 1, quote: "Use the streaming transport", note: "Keep this behavior" });
1967
4277
  await tool.execute("add-2", { action: "add", quote: "Render Markdown with a library" }, undefined, undefined, {} as never);
4278
+ await expect(tool.execute("empty-prompt", { action: "prompt", question: " " }, undefined, undefined, {} as never)).rejects.toThrow(
4279
+ /annotation question is required/iu,
4280
+ );
4281
+ await expect(tool.execute("long-prompt", { action: "prompt", question: "q".repeat(4_001) }, undefined, undefined, {} as never)).rejects.toThrow(
4282
+ /annotation question.*4000/iu,
4283
+ );
1968
4284
  const prompt = await tool.execute("prompt", { action: "prompt", question: "What should we change?" }, undefined, undefined, {} as never);
1969
4285
  const [firstBlock] = prompt.content;
1970
4286
  expect(firstBlock?.type).toBe("text");
@@ -1978,6 +4294,138 @@ describe("Pi domain plugins", () => {
1978
4294
  expect((await panels.snapshot())[0]).toMatchObject({ data: { count: 0, annotations: [] } });
1979
4295
  });
1980
4296
 
4297
+ test("publishes annotation input bounds in the tool parameter schema", async () => {
4298
+ const { context } = await createContext();
4299
+ const tools = new PiToolRegistry();
4300
+ context.provide("piTools", tools);
4301
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4302
+ await context.plugin(annotationPlugin);
4303
+
4304
+ expect(namedTool(tools, "annotation_manage").parameters).toMatchObject({
4305
+ properties: {
4306
+ quote: { maxLength: 4_000 },
4307
+ note: { maxLength: 1_000 },
4308
+ id: { type: "integer", minimum: 1 },
4309
+ question: { maxLength: 4_000 },
4310
+ },
4311
+ });
4312
+ });
4313
+
4314
+ test("rejects an unknown annotation action at the execution boundary", async () => {
4315
+ const { context } = await createContext();
4316
+ const tools = new PiToolRegistry();
4317
+ context.provide("piTools", tools);
4318
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4319
+ await context.plugin(annotationPlugin);
4320
+
4321
+ await expect(namedTool(tools, "annotation_manage").execute("unknown", { action: "archive" }, undefined, undefined, {} as never)).rejects.toThrow(
4322
+ /unknown annotation action/iu,
4323
+ );
4324
+ });
4325
+
4326
+ test("does not expose mutable annotation state through an add result", async () => {
4327
+ const { context } = await createContext();
4328
+ const tools = new PiToolRegistry();
4329
+ context.provide("piTools", tools);
4330
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4331
+ await context.plugin(annotationPlugin);
4332
+ const tool = namedTool(tools, "annotation_manage");
4333
+ const added = await tool.execute("add", { action: "add", quote: "Original" }, undefined, undefined, {} as never);
4334
+ (added.details as { quote: string }).quote = "Mutated";
4335
+
4336
+ await expect(tool.execute("list", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
4337
+ details: { annotations: [{ quote: "Original" }] },
4338
+ });
4339
+ });
4340
+
4341
+ test("does not expose mutable annotation state through a list result", async () => {
4342
+ const { context } = await createContext();
4343
+ const tools = new PiToolRegistry();
4344
+ context.provide("piTools", tools);
4345
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4346
+ await context.plugin(annotationPlugin);
4347
+ const tool = namedTool(tools, "annotation_manage");
4348
+ await tool.execute("add", { action: "add", quote: "Original", note: "Original note" }, undefined, undefined, {} as never);
4349
+ const listed = await tool.execute("list", { action: "list" }, undefined, undefined, {} as never);
4350
+ (listed.details as { annotations: Array<{ note: string }> }).annotations[0]!.note = "Mutated";
4351
+
4352
+ await expect(tool.execute("list-again", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
4353
+ details: { annotations: [{ note: "Original note" }] },
4354
+ });
4355
+ });
4356
+
4357
+ test("enforces annotation action preconditions and bounded collection capacity", async () => {
4358
+ const { context } = await createContext();
4359
+ const tools = new PiToolRegistry();
4360
+ context.provide("piTools", tools);
4361
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4362
+ await context.plugin(annotationPlugin);
4363
+ const tool = namedTool(tools, "annotation_manage");
4364
+
4365
+ await expect(tool.execute("empty-list", { action: "list" }, undefined, undefined, {} as never)).resolves.toMatchObject({
4366
+ details: { count: 0, annotations: [] },
4367
+ });
4368
+ await expect(tool.execute("empty-prompt", { action: "prompt", question: "Question" }, undefined, undefined, {} as never)).rejects.toThrow(
4369
+ /add at least one annotation/iu,
4370
+ );
4371
+ await expect(tool.execute("missing-remove", { action: "remove" }, undefined, undefined, {} as never)).rejects.toThrow(/id is required/iu);
4372
+ await expect(tool.execute("unknown-remove", { action: "remove", id: 1 }, undefined, undefined, {} as never)).rejects.toThrow(/not found/iu);
4373
+ await expect(tool.execute("empty-quote", { action: "add", quote: " " }, undefined, undefined, {} as never)).rejects.toThrow(/quote.*1-4000/iu);
4374
+ await expect(tool.execute("long-quote", { action: "add", quote: "q".repeat(4_001) }, undefined, undefined, {} as never)).rejects.toThrow(/quote.*1-4000/iu);
4375
+ await expect(tool.execute("long-note", { action: "add", quote: "Valid", note: "n".repeat(1_001) }, undefined, undefined, {} as never)).rejects.toThrow(
4376
+ /note.*0-1000/iu,
4377
+ );
4378
+ for (let index = 1; index <= 50; index += 1) {
4379
+ await tool.execute(`add-${index}`, { action: "add", quote: `Quote ${index}` }, undefined, undefined, {} as never);
4380
+ }
4381
+ await expect(tool.execute("over-capacity", { action: "add", quote: "One too many" }, undefined, undefined, {} as never)).rejects.toThrow(
4382
+ /at most 50 annotations/iu,
4383
+ );
4384
+ await tool.execute("remove", { action: "remove", id: 25 }, undefined, undefined, {} as never);
4385
+ await expect(tool.execute("replacement", { action: "add", quote: "Replacement" }, undefined, undefined, {} as never)).resolves.toMatchObject({
4386
+ details: { id: 51 },
4387
+ });
4388
+ });
4389
+
4390
+ test("clears the last annotation prompt together with the collection", async () => {
4391
+ const { context } = await createContext();
4392
+ const tools = new PiToolRegistry();
4393
+ const panels = new PiPluginUiRegistry();
4394
+ context.provide("piTools", tools);
4395
+ context.provide("piPluginUi", panels);
4396
+ await context.plugin(annotationPlugin);
4397
+ const tool = namedTool(tools, "annotation_manage");
4398
+ await tool.execute("add", { action: "add", quote: "Quote" }, undefined, undefined, {} as never);
4399
+ await tool.execute("prompt", { action: "prompt", question: "Question" }, undefined, undefined, {} as never);
4400
+ const beforeClear = (await panels.snapshot())[0];
4401
+ if (beforeClear === undefined) throw new Error("annotation-panel was not registered");
4402
+ expect(beforeClear.data).toMatchObject({ count: 1 });
4403
+ expect(typeof (beforeClear.data as { lastPrompt?: string }).lastPrompt).toBe("string");
4404
+
4405
+ await tool.execute("clear", { action: "clear" }, undefined, undefined, {} as never);
4406
+
4407
+ const panel = (await panels.snapshot())[0];
4408
+ if (panel === undefined) throw new Error("annotation-panel was not registered");
4409
+ expect(panel.data).toMatchObject({ count: 0, annotations: [] });
4410
+ expect((panel.data as { lastPrompt?: string }).lastPrompt).toBeUndefined();
4411
+ });
4412
+
4413
+ test("unregisters the annotation tool and panel when its plugin context is disposed", async () => {
4414
+ const { context } = await createContext();
4415
+ const tools = new PiToolRegistry();
4416
+ const panels = new PiPluginUiRegistry();
4417
+ context.provide("piTools", tools);
4418
+ context.provide("piPluginUi", panels);
4419
+ await context.plugin(annotationPlugin);
4420
+ expect(tools.snapshot().customTools.map((tool) => tool.name)).toEqual(["annotation_manage"]);
4421
+ await expect(panels.snapshot()).resolves.toMatchObject([{ id: "annotation-panel" }]);
4422
+
4423
+ await context.fiber.dispose();
4424
+
4425
+ expect(tools.snapshot().customTools).toEqual([]);
4426
+ await expect(panels.snapshot()).resolves.toEqual([]);
4427
+ });
4428
+
1981
4429
  test("persists completed session costs and exposes a daily budget report", async () => {
1982
4430
  const { context, agentDir } = await createContext();
1983
4431
  const panels = new PiPluginUiRegistry();
@@ -1998,7 +4446,6 @@ describe("Pi domain plugins", () => {
1998
4446
  context.provide("piRuntime", { session: { getSessionStats: () => sessionStats } } as never);
1999
4447
  await context.plugin(costMeterPlugin, { dailyBudget: 5 });
2000
4448
  context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false });
2001
- await new Promise((resolve) => setTimeout(resolve, 10));
2002
4449
  sessionStats = {
2003
4450
  ...sessionStats,
2004
4451
  assistantMessages: 3,
@@ -2007,7 +4454,6 @@ describe("Pi domain plugins", () => {
2007
4454
  cost: 2,
2008
4455
  };
2009
4456
  context.emit("pi/session-event", { type: "agent_end", messages: [], willRetry: false });
2010
- await new Promise((resolve) => setTimeout(resolve, 10));
2011
4457
  const tool = tools.snapshot().customTools.find((entry) => entry.name === "cost_report");
2012
4458
  if (tool === undefined) throw new Error("cost_report was not registered");
2013
4459
  const report = await tool.execute("report", {}, undefined, undefined, {} as never);
@@ -2019,6 +4465,130 @@ describe("Pi domain plugins", () => {
2019
4465
  expect(await readFile(join(agentDir, "cost-meter.json"), "utf8")).toContain("session-1");
2020
4466
  });
2021
4467
 
4468
+ test("does not read cost history through a symbolic link", async () => {
4469
+ const { context, cwd, agentDir } = await createContext();
4470
+ const outside = join(cwd, "outside-costs.json");
4471
+ await writeFile(
4472
+ outside,
4473
+ JSON.stringify({ version: 1, entries: [{ sessionId: "secret", cost: 99, tokens: 1, messages: 1, recordedAt: new Date().toISOString() }] }),
4474
+ "utf8",
4475
+ );
4476
+ await symlink(outside, join(agentDir, "cost-meter.json"));
4477
+ const tools = new PiToolRegistry();
4478
+ context.provide("piTools", tools);
4479
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4480
+ context.provide("piRuntime", {
4481
+ session: {
4482
+ getSessionStats: () => ({
4483
+ sessionFile: undefined,
4484
+ sessionId: "current",
4485
+ userMessages: 0,
4486
+ assistantMessages: 0,
4487
+ toolCalls: 0,
4488
+ toolResults: 0,
4489
+ totalMessages: 0,
4490
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
4491
+ cost: 0,
4492
+ }),
4493
+ },
4494
+ } as never);
4495
+ await context.plugin(costMeterPlugin);
4496
+
4497
+ await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/symbolic link|regular file/iu);
4498
+ });
4499
+
4500
+ test("rejects oversized cost history before parsing it", async () => {
4501
+ const { context, agentDir } = await createContext();
4502
+ await writeFile(join(agentDir, "cost-meter.json"), Buffer.alloc(4 * 1024 * 1024 + 1));
4503
+ const tools = new PiToolRegistry();
4504
+ context.provide("piTools", tools);
4505
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4506
+ context.provide("piRuntime", {
4507
+ session: {
4508
+ getSessionStats: () => ({
4509
+ sessionFile: undefined,
4510
+ sessionId: "current",
4511
+ userMessages: 0,
4512
+ assistantMessages: 0,
4513
+ toolCalls: 0,
4514
+ toolResults: 0,
4515
+ totalMessages: 0,
4516
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
4517
+ cost: 0,
4518
+ }),
4519
+ },
4520
+ } as never);
4521
+ await context.plugin(costMeterPlugin);
4522
+
4523
+ await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/4 MiB|size|limit/iu);
4524
+ });
4525
+
4526
+ test("rolls back failed cost records and recovers the persistence queue", async () => {
4527
+ const { context, agentDir } = await createContext();
4528
+ const costPath = join(agentDir, "cost-meter.json");
4529
+ await writeFile(costPath, JSON.stringify({ version: 1, entries: [] }), "utf8");
4530
+ const tools = new PiToolRegistry();
4531
+ context.provide("piTools", tools);
4532
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4533
+ let sessionStats = {
4534
+ sessionFile: undefined,
4535
+ sessionId: "failed-session",
4536
+ userMessages: 1,
4537
+ assistantMessages: 1,
4538
+ toolCalls: 0,
4539
+ toolResults: 0,
4540
+ totalMessages: 2,
4541
+ tokens: { input: 10, output: 10, cacheRead: 0, cacheWrite: 0, total: 20 },
4542
+ cost: 1,
4543
+ };
4544
+ context.provide("piRuntime", { session: { getSessionStats: () => sessionStats } } as never);
4545
+ await context.plugin(costMeterPlugin);
4546
+ const report = namedTool(tools, "cost_report");
4547
+ await report.execute("load", {}, undefined, undefined, {} as never);
4548
+ await rm(costPath);
4549
+ await mkdir(costPath);
4550
+
4551
+ await expect(report.execute("call-1", { refresh: true }, undefined, undefined, {} as never)).rejects.toThrow();
4552
+ await rm(costPath, { recursive: true });
4553
+ sessionStats = { ...sessionStats, sessionId: "saved-session", cost: 2 };
4554
+ await expect(report.execute("call-2", { refresh: true }, undefined, undefined, {} as never)).resolves.toMatchObject({
4555
+ details: { entries: [{ sessionId: "saved-session" }] },
4556
+ });
4557
+ const persisted = JSON.parse(await readFile(costPath, "utf8")) as { entries: Array<{ sessionId: string }> };
4558
+ expect(persisted.entries.map((entry) => entry.sessionId)).toEqual(["saved-session"]);
4559
+ expect((await readdir(agentDir)).filter((name) => name.startsWith(".cost-meter.json.") && name.endsWith(".tmp"))).toEqual([]);
4560
+ });
4561
+
4562
+ test("rejects malformed cost entries instead of reporting partial data", async () => {
4563
+ const { context, agentDir } = await createContext();
4564
+ await writeFile(
4565
+ join(agentDir, "cost-meter.json"),
4566
+ JSON.stringify({ version: 1, entries: [{ sessionId: "partial", cost: 1, recordedAt: new Date().toISOString() }] }),
4567
+ "utf8",
4568
+ );
4569
+ const tools = new PiToolRegistry();
4570
+ context.provide("piTools", tools);
4571
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4572
+ context.provide("piRuntime", {
4573
+ session: {
4574
+ getSessionStats: () => ({
4575
+ sessionFile: undefined,
4576
+ sessionId: "current",
4577
+ userMessages: 0,
4578
+ assistantMessages: 0,
4579
+ toolCalls: 0,
4580
+ toolResults: 0,
4581
+ totalMessages: 0,
4582
+ tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
4583
+ cost: 0,
4584
+ }),
4585
+ },
4586
+ } as never);
4587
+ await context.plugin(costMeterPlugin);
4588
+
4589
+ await expect(namedTool(tools, "cost_report").execute("call-1", {}, undefined, undefined, {} as never)).rejects.toThrow(/invalid entries/iu);
4590
+ });
4591
+
2022
4592
  test("creates, diffs, lists, and safely restores a workspace savepoint", async () => {
2023
4593
  const { context, cwd, agentDir } = await createContext();
2024
4594
  await mkdir(join(cwd, "src"), { recursive: true });
@@ -2086,6 +4656,69 @@ describe("Pi domain plugins", () => {
2086
4656
  expect((await panels.snapshot())[0]).toMatchObject({ id: "skill-catalog-panel", data: { skillCount: 1, mcpCount: 1 } });
2087
4657
  });
2088
4658
 
4659
+ test("rejects non-file skill catalog entries before reading them", async () => {
4660
+ const { context, cwd } = await createContext();
4661
+ const skillPath = join(cwd, ".pi", "skills", "directory-skill", "SKILL.md");
4662
+ await mkdir(skillPath, { recursive: true });
4663
+ const tools = new PiToolRegistry();
4664
+ context.provide("piTools", tools);
4665
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4666
+ context.provide("piResources", {
4667
+ resourceLoader: {
4668
+ getSkills: () => ({
4669
+ skills: [
4670
+ {
4671
+ name: "directory-skill",
4672
+ description: "Invalid directory entry",
4673
+ filePath: skillPath,
4674
+ baseDir: dirname(skillPath),
4675
+ sourceInfo: { source: "test", scope: "project" },
4676
+ disableModelInvocation: false,
4677
+ },
4678
+ ],
4679
+ diagnostics: [],
4680
+ }),
4681
+ },
4682
+ } as never);
4683
+ context.provide("piMcp", { snapshot: () => ({ servers: [] }) });
4684
+ await context.plugin(skillCatalogPlugin);
4685
+
4686
+ await expect(
4687
+ namedTool(tools, "skill_catalog").execute("read", { action: "read", name: "directory-skill" }, undefined, undefined, {} as never),
4688
+ ).rejects.toThrow(/regular file/iu);
4689
+ });
4690
+
4691
+ test("scans loaded skills through the bounded skill-guard plugin path", async () => {
4692
+ const { context, cwd } = await createContext();
4693
+ const skillPath = join(cwd, "oversized-skill.md");
4694
+ await writeFile(skillPath, Buffer.alloc(128 * 1024 + 1, 0x20));
4695
+ const tools = new PiToolRegistry();
4696
+ context.provide("piTools", tools);
4697
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4698
+ context.provide("piResources", {
4699
+ resourceLoader: {
4700
+ getSkills: () => ({
4701
+ skills: [
4702
+ {
4703
+ name: "oversized",
4704
+ description: "Oversized fixture",
4705
+ filePath: skillPath,
4706
+ baseDir: cwd,
4707
+ sourceInfo: { source: "test", scope: "project" },
4708
+ disableModelInvocation: false,
4709
+ },
4710
+ ],
4711
+ diagnostics: [],
4712
+ }),
4713
+ },
4714
+ } as never);
4715
+ await context.plugin(skillGuardPlugin);
4716
+
4717
+ await expect(namedTool(tools, "skill_guard_scan").execute("scan", {}, undefined, undefined, {} as never)).resolves.toMatchObject({
4718
+ details: { total: 1, review: 1, reports: [{ name: "oversized", findings: [{ code: "size_limit" }] }] },
4719
+ });
4720
+ });
4721
+
2089
4722
  test("reports MCP server health and bridged tools through the console plugin", async () => {
2090
4723
  const { context, agentDir } = await createContext();
2091
4724
  const panels = new PiPluginUiRegistry();
@@ -2144,7 +4777,29 @@ describe("Pi domain plugins", () => {
2144
4777
  {} as never,
2145
4778
  );
2146
4779
  expect(await readFile(patchPath, "utf8")).toContain("mcp-search");
2147
- expect(await readFile(`${patchPath}.bak`, "utf8")).toBe("");
4780
+ expect(await readFile(`${patchPath}.bak`, "utf8")).toContain("mcp-docs");
2148
4781
  expect((await panels.snapshot())[0]).toMatchObject({ id: "mcp-panel", data: { servers: [{ id: "docs", toolCount: 1 }] } });
2149
4782
  });
4783
+
4784
+ test("rejects oversized MCP patch files before reading or backing them up", async () => {
4785
+ const { context, agentDir } = await createContext();
4786
+ const patchPath = join(agentDir, "cordis.patch.yml");
4787
+ await writeFile(patchPath, Buffer.alloc(2 * 1024 * 1024 + 1));
4788
+ const tools = new PiToolRegistry();
4789
+ context.provide("piTools", tools);
4790
+ context.provide("piPluginUi", new PiPluginUiRegistry());
4791
+ context.provide("piMcp", { snapshot: () => ({ servers: [] }) });
4792
+ await context.plugin(mcpPanelPlugin, { patchPath });
4793
+
4794
+ await expect(
4795
+ namedTool(tools, "mcp_panel").execute(
4796
+ "apply",
4797
+ { action: "apply", serverId: "docs", command: ["node", "server.js"], confirm: true },
4798
+ undefined,
4799
+ undefined,
4800
+ {} as never,
4801
+ ),
4802
+ ).rejects.toThrow(/2 MiB|size|limit/iu);
4803
+ await expect(stat(`${patchPath}.bak`)).rejects.toMatchObject({ code: "ENOENT" });
4804
+ });
2150
4805
  });