dsh-pstack 0.3.0-beta.1 → 0.3.1

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 (544) hide show
  1. package/README.md +122 -76
  2. package/README.zh.md +116 -70
  3. package/assets/agent-presets.png +0 -0
  4. package/assets/plugin-configure.png +0 -0
  5. package/assets/plugin-recommended.png +0 -0
  6. package/assets/plugin-roles.png +0 -0
  7. package/assets/session-preset.png +0 -0
  8. package/cordis.patch.yml +332 -24
  9. package/dist/budget.d.mts +82 -0
  10. package/dist/budget.mjs +293 -0
  11. package/dist/catalogue-wgVaGeUx.d.mts +27 -0
  12. package/dist/catalogue.d.mts +2 -0
  13. package/dist/catalogue.mjs +116 -0
  14. package/dist/client.js +4072 -0
  15. package/dist/index.d.mts +2 -87
  16. package/dist/index.mjs +3 -394
  17. package/dist/models.d.mts +11 -40
  18. package/dist/models.mjs +47 -107
  19. package/dist/paths.d.mts +13 -0
  20. package/dist/paths.mjs +26 -0
  21. package/dist/poteto-mode.d.mts +37 -0
  22. package/dist/poteto-mode.mjs +61 -0
  23. package/dist/preset-copy.d.mts +12 -0
  24. package/dist/preset-copy.mjs +64 -0
  25. package/dist/preset.d.mts +31 -0
  26. package/dist/preset.mjs +51 -0
  27. package/dist/pstack/docs/harness/dsh.md +40 -40
  28. package/dist/pstack/plugin.json +21 -0
  29. package/dist/pstack/skills/poteto-mode/scripts/bootstrap.ts +62 -0
  30. package/dist/pstack/skills/poteto-mode/scripts/bun.lock +67 -0
  31. package/dist/pstack/skills/poteto-mode/scripts/check-plan.mjs +70 -83
  32. package/dist/pstack/skills/poteto-mode/scripts/{check-plan.test.mjs → check-plan.test.ts} +24 -42
  33. package/dist/pstack/skills/poteto-mode/scripts/package.json +16 -0
  34. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
  35. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
  36. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +129 -0
  37. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts +498 -0
  38. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.ts +726 -0
  39. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.ts +482 -0
  40. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.ts +842 -0
  41. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
  42. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
  43. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
  44. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.ts +414 -0
  45. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
  46. package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.sh +134 -0
  47. package/dist/pstack/skills/show-me-your-work/scripts/log.sh +42 -0
  48. package/dist/pstack/skills/statechart/scripts/backends/cfg.ts +33 -0
  49. package/dist/pstack/skills/statechart/scripts/backends/mermaid.ts +201 -0
  50. package/dist/pstack/skills/statechart/scripts/backends/plain-text.ts +72 -0
  51. package/dist/pstack/skills/statechart/scripts/backends/report.ts +177 -0
  52. package/dist/pstack/skills/statechart/scripts/frontends/config.ts +276 -0
  53. package/dist/pstack/skills/statechart/scripts/frontends/dot.ts +74 -0
  54. package/dist/pstack/skills/statechart/scripts/frontends/marks.ts +93 -0
  55. package/dist/pstack/skills/statechart/scripts/frontends/messages.ts +52 -0
  56. package/dist/pstack/skills/statechart/scripts/frontends/spec.ts +114 -0
  57. package/dist/pstack/skills/statechart/scripts/frontends/trace.ts +24 -0
  58. package/dist/pstack/skills/statechart/scripts/frontends/value.ts +166 -0
  59. package/dist/pstack/skills/statechart/scripts/frontends/verdict.ts +40 -0
  60. package/dist/pstack/skills/statechart/scripts/frontends/view.ts +119 -0
  61. package/dist/pstack/skills/statechart/scripts/ir.ts +547 -0
  62. package/dist/pstack/skills/statechart/scripts/package.json +11 -0
  63. package/dist/pstack/skills/statechart/scripts/passes/annotate.ts +128 -0
  64. package/dist/pstack/skills/statechart/scripts/passes/label.ts +269 -0
  65. package/dist/pstack/skills/statechart/scripts/passes/lift.ts +85 -0
  66. package/dist/pstack/skills/statechart/scripts/passes/loop-check.ts +68 -0
  67. package/dist/pstack/skills/statechart/scripts/passes/paths.ts +60 -0
  68. package/dist/pstack/skills/statechart/scripts/passes/reach.ts +135 -0
  69. package/dist/pstack/skills/statechart/scripts/passes/region-check.ts +97 -0
  70. package/dist/pstack/skills/statechart/scripts/passes/regions.ts +295 -0
  71. package/dist/pstack/skills/statechart/scripts/pipeline.ts +76 -0
  72. package/dist/pstack/skills/statechart/scripts/run.ts +481 -0
  73. package/dist/pstack/skills/statechart/scripts/statechart +29 -0
  74. package/dist/pstack/skills/statechart/scripts/statechart.ts +164 -0
  75. package/dist/pstack/skills/statechart/scripts/test/ConfigOracle.java +63 -0
  76. package/dist/pstack/skills/statechart/scripts/test/chart.test.ts +1210 -0
  77. package/dist/pstack/skills/statechart/scripts/test/charts/Braces.mmd +7 -0
  78. package/dist/pstack/skills/statechart/scripts/test/charts/Escapes.mmd +11 -0
  79. package/dist/pstack/skills/statechart/scripts/test/charts/Phases.mmd +29 -0
  80. package/dist/pstack/skills/statechart/scripts/test/cli.test.ts +1314 -0
  81. package/dist/pstack/skills/statechart/scripts/test/config-fuzz.test.ts +263 -0
  82. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/ChartRun.out +57 -0
  83. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/Counter.cfg +3 -0
  84. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/Counter.tla +10 -0
  85. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/chart.mmd +9 -0
  86. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/graph.dot +16 -0
  87. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/report.md +7 -0
  88. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/sany.out +2 -0
  89. package/dist/pstack/skills/statechart/scripts/test/corpus/Counter/verdict-1.out +49 -0
  90. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/ChartRun.out +60 -0
  91. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.cfg +4 -0
  92. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.tla +22 -0
  93. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/chart.mmd +26 -0
  94. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/graph.dot +19 -0
  95. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/report.md +20 -0
  96. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/sany.out +2 -0
  97. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.exit +1 -0
  98. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.out +56 -0
  99. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.exit +1 -0
  100. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.out +82 -0
  101. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-3.out +57 -0
  102. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/ChartRun.out +59 -0
  103. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.cfg +3 -0
  104. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.tla +11 -0
  105. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/chart.mmd +22 -0
  106. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/graph.dot +16 -0
  107. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/report.md +17 -0
  108. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/sany.out +2 -0
  109. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.exit +1 -0
  110. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.out +56 -0
  111. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.exit +1 -0
  112. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.out +77 -0
  113. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/ChartRun.out +69 -0
  114. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.cfg +2 -0
  115. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.tla +11 -0
  116. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/chart.mmd +85 -0
  117. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/graph.dot +70 -0
  118. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/report.md +12 -0
  119. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/sany.out +2 -0
  120. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.exit +1 -0
  121. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.out +67 -0
  122. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-2.out +57 -0
  123. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.exit +1 -0
  124. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.out +85 -0
  125. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.cfg +6 -0
  126. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.tla +13 -0
  127. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/chart.mmd +5 -0
  128. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/graph.dot +10 -0
  129. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/report.md +31 -0
  130. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/sany.out +2 -0
  131. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.exit +1 -0
  132. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.out +64 -0
  133. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.exit +1 -0
  134. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.out +77 -0
  135. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/ChartRun.out +55 -0
  136. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.cfg +3 -0
  137. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.tla +10 -0
  138. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/chart.mmd +12 -0
  139. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/graph.dot +6 -0
  140. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/report.md +12 -0
  141. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/sany.out +2 -0
  142. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.exit +1 -0
  143. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.out +33 -0
  144. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-2.out +54 -0
  145. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/ChartRun.out +54 -0
  146. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.cfg +3 -0
  147. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.tla +10 -0
  148. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/chart.mmd +1 -0
  149. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/graph.dot +6 -0
  150. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/report.md +7 -0
  151. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/sany.out +2 -0
  152. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/verdict-1.out +46 -0
  153. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/ChartRun.out +59 -0
  154. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.cfg +2 -0
  155. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.tla +10 -0
  156. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterSteps.tla +6 -0
  157. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/chart.mmd +17 -0
  158. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/graph.dot +15 -0
  159. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/report.md +14 -0
  160. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/sany.out +3 -0
  161. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.exit +1 -0
  162. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.out +78 -0
  163. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/ChartRun.out +59 -0
  164. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.cfg +3 -0
  165. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.tla +19 -0
  166. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/chart.mmd +26 -0
  167. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/graph.dot +22 -0
  168. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/report.md +12 -0
  169. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/sany.out +2 -0
  170. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.exit +1 -0
  171. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.out +58 -0
  172. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-2.out +57 -0
  173. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/ChartRun.out +57 -0
  174. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.cfg +2 -0
  175. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.tla +11 -0
  176. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/chart.mmd +17 -0
  177. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/graph.dot +15 -0
  178. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/report.md +14 -0
  179. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/sany.out +2 -0
  180. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.exit +1 -0
  181. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.out +76 -0
  182. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/ChartRun.out +59 -0
  183. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.cfg +3 -0
  184. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.tla +11 -0
  185. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/chart.mmd +19 -0
  186. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/graph.dot +10 -0
  187. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/report.md +15 -0
  188. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/sany.out +2 -0
  189. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.exit +1 -0
  190. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.out +56 -0
  191. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.exit +1 -0
  192. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.out +67 -0
  193. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/ChartRun.out +62 -0
  194. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.cfg +6 -0
  195. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.tla +24 -0
  196. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/Moves.tla +5 -0
  197. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/chart.mmd +22 -0
  198. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/graph.dot +10 -0
  199. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/report.md +23 -0
  200. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/sany.out +3 -0
  201. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.exit +1 -0
  202. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.out +58 -0
  203. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.exit +1 -0
  204. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.out +66 -0
  205. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.exit +1 -0
  206. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.out +74 -0
  207. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-4.out +59 -0
  208. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/ChartRun.out +58 -0
  209. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.cfg +3 -0
  210. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.tla +15 -0
  211. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/chart.mmd +18 -0
  212. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/graph.dot +13 -0
  213. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/report.md +11 -0
  214. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/sany.out +2 -0
  215. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.exit +1 -0
  216. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.out +33 -0
  217. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-2.out +57 -0
  218. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/ChartRun.out +57 -0
  219. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.cfg +3 -0
  220. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.tla +15 -0
  221. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/chart.mmd +7 -0
  222. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/graph.dot +13 -0
  223. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/report.md +6 -0
  224. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/sany.out +2 -0
  225. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterStop/verdict-1.out +49 -0
  226. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/ChartRun.out +61 -0
  227. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.cfg +4 -0
  228. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.tla +14 -0
  229. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/chart.mmd +30 -0
  230. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/graph.dot +19 -0
  231. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/report.md +27 -0
  232. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/sany.out +2 -0
  233. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.exit +1 -0
  234. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.out +56 -0
  235. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.exit +1 -0
  236. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.out +64 -0
  237. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.exit +1 -0
  238. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.out +87 -0
  239. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-4.out +57 -0
  240. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/ChartRun.out +60 -0
  241. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.cfg +3 -0
  242. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.tla +12 -0
  243. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/chart.mmd +23 -0
  244. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/graph.dot +19 -0
  245. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/report.md +11 -0
  246. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/sany.out +2 -0
  247. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.exit +1 -0
  248. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.out +56 -0
  249. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-2.out +57 -0
  250. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/ChartRun.out +61 -0
  251. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/CounterView.cfg +4 -0
  252. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/CounterView.tla +14 -0
  253. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/chart.mmd +23 -0
  254. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/graph.dot +19 -0
  255. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/report.md +27 -0
  256. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/sany.out +2 -0
  257. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-1.exit +1 -0
  258. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-1.out +56 -0
  259. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-2.exit +1 -0
  260. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-2.out +64 -0
  261. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-3.exit +1 -0
  262. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-3.out +87 -0
  263. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-4.out +57 -0
  264. package/dist/pstack/skills/statechart/scripts/test/corpus/CounterView/view.mjs +1 -0
  265. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/ChartRun.out +59 -0
  266. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/DieHard.cfg +4 -0
  267. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/DieHard.tla +38 -0
  268. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/chart.mmd +92 -0
  269. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/graph.dot +126 -0
  270. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/report.md +17 -0
  271. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/sany.out +2 -0
  272. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-1.exit +1 -0
  273. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-1.out +91 -0
  274. package/dist/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-2.out +57 -0
  275. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/ChartRun.out +61 -0
  276. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.cfg +6 -0
  277. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.tla +11 -0
  278. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/chart.mmd +22 -0
  279. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/graph.dot +16 -0
  280. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/report.md +25 -0
  281. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/sany.out +2 -0
  282. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.exit +1 -0
  283. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.out +56 -0
  284. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.exit +1 -0
  285. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.out +64 -0
  286. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.exit +1 -0
  287. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.out +64 -0
  288. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.exit +1 -0
  289. package/dist/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.out +77 -0
  290. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/ChartRun.out +57 -0
  291. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.cfg +4 -0
  292. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.tla +11 -0
  293. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/chart.mmd +13 -0
  294. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/graph.dot +12 -0
  295. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/report.md +11 -0
  296. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/sany.out +2 -0
  297. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.exit +1 -0
  298. package/dist/pstack/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.out +72 -0
  299. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/ChartRun.out +57 -0
  300. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/Login.cfg +3 -0
  301. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/Login.tla +62 -0
  302. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/chart.mmd +27 -0
  303. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/graph.dot +40 -0
  304. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/report.md +7 -0
  305. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/sany.out +1 -0
  306. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/verdict-1.out +47 -0
  307. package/dist/pstack/skills/statechart/scripts/test/corpus/Login/view.mjs +1 -0
  308. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/ChartRun.out +57 -0
  309. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.cfg +2 -0
  310. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.tla +64 -0
  311. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/chart.mmd +28 -0
  312. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/graph.dot +37 -0
  313. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/report.md +8 -0
  314. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/sany.out +1 -0
  315. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/verdict-1.out +47 -0
  316. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/view.mjs +1 -0
  317. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/ChartRun.out +60 -0
  318. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.cfg +3 -0
  319. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.tla +64 -0
  320. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/chart.mmd +44 -0
  321. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/graph.dot +58 -0
  322. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/report.md +17 -0
  323. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/sany.out +1 -0
  324. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.exit +1 -0
  325. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.out +83 -0
  326. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-2.out +57 -0
  327. package/dist/pstack/skills/statechart/scripts/test/corpus/LoginParallel/view.mjs +1 -0
  328. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/ChartRun.out +60 -0
  329. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.cfg +3 -0
  330. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.tla +103 -0
  331. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/chart.mmd +43 -0
  332. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/graph.dot +93 -0
  333. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/report.md +14 -0
  334. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/sany.out +1 -0
  335. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.exit +1 -0
  336. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.out +59 -0
  337. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-2.out +72 -0
  338. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/view.mjs +3 -0
  339. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/ChartRun.out +57 -0
  340. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.cfg +2 -0
  341. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.tla +100 -0
  342. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/chart.mmd +31 -0
  343. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/graph.dot +85 -0
  344. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/report.md +9 -0
  345. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/sany.out +1 -0
  346. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/verdict-1.out +47 -0
  347. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/view.mjs +3 -0
  348. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/ChartRun.out +59 -0
  349. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayer.tla +103 -0
  350. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.cfg +3 -0
  351. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.tla +6 -0
  352. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/chart.mmd +42 -0
  353. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/graph.dot +93 -0
  354. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/report.md +19 -0
  355. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/sany.out +2 -0
  356. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.exit +1 -0
  357. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.out +61 -0
  358. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-2.out +59 -0
  359. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-3.out +61 -0
  360. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.exit +1 -0
  361. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.out +66 -0
  362. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.exit +1 -0
  363. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.out +58 -0
  364. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-6.out +49 -0
  365. package/dist/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/view.mjs +3 -0
  366. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/ChartRun.out +57 -0
  367. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.cfg +6 -0
  368. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.tla +11 -0
  369. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/chart.mmd +5 -0
  370. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/graph.dot +11 -0
  371. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/report.md +7 -0
  372. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/sany.out +5 -0
  373. package/dist/pstack/skills/statechart/scripts/test/corpus/PickWorker/verdict-1.out +70 -0
  374. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/ChartRun.out +61 -0
  375. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/Shadow.cfg +4 -0
  376. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/Shadow.tla +11 -0
  377. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/ShadowLib.tla +6 -0
  378. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/ShadowSub.tla +3 -0
  379. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/chart.mmd +9 -0
  380. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/graph.dot +16 -0
  381. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/report.md +8 -0
  382. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/sany.out +4 -0
  383. package/dist/pstack/skills/statechart/scripts/test/corpus/Shadow/verdict-1.out +65 -0
  384. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/ChartRun.out +59 -0
  385. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.cfg +2 -0
  386. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.tla +9 -0
  387. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/chart.mmd +23 -0
  388. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/graph.dot +17 -0
  389. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/report.md +15 -0
  390. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/sany.out +1 -0
  391. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.exit +1 -0
  392. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.out +65 -0
  393. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.exit +1 -0
  394. package/dist/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.out +75 -0
  395. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/ChartRun.out +69 -0
  396. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.cfg +3 -0
  397. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.tla +75 -0
  398. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/chart.mmd +60 -0
  399. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/graph.dot +297 -0
  400. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/report.md +20 -0
  401. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/sany.out +1 -0
  402. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.exit +1 -0
  403. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.out +95 -0
  404. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-2.out +57 -0
  405. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhase/view.mjs +1 -0
  406. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/ChartRun.out +57 -0
  407. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.cfg +3 -0
  408. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.tla +75 -0
  409. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/chart.mmd +37 -0
  410. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/graph.dot +223 -0
  411. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/report.md +11 -0
  412. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/sany.out +1 -0
  413. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/verdict-1.out +47 -0
  414. package/dist/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/view.mjs +1 -0
  415. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.exit +1 -0
  416. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.out +75 -0
  417. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.cfg +5 -0
  418. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.tla +11 -0
  419. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/chart.mmd +18 -0
  420. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/graph.dot +14 -0
  421. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/report.md +29 -0
  422. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/sany.out +2 -0
  423. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.exit +1 -0
  424. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.out +64 -0
  425. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.exit +1 -0
  426. package/dist/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.out +75 -0
  427. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/ChartRun.out +58 -0
  428. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.cfg +2 -0
  429. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.tla +8 -0
  430. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/chart.mmd +22 -0
  431. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/graph.dot +16 -0
  432. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/report.md +15 -0
  433. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/sany.out +2 -0
  434. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.exit +1 -0
  435. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.out +56 -0
  436. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.exit +1 -0
  437. package/dist/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.out +77 -0
  438. package/dist/pstack/skills/statechart/scripts/test/corpus.test.ts +403 -0
  439. package/dist/pstack/skills/statechart/scripts/test/differential.test.ts +565 -0
  440. package/dist/pstack/skills/statechart/scripts/test/grid/Grid.cfg +2 -0
  441. package/dist/pstack/skills/statechart/scripts/test/grid/Grid.tla +9 -0
  442. package/dist/pstack/skills/statechart/scripts/test/grid/sany.out +2 -0
  443. package/dist/pstack/skills/statechart/scripts/test/java.ts +63 -0
  444. package/dist/pstack/skills/statechart/scripts/test/labels.ts +643 -0
  445. package/dist/pstack/skills/statechart/scripts/test/messages.test.ts +212 -0
  446. package/dist/pstack/skills/statechart/scripts/test/pipeline.test.ts +270 -0
  447. package/dist/pstack/skills/statechart/scripts/test/plain-tlc.test.ts +255 -0
  448. package/dist/pstack/skills/statechart/scripts/test/render.test.ts +81 -0
  449. package/dist/pstack/skills/statechart/scripts/test/tlc-out.test.ts +90 -0
  450. package/dist/pstack/skills/statechart/scripts/test/tlc.ts +158 -0
  451. package/dist/pstack/skills/statechart/scripts/tsconfig.json +11 -0
  452. package/dist/pstack/skills/statechart/scripts/worker.ts +35 -0
  453. package/dist/readonly.d.mts +3 -0
  454. package/dist/readonly.mjs +9 -0
  455. package/dist/role-catalog.d.mts +11 -24
  456. package/dist/role-catalog.mjs +17 -32
  457. package/dist/role-config.d.mts +41 -0
  458. package/dist/role-config.mjs +112 -0
  459. package/dist/role-editor.d.mts +25 -0
  460. package/dist/role-editor.mjs +372 -0
  461. package/dist/role-file-CIVHnhBg.d.mts +53 -0
  462. package/dist/role-file.d.mts +2 -0
  463. package/dist/role-file.mjs +121 -0
  464. package/dist/{role-manifest-8OERX9-s.d.mts → role-manifest-D1npMjqZ.d.mts} +21 -10
  465. package/dist/role-manifest.d.mts +2 -0
  466. package/dist/{role-manifest-79cnFfDM.mjs → role-manifest.mjs} +20 -16
  467. package/dist/role-remote.d.mts +23 -0
  468. package/dist/role-remote.mjs +106 -0
  469. package/dist/{roles.d.mts → role-service.d.mts} +8 -4
  470. package/dist/role-service.mjs +43 -0
  471. package/dist/role-wire.d.mts +54 -0
  472. package/dist/role-wire.mjs +127 -0
  473. package/dist/route-7SqXDYrL.d.mts +36 -0
  474. package/dist/route.d.mts +2 -0
  475. package/dist/route.mjs +78 -0
  476. package/dist/skill.d.mts +9 -23
  477. package/dist/skill.mjs +28 -37
  478. package/dist/skills/authored/docs-drift/SKILL.md +4 -4
  479. package/dist/skills/authored/docs-drift/SKILL.zh.md +4 -4
  480. package/dist/skills/vendored/control-cli/SKILL.md +109 -0
  481. package/dist/skills/vendored/control-ui/SKILL.md +109 -0
  482. package/dist/skills/vendored/deslop/SKILL.md +22 -0
  483. package/dist/subagent.d.mts +60 -169
  484. package/dist/subagent.mjs +344 -587
  485. package/dist/wire-error-D_r-UW4w.d.mts +5 -0
  486. package/dist/wire-error.d.mts +2 -0
  487. package/dist/wire-error.mjs +9 -0
  488. package/locale/en.json +1 -1
  489. package/package.json +51 -35
  490. package/assets/pstack-config-entry.png +0 -0
  491. package/dist/client.mjs +0 -2482
  492. package/dist/client.mjs.map +0 -1
  493. package/dist/host-contract-CUPSpeNk.d.mts +0 -22
  494. package/dist/paths-yji9SNak.mjs +0 -47
  495. package/dist/pstack/skills/poteto-mode/scripts/expect.mjs +0 -254
  496. package/dist/pstack/skills/poteto-mode/scripts/support.mjs +0 -93
  497. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +0 -123
  498. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -217
  499. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +0 -119
  500. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +0 -626
  501. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -521
  502. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +0 -657
  503. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -523
  504. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +0 -106
  505. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +0 -9
  506. package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -5
  507. package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.mjs +0 -668
  508. package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +0 -134
  509. package/dist/pstack/skills/show-me-your-work/scripts/log.mjs +0 -106
  510. package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +0 -56
  511. package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +0 -31
  512. package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +0 -186
  513. package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +0 -62
  514. package/dist/pstack/skills/statechart/scripts/backends/report.mjs +0 -84
  515. package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +0 -268
  516. package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +0 -67
  517. package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +0 -94
  518. package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +0 -48
  519. package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +0 -101
  520. package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +0 -39
  521. package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +0 -180
  522. package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +0 -40
  523. package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +0 -99
  524. package/dist/pstack/skills/statechart/scripts/ir.mjs +0 -277
  525. package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +0 -92
  526. package/dist/pstack/skills/statechart/scripts/passes/label.mjs +0 -210
  527. package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +0 -84
  528. package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +0 -47
  529. package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +0 -72
  530. package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +0 -135
  531. package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +0 -82
  532. package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +0 -268
  533. package/dist/pstack/skills/statechart/scripts/pipeline.mjs +0 -72
  534. package/dist/pstack/skills/statechart/scripts/run.mjs +0 -440
  535. package/dist/pstack/skills/statechart/scripts/statechart.mjs +0 -155
  536. package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +0 -198
  537. package/dist/pstack/skills/statechart/scripts/worker.mjs +0 -31
  538. package/dist/remote.d.mts +0 -27
  539. package/dist/remote.mjs +0 -235
  540. package/dist/role-config-a4JpVdc-.mjs +0 -379
  541. package/dist/role-wire-CAJsVA6e.d.mts +0 -54
  542. package/dist/roles.mjs +0 -293
  543. package/dist/script.d.mts +0 -91
  544. package/dist/script.mjs +0 -485
package/dist/client.js ADDED
@@ -0,0 +1,4072 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-pstack",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_dom = require("react-dom");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ // src/role-manifest.ts
11
+ /**
12
+ * The pstack role manifest.
13
+ *
14
+ * Role keys are matched exactly after whitespace and comma normalization. Commas
15
+ * belong to some keys (`feature, refactoring`), so the key is never split on a
16
+ * comma. Role behavior comes from `pstack/skills/setup-pstack/SKILL.md`.
17
+ */
18
+ /** Whether a role holds one route or a comma-separated list of routes. */
19
+ const ROLE_SHAPES = ["single", "list"];
20
+ /** Values that resolve to "send no route override and inherit the parent route". */
21
+ const INHERIT_ALIASES = ["inherit-parent", "auto"];
22
+ /** The 17 roles, in the order `setup-pstack` documents them. */
23
+ const ROLE_MANIFEST = [
24
+ {
25
+ key: "feature, refactoring",
26
+ shape: "single",
27
+ autoPanel: false
28
+ },
29
+ {
30
+ key: "bug-fix",
31
+ shape: "single",
32
+ autoPanel: false
33
+ },
34
+ {
35
+ key: "perf-issue",
36
+ shape: "single",
37
+ autoPanel: false
38
+ },
39
+ {
40
+ key: "hillclimb",
41
+ shape: "single",
42
+ autoPanel: false
43
+ },
44
+ {
45
+ key: "judgment and prose",
46
+ shape: "single",
47
+ autoPanel: false
48
+ },
49
+ {
50
+ key: "hardest tasks",
51
+ shape: "single",
52
+ autoPanel: false
53
+ },
54
+ {
55
+ key: "how explorer",
56
+ shape: "single",
57
+ autoPanel: false
58
+ },
59
+ {
60
+ key: "how explainer",
61
+ shape: "single",
62
+ autoPanel: false
63
+ },
64
+ {
65
+ key: "why investigators",
66
+ shape: "single",
67
+ autoPanel: false
68
+ },
69
+ {
70
+ key: "why synthesizer",
71
+ shape: "single",
72
+ autoPanel: false
73
+ },
74
+ {
75
+ key: "reflect tooling",
76
+ shape: "single",
77
+ autoPanel: false
78
+ },
79
+ {
80
+ key: "reflect judgment, divergent, synthesizer",
81
+ shape: "single",
82
+ autoPanel: false
83
+ },
84
+ {
85
+ key: "arena runners",
86
+ shape: "list",
87
+ autoPanel: true
88
+ },
89
+ {
90
+ key: "arena cross-judge pool",
91
+ shape: "list",
92
+ autoPanel: false
93
+ },
94
+ {
95
+ key: "swarm workers",
96
+ shape: "list",
97
+ autoPanel: false
98
+ },
99
+ {
100
+ key: "architect runners",
101
+ shape: "list",
102
+ autoPanel: true
103
+ },
104
+ {
105
+ key: "interrogate reviewers",
106
+ shape: "list",
107
+ autoPanel: true
108
+ }
109
+ ];
110
+ /** Normalize spacing and comma formatting without splitting the key. */
111
+ function normalizeRoleName(raw) {
112
+ return raw.normalize("NFC").trim().toLowerCase().replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").replace(/:+$/, "").trim();
113
+ }
114
+ /** Look up one role by its exact normalized key. */
115
+ function findRole(raw) {
116
+ const key = normalizeRoleName(raw);
117
+ return ROLE_MANIFEST.find((role) => role.key === key);
118
+ }
119
+ /** Whether a string is one of the manifest keys. */
120
+ function isRoleKey(raw) {
121
+ return findRole(raw) !== void 0;
122
+ }
123
+ // src/route.ts
124
+ /** Validate route parts shared by the text and wire parsers. */
125
+ function validateRouteParts(provider, model, effort) {
126
+ if (provider === "" || model === "" || /\s/.test(provider) || /\s/.test(model)) return "is not a route; provider and model cannot contain spaces";
127
+ if (effort !== void 0 && effort === "") return "has empty brackets; drop them or name an effort";
128
+ if (effort !== void 0 && /\s/.test(effort)) return "has a multi-word effort; an effort is one token";
129
+ }
130
+ /** Render one role value in the file grammar. */
131
+ function renderRouteValue(routes) {
132
+ return routes.map((route) => route.kind === "inherit" ? route.alias : route.effort === void 0 ? `${route.provider}/${route.model}` : `${route.provider}/${route.model} [${route.effort}]`).join(", ");
133
+ }
134
+ // src/wire-error.ts
135
+ /** Error raised when a wire value fails its strict decoder. */
136
+ var WireTypeError = class extends TypeError {
137
+ constructor(path, expected) {
138
+ super(`${path}: expected ${expected}`);
139
+ this.name = "WireTypeError";
140
+ }
141
+ };
142
+ // src/budget.ts
143
+ const BUDGET_TIERS = [
144
+ "unlimited",
145
+ "large",
146
+ "medium",
147
+ "small"
148
+ ];
149
+ const BUDGET_TARGETS = {
150
+ unlimited: "max",
151
+ large: "xhigh",
152
+ medium: "high",
153
+ small: "medium"
154
+ };
155
+ const EFFORT_LADDER = [
156
+ "off",
157
+ "low",
158
+ "medium",
159
+ "high",
160
+ "xhigh",
161
+ "max"
162
+ ];
163
+ var BudgetPlanError = class extends Error {
164
+ code;
165
+ detail;
166
+ constructor(code, detail) {
167
+ super(detail === void 0 ? code : `${code}: ${detail}`);
168
+ this.code = code;
169
+ this.detail = detail;
170
+ this.name = "BudgetPlanError";
171
+ }
172
+ };
173
+ /** Read a tier from the existing `# budget: <tier> (<target>)` line. */
174
+ function budgetTierFromLabel(raw) {
175
+ if (raw === void 0) return void 0;
176
+ return /^\s*(unlimited|large|medium|small)\b/i.exec(raw)?.[1]?.toLowerCase();
177
+ }
178
+ /** Render the persisted budget value that follows `# budget:`. */
179
+ function budgetValueFor(tier) {
180
+ return `${tier} (${BUDGET_TARGETS[tier]})`;
181
+ }
182
+ function budgetLineFor(tier) {
183
+ return `# budget: ${budgetValueFor(tier)}`;
184
+ }
185
+ /** Parse and validate a budget apply request at the wire boundary. */
186
+ function parseBudgetApplyRequest(value) {
187
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new WireTypeError("request", "an object");
188
+ const record = value;
189
+ if (typeof record["tier"] !== "string" || !BUDGET_TIERS.includes(record["tier"])) throw new WireTypeError("request.tier", `one of ${BUDGET_TIERS.join(", ")}`);
190
+ if (record["scope"] !== "concrete" && record["scope"] !== "unconfigured") throw new WireTypeError("request.scope", "\"concrete\" or \"unconfigured\"");
191
+ const rawModel = record["defaultModel"];
192
+ let defaultModel;
193
+ if (rawModel !== void 0) {
194
+ if (typeof rawModel !== "object" || rawModel === null || Array.isArray(rawModel)) throw new WireTypeError("request.defaultModel", "an object when present");
195
+ const model = rawModel;
196
+ if (typeof model["provider"] !== "string" || model["provider"].trim() === "") throw new WireTypeError("request.defaultModel.provider", "a non-empty string");
197
+ if (typeof model["model"] !== "string" || model["model"].trim() === "") throw new WireTypeError("request.defaultModel.model", "a non-empty string");
198
+ defaultModel = {
199
+ provider: model["provider"].trim(),
200
+ model: model["model"].trim()
201
+ };
202
+ }
203
+ if (record["scope"] === "unconfigured" && defaultModel === void 0) throw new WireTypeError("request.defaultModel", "required for the unconfigured scope");
204
+ return {
205
+ tier: record["tier"],
206
+ scope: record["scope"],
207
+ ...defaultModel === void 0 ? {} : { defaultModel }
208
+ };
209
+ }
210
+ /** Whether a plan row must be fixed before a budget can be applied. */
211
+ function isBudgetRowBlocked(row) {
212
+ return row.status === "needs-choice" || row.detail?.kind === "missing-model";
213
+ }
214
+ /** Plan every role change without touching the filesystem. */
215
+ function planBudget(roles, catalogue, request) {
216
+ const defaultModel = request.scope === "unconfigured" ? resolveDefaultModel(catalogue, request.defaultModel) : void 0;
217
+ const warnings = [];
218
+ const rows = roles.map((role) => planRole(role, request, defaultModel, catalogue, warnings));
219
+ return {
220
+ tier: request.tier,
221
+ scope: request.scope,
222
+ budgetLine: budgetLineFor(request.tier),
223
+ rows,
224
+ warnings
225
+ };
226
+ }
227
+ function planRole(role, request, defaultModel, catalogue, warnings) {
228
+ const previous = role.routes;
229
+ if (role.hasIssues === true) return {
230
+ key: role.key,
231
+ status: "skipped",
232
+ previous,
233
+ routes: previous
234
+ };
235
+ if (!role.configured) {
236
+ if (request.scope !== "unconfigured" || defaultModel === void 0) return {
237
+ key: role.key,
238
+ status: "skipped",
239
+ previous,
240
+ routes: previous
241
+ };
242
+ const effort = effortFor(defaultModel, request.tier);
243
+ if (role.autoPanel) warnings.push({
244
+ kind: "auto-panel",
245
+ role: role.key
246
+ });
247
+ return {
248
+ key: role.key,
249
+ status: statusForNew(effort),
250
+ previous,
251
+ routes: [routeFor(defaultModel, effort.effort)],
252
+ ...effort.detail === void 0 ? {} : { detail: effort.detail }
253
+ };
254
+ }
255
+ if (request.scope !== "concrete") return {
256
+ key: role.key,
257
+ status: "skipped",
258
+ previous,
259
+ routes: previous
260
+ };
261
+ if (previous.every((route) => route.kind === "inherit")) return {
262
+ key: role.key,
263
+ status: "skipped",
264
+ previous,
265
+ routes: previous
266
+ };
267
+ let aggregate = "unchanged";
268
+ let detail;
269
+ const routes = previous.map((route) => {
270
+ if (route.kind === "inherit") return route;
271
+ const model = findCatalogueModel(catalogue, route.provider, route.model);
272
+ if (model === void 0) {
273
+ aggregate = mergeStatus(aggregate, "custom");
274
+ detail = preferDetail(detail, {
275
+ kind: "missing-model",
276
+ provider: route.provider,
277
+ model: route.model
278
+ });
279
+ return route;
280
+ }
281
+ const effort = effortFor(model, request.tier);
282
+ aggregate = mergeStatus(aggregate, statusForExisting(effort, route));
283
+ detail = preferDetail(detail, effort.detail);
284
+ if (effort.effort === void 0) return route;
285
+ return {
286
+ ...route,
287
+ effort: effort.effort
288
+ };
289
+ });
290
+ return {
291
+ key: role.key,
292
+ status: aggregate,
293
+ previous,
294
+ routes,
295
+ ...detail === void 0 ? {} : { detail }
296
+ };
297
+ }
298
+ function statusForNew(effort) {
299
+ if (effort.status === "exact") return "changed";
300
+ if (effort.status === "fallback") return "fallback";
301
+ return effort.status;
302
+ }
303
+ function statusForExisting(effort, route) {
304
+ if (effort.status === "exact" && route.effort === effort.effort) return "unchanged";
305
+ if (effort.status === "exact") return "changed";
306
+ if (effort.status === "fallback") return "fallback";
307
+ return effort.status;
308
+ }
309
+ function mergeStatus(left, right) {
310
+ const order = {
311
+ unchanged: 0,
312
+ changed: 1,
313
+ fallback: 2,
314
+ unsupported: 3,
315
+ custom: 4,
316
+ "needs-choice": 5,
317
+ skipped: 6
318
+ };
319
+ return order[right] > order[left] ? right : left;
320
+ }
321
+ function preferDetail(current, next) {
322
+ if (next === void 0) return current;
323
+ if (current === void 0) return next;
324
+ const order = {
325
+ fallback: 0,
326
+ custom: 1,
327
+ unsupported: 2,
328
+ "needs-choice": 3,
329
+ "missing-model": 4
330
+ };
331
+ return order[next.kind] > order[current.kind] ? next : current;
332
+ }
333
+ function effortFor(model, tier) {
334
+ if (model.efforts.length === 0) return {
335
+ status: "unsupported",
336
+ detail: { kind: "unsupported" }
337
+ };
338
+ const target = BUDGET_TARGETS[tier];
339
+ if (model.efforts.includes(target)) return {
340
+ status: "exact",
341
+ effort: target
342
+ };
343
+ const targetIndex = EFFORT_LADDER.indexOf(target);
344
+ const known = model.efforts.map((effort) => ({
345
+ effort,
346
+ index: EFFORT_LADDER.indexOf(effort)
347
+ })).filter((entry) => entry.index >= 0);
348
+ if (known.length === 0) return {
349
+ status: "custom",
350
+ detail: { kind: "custom" }
351
+ };
352
+ const lower = known.filter((entry) => entry.index <= targetIndex).sort((left, right) => right.index - left.index);
353
+ if (lower[0] !== void 0) return {
354
+ status: "fallback",
355
+ effort: lower[0].effort,
356
+ detail: {
357
+ kind: "fallback",
358
+ effort: lower[0].effort
359
+ }
360
+ };
361
+ return {
362
+ status: "needs-choice",
363
+ detail: { kind: "needs-choice" }
364
+ };
365
+ }
366
+ function resolveDefaultModel(catalogue, choice) {
367
+ if (choice === void 0) throw new BudgetPlanError("no-default-model");
368
+ const provider = catalogue.providers.find((entry) => entry.provider === choice.provider);
369
+ if (provider === void 0) throw new BudgetPlanError("provider-not-found", choice.provider);
370
+ if (provider.error !== void 0) throw new BudgetPlanError("provider-discovery", choice.provider);
371
+ const model = provider.models.find((entry) => entry.id === choice.model);
372
+ if (model === void 0) throw new BudgetPlanError("model-not-found", `${choice.provider}/${choice.model}`);
373
+ if (model.error !== void 0) throw new BudgetPlanError("model-discovery", `${choice.provider}/${choice.model}`);
374
+ return model;
375
+ }
376
+ function findCatalogueModel(catalogue, providerId, modelId) {
377
+ const provider = catalogue.providers.find((entry) => entry.provider === providerId);
378
+ if (provider === void 0 || provider.error !== void 0) return void 0;
379
+ const model = provider.models.find((entry) => entry.id === modelId);
380
+ if (model === void 0 || model.error !== void 0) return void 0;
381
+ return model;
382
+ }
383
+ function routeFor(model, effort) {
384
+ return {
385
+ kind: "route",
386
+ provider: splitRoute(model.route).provider,
387
+ model: model.id,
388
+ ...effort === void 0 ? {} : { effort }
389
+ };
390
+ }
391
+ function splitRoute(route) {
392
+ const slash = route.indexOf("/");
393
+ if (slash <= 0) throw new Error(`invalid model route "${route}"`);
394
+ return {
395
+ provider: route.slice(0, slash),
396
+ model: route.slice(slash + 1)
397
+ };
398
+ }
399
+ // src/catalogue.ts
400
+ function parseRecord$1(value, path) {
401
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new WireTypeError(path, "an object");
402
+ return value;
403
+ }
404
+ function optionalString$1(record, key, path) {
405
+ const value = record[key];
406
+ if (value !== void 0 && typeof value !== "string") throw new WireTypeError(`${path}.${key}`, "a string when present");
407
+ return value;
408
+ }
409
+ function optionalInteger(record, key, path) {
410
+ const value = record[key];
411
+ if (value !== void 0 && !Number.isSafeInteger(value)) throw new WireTypeError(`${path}.${key}`, "an integer when present");
412
+ return value;
413
+ }
414
+ /** Parse the live model catalogue returned by the Host. */
415
+ function parseCatalogueResult(value) {
416
+ const record = parseRecord$1(value, "catalogue");
417
+ if (!Array.isArray(record["providers"])) throw new WireTypeError("catalogue.providers", "an array");
418
+ if (typeof record["truncated"] !== "boolean") throw new WireTypeError("catalogue.truncated", "a boolean");
419
+ const providerFilter = optionalString$1(record, "providerFilter", "catalogue");
420
+ return {
421
+ providers: record["providers"].map((provider, index) => {
422
+ const path = `catalogue.providers[${index}]`;
423
+ const entry = parseRecord$1(provider, path);
424
+ if (typeof entry["provider"] !== "string" || typeof entry["name"] !== "string") throw new WireTypeError(path, "provider and name strings");
425
+ if (!Array.isArray(entry["models"])) throw new WireTypeError(`${path}.models`, "an array");
426
+ const providerError = optionalString$1(entry, "error", path);
427
+ return {
428
+ provider: entry["provider"],
429
+ name: entry["name"],
430
+ ...providerError === void 0 ? {} : { error: providerError },
431
+ models: entry["models"].map((model, modelIndex) => {
432
+ const modelPath = `${path}.models[${modelIndex}]`;
433
+ const item = parseRecord$1(model, modelPath);
434
+ if (typeof item["route"] !== "string" || typeof item["id"] !== "string" || typeof item["name"] !== "string") throw new WireTypeError(modelPath, "route, id, and name strings");
435
+ const efforts = item["efforts"];
436
+ if (!Array.isArray(efforts) || efforts.some((effort) => typeof effort !== "string")) throw new WireTypeError(`${modelPath}.efforts`, "an array of strings");
437
+ const description = optionalString$1(item, "description", modelPath);
438
+ const contextWindow = optionalInteger(item, "contextWindow", modelPath);
439
+ const maxTokens = optionalInteger(item, "maxTokens", modelPath);
440
+ const defaultEffort = optionalString$1(item, "defaultEffort", modelPath);
441
+ const modelError = optionalString$1(item, "error", modelPath);
442
+ return {
443
+ route: item["route"],
444
+ id: item["id"],
445
+ name: item["name"],
446
+ ...description === void 0 ? {} : { description },
447
+ ...contextWindow === void 0 ? {} : { contextWindow },
448
+ ...maxTokens === void 0 ? {} : { maxTokens },
449
+ efforts,
450
+ ...defaultEffort === void 0 ? {} : { defaultEffort },
451
+ ...modelError === void 0 ? {} : { error: modelError }
452
+ };
453
+ })
454
+ };
455
+ }),
456
+ truncated: record["truncated"],
457
+ ...typeof providerFilter === "string" ? { providerFilter } : {}
458
+ };
459
+ }
460
+ // src/role-wire.ts
461
+ /** Wire shapes shared by the role settings Host service and Client page. */
462
+ function parseRecord(value, path) {
463
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new WireTypeError(path, "an object");
464
+ return value;
465
+ }
466
+ function optionalString(record, key, path) {
467
+ const value = record[key];
468
+ if (value !== void 0 && typeof value !== "string") throw new WireTypeError(`${path}.${key}`, "a string when present");
469
+ return value;
470
+ }
471
+ function parseRouteView(value, path) {
472
+ const record = parseRecord(value, path);
473
+ if (record["kind"] === "inherit") {
474
+ const alias = record["alias"] ?? "inherit-parent";
475
+ if (typeof alias !== "string" || !INHERIT_ALIASES.includes(alias)) throw new WireTypeError(`${path}.alias`, "\"inherit-parent\" or \"auto\"");
476
+ return {
477
+ kind: "inherit",
478
+ alias
479
+ };
480
+ }
481
+ if (record["kind"] !== "route") throw new WireTypeError(`${path}.kind`, "\"route\" or \"inherit\"");
482
+ const provider = record["provider"];
483
+ const model = record["model"];
484
+ const effort = record["effort"];
485
+ if (typeof provider !== "string" || provider.trim() === "") throw new WireTypeError(`${path}.provider`, "a non-empty string");
486
+ if (typeof model !== "string" || model.trim() === "") throw new WireTypeError(`${path}.model`, "a non-empty string");
487
+ if (effort !== void 0 && (typeof effort !== "string" || effort.trim() === "")) throw new WireTypeError(`${path}.effort`, "a non-empty string when present");
488
+ const providerValue = provider.trim();
489
+ const modelValue = model.trim();
490
+ const effortValue = typeof effort === "string" ? effort.trim() : void 0;
491
+ const issue = validateRouteParts(providerValue, modelValue, effortValue);
492
+ if (issue !== void 0) throw new WireTypeError(path, issue);
493
+ return {
494
+ kind: "route",
495
+ provider: providerValue,
496
+ model: modelValue,
497
+ ...effortValue === void 0 ? {} : { effort: effortValue }
498
+ };
499
+ }
500
+ /** Parse one save request. An empty route list removes the role line. */
501
+ function parseSaveRoleRequest(value) {
502
+ const record = parseRecord(value, "request");
503
+ if (typeof record["key"] !== "string" || record["key"].trim() === "") throw new WireTypeError("request.key", "a non-empty string");
504
+ if (!Array.isArray(record["routes"])) throw new WireTypeError("request.routes", "an array");
505
+ return {
506
+ key: record["key"],
507
+ routes: record["routes"].map((route, index) => parseRouteView(route, `request.routes[${index}]`))
508
+ };
509
+ }
510
+ function parseRoleFileIssue(value, path) {
511
+ const record = parseRecord(value, path);
512
+ if (typeof record["message"] !== "string") throw new WireTypeError(`${path}.message`, "a string");
513
+ const line = record["line"];
514
+ if (line !== void 0 && typeof line !== "number") throw new WireTypeError(`${path}.line`, "a number when present");
515
+ return {
516
+ ...typeof line === "number" ? { line } : {},
517
+ message: record["message"]
518
+ };
519
+ }
520
+ function parseRoleView(value, index) {
521
+ const path = `snapshot.roles[${index}]`;
522
+ const record = parseRecord(value, path);
523
+ if (typeof record["key"] !== "string" || !isRoleKey(record["key"])) throw new WireTypeError(`${path}.key`, "a role in the manifest");
524
+ if (typeof record["shape"] !== "string" || !ROLE_SHAPES.includes(record["shape"])) throw new WireTypeError(`${path}.shape`, "\"single\" or \"list\"");
525
+ if (typeof record["configured"] !== "boolean") throw new WireTypeError(`${path}.configured`, "a boolean");
526
+ if (!Array.isArray(record["routes"]) || !Array.isArray(record["issues"])) throw new WireTypeError(path, "routes and issues arrays");
527
+ const line = record["line"];
528
+ if (line !== void 0 && typeof line !== "number") throw new WireTypeError(`${path}.line`, "a number when present");
529
+ return {
530
+ key: record["key"],
531
+ shape: record["shape"],
532
+ configured: record["configured"],
533
+ ...typeof line === "number" ? { line } : {},
534
+ routes: record["routes"].map((route, routeIndex) => parseRouteView(route, `${path}.routes[${routeIndex}]`)),
535
+ issues: record["issues"].map((issue, issueIndex) => {
536
+ if (typeof issue !== "string") throw new WireTypeError(`${path}.issues[${issueIndex}]`, "a string");
537
+ return issue;
538
+ })
539
+ };
540
+ }
541
+ /** Parse one role-file snapshot returned by the Host. */
542
+ function parseRoleFileSnapshot(value) {
543
+ const record = parseRecord(value, "snapshot");
544
+ const status = record["status"];
545
+ if (status !== "missing" && status !== "loaded" && status !== "unreadable") throw new WireTypeError("snapshot.status", "\"missing\", \"loaded\", or \"unreadable\"");
546
+ if (typeof record["path"] !== "string") throw new WireTypeError("snapshot.path", "a string");
547
+ if (typeof record["canSave"] !== "boolean") throw new WireTypeError("snapshot.canSave", "a boolean");
548
+ if (!Array.isArray(record["roles"]) || !Array.isArray(record["issues"])) throw new WireTypeError("snapshot", "roles and issues arrays");
549
+ const reason = optionalString(record, "reason", "snapshot");
550
+ const budget = optionalString(record, "budget", "snapshot");
551
+ return {
552
+ path: record["path"],
553
+ status,
554
+ ...typeof reason === "string" ? { reason } : {},
555
+ ...typeof budget === "string" ? { budget } : {},
556
+ roles: record["roles"].map((role, index) => parseRoleView(role, index)),
557
+ issues: record["issues"].map((issue, index) => parseRoleFileIssue(issue, `snapshot.issues[${index}]`)),
558
+ canSave: record["canSave"]
559
+ };
560
+ }
561
+ function parseSnapshotResult(value) {
562
+ const record = parseRecord(value, "result");
563
+ if (record["ok"] === true) return {
564
+ ok: true,
565
+ snapshot: parseRoleFileSnapshot(record["snapshot"])
566
+ };
567
+ if (record["ok"] === false && typeof record["reason"] === "string") return {
568
+ ok: false,
569
+ reason: record["reason"]
570
+ };
571
+ throw new WireTypeError("result", "a successful snapshot or a refusal reason");
572
+ }
573
+ /** Parse one role-save result returned by the Host. */
574
+ function parseSaveRoleResult(value) {
575
+ return parseSnapshotResult(value);
576
+ }
577
+ /** Parse one budget-apply result returned by the Host. */
578
+ function parseBudgetApplyResult(value) {
579
+ return parseSnapshotResult(value);
580
+ }
581
+ // src/role-remote.ts
582
+ /** npm package owning these methods. */
583
+ const REMOTE_PACKAGE = "dsh-pstack";
584
+ /** Cordis service key and wire namespace. */
585
+ const ROLES_SERVICE = "pstackRoles";
586
+ function codec(typeSymbol, parse) {
587
+ return {
588
+ mode: "strict",
589
+ typeSymbol,
590
+ create: () => ({ parse }),
591
+ decode: parse
592
+ };
593
+ }
594
+ function parameter(name, typeSymbol, parse) {
595
+ return {
596
+ name,
597
+ wire: name,
598
+ source: "json",
599
+ codec: codec(typeSymbol, parse)
600
+ };
601
+ }
602
+ function directMethod(options) {
603
+ return {
604
+ id: `${REMOTE_PACKAGE}#${ROLES_SERVICE}/${options.method}`,
605
+ service: ROLES_SERVICE,
606
+ namespace: ROLES_SERVICE,
607
+ method: options.method,
608
+ invocation: { kind: "direct" },
609
+ parameters: options.parameters,
610
+ result: options.result,
611
+ ...options.cancellation === true ? { cancellation: { parameter: "signal" } } : {},
612
+ input() {
613
+ return { create: () => ({ parse(value) {
614
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("the method input must be an object");
615
+ const record = value;
616
+ const parsed = {};
617
+ for (const item of options.parameters) {
618
+ if (item.codec.mode !== "strict") throw new TypeError("expected a strict codec");
619
+ parsed[item.wire] = item.codec.create().parse(record[item.wire]);
620
+ }
621
+ return parsed;
622
+ } }) };
623
+ },
624
+ invoke(remote, input, signal) {
625
+ const namespace = Reflect.get(remote, ROLES_SERVICE);
626
+ const values = options.parameters.map((item) => input[item.wire]);
627
+ if (options.cancellation === true) values.push(signal);
628
+ return namespace[options.method](...values);
629
+ }
630
+ };
631
+ }
632
+ const statusMethod = directMethod({
633
+ method: "status",
634
+ parameters: [],
635
+ result: codec("PstackRoleFileSnapshot", parseRoleFileSnapshot)
636
+ });
637
+ const catalogueMethod = directMethod({
638
+ method: "catalogue",
639
+ parameters: [],
640
+ result: codec("PstackRoleCatalogue", parseCatalogueResult),
641
+ cancellation: true
642
+ });
643
+ const saveMethod = directMethod({
644
+ method: "save",
645
+ parameters: [parameter("request", "PstackSaveRoleRequest", parseSaveRoleRequest)],
646
+ result: codec("PstackSaveRoleResult", parseSaveRoleResult),
647
+ cancellation: true
648
+ });
649
+ const budgetMethod = directMethod({
650
+ method: "budget",
651
+ parameters: [parameter("request", "PstackBudgetApplyRequest", parseBudgetApplyRequest)],
652
+ result: codec("PstackBudgetApplyResult", parseBudgetApplyResult),
653
+ cancellation: true
654
+ });
655
+ /** Mountable contribution shared by Host registration and Client `$mount`. */
656
+ const pstackRolesRemote = {
657
+ package: REMOTE_PACKAGE,
658
+ descriptors: [
659
+ statusMethod,
660
+ catalogueMethod,
661
+ saveMethod,
662
+ budgetMethod
663
+ ],
664
+ methods: {
665
+ [`${ROLES_SERVICE}/status`]: statusMethod,
666
+ [`${ROLES_SERVICE}/catalogue`]: catalogueMethod,
667
+ [`${ROLES_SERVICE}/save`]: saveMethod,
668
+ [`${ROLES_SERVICE}/budget`]: budgetMethod
669
+ }
670
+ };
671
+ pstackRolesRemote.descriptors;
672
+ // src/client/icons.tsx
673
+ function icon(paths) {
674
+ return function PstackIcon({ size = 16, ...rest }) {
675
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
676
+ width: size,
677
+ height: size,
678
+ viewBox: "0 0 16 16",
679
+ fill: "none",
680
+ stroke: "currentColor",
681
+ strokeWidth: 1.5,
682
+ strokeLinecap: "round",
683
+ strokeLinejoin: "round",
684
+ "aria-hidden": "true",
685
+ focusable: "false",
686
+ ...rest,
687
+ children: paths
688
+ });
689
+ };
690
+ }
691
+ const IconSettings = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.8 1.8h2.4l.5 1.6 1.4.8 1.6-.5 1.2 2.1-1.2 1.2v1.6l1.2 1.2-1.2 2.1-1.6-.5-1.4.8-.5 1.6H6.8l-.5-1.6-1.4-.8-1.6.5-1.2-2.1 1.2-1.2V6.9L2.1 5.7l1.2-2.1 1.6.5 1.4-.8z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
692
+ cx: "8",
693
+ cy: "8",
694
+ r: "1.9"
695
+ })] }));
696
+ const IconClose = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4l8 8M12 4l-8 8" }));
697
+ const IconRefresh = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13 8a5 5 0 1 1-1.5-3.6" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13 2.8v3.1H9.9" })] }));
698
+ const IconCheck = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.8 8.4l3.4 3.4 7-7.4" }));
699
+ const IconCheckCircle = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
700
+ cx: "8",
701
+ cy: "8",
702
+ r: "6"
703
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 8.2l1.9 1.9 3.8-4" })] }));
704
+ const IconPlus = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 3.2v9.6M3.2 8h9.6" }));
705
+ const IconTrash = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.8 4.2h10.4M6.4 4.2V2.8h3.2v1.4M4.2 4.2l.6 9h6.4l.6-9" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.7 6.8v3.8M9.3 6.8v3.8" })] }));
706
+ const IconRoute = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
707
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
708
+ cx: "3.4",
709
+ cy: "3.4",
710
+ r: "1.7"
711
+ }),
712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
713
+ cx: "12.6",
714
+ cy: "12.6",
715
+ r: "1.7"
716
+ }),
717
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3.4 5.1v3.3a2.2 2.2 0 0 0 2.2 2.2h3.6a2.2 2.2 0 0 1 2.2 2.2" })
718
+ ] }));
719
+ const IconSearch = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
720
+ cx: "7",
721
+ cy: "7",
722
+ r: "4.4"
723
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10.2 10.2L14 14" })] }));
724
+ const IconTerminal = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
725
+ x: "1.8",
726
+ y: "2.8",
727
+ width: "12.4",
728
+ height: "10.4",
729
+ rx: "1.8"
730
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4.4 6.2l2.1 1.8-2.1 1.8M8.4 10.2h3.2" })] }));
731
+ const IconGitBranch = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
733
+ cx: "4",
734
+ cy: "3.4",
735
+ r: "1.6"
736
+ }),
737
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
738
+ cx: "4",
739
+ cy: "12.6",
740
+ r: "1.6"
741
+ }),
742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
743
+ cx: "12",
744
+ cy: "5.6",
745
+ r: "1.6"
746
+ }),
747
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 5v6M5.6 5.6h3.2a3.2 3.2 0 0 1 3.2 3.2v1.6" })
748
+ ] }));
749
+ const IconRepeat = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 6.2A3.2 3.2 0 0 1 6.2 3H13" }),
751
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10.8 1.6L13.2 3l-2.4 1.4" }),
752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M13 9.8A3.2 3.2 0 0 1 9.8 13H3" }),
753
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 14.4L2.8 13l2.4-1.4" })
754
+ ] }));
755
+ const IconInfo = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
756
+ cx: "8",
757
+ cy: "8",
758
+ r: "6"
759
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 7.2v4M8 4.9v.2" })] }));
760
+ const IconAlert = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 2.2L14 13H2z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6.2v3M8 11.4v.2" })] }));
761
+ const IconSave = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 2.8h8.2L13.2 5v8.2H2.8V2.8z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 2.8v3.6h5.2V2.8M5.2 13.2V9.4h5.6v3.8" })] }));
762
+ const IconUndo = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 8a5 5 0 1 0 1.5-3.6" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 2.8v3.1h3.1" })] }));
763
+ const IconBrowser = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
765
+ x: "1.8",
766
+ y: "2.8",
767
+ width: "12.4",
768
+ height: "10.4",
769
+ rx: "1.8"
770
+ }),
771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M1.8 6h12.4" }),
772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
773
+ cx: "8",
774
+ cy: "9.6",
775
+ r: "2.1"
776
+ }),
777
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.9 9.6h4.2M8 7.5v4.2" })
778
+ ] }));
779
+ const IconMonitor = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
780
+ x: "1.8",
781
+ y: "2.8",
782
+ width: "12.4",
783
+ height: "8.4",
784
+ rx: "1.6"
785
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5.2 14h5.6M8 11.2V14" })] }));
786
+ const IconPlug = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
787
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6 2.8v3M10 2.8v3" }),
788
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4.4 5.8h7.2v2.4a3.6 3.6 0 0 1-3.6 3.6 3.6 3.6 0 0 1-3.6-3.6z" }),
789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 11.8v1.6" })
790
+ ] }));
791
+ const IconUsers = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
792
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
793
+ cx: "6",
794
+ cy: "5.2",
795
+ r: "2"
796
+ }),
797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.4 13v-1.1a3.6 3.6 0 0 1 3.6-3.6h0a3.6 3.6 0 0 1 3.6 3.6V13" }),
798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10.4 3.5a2 2 0 0 1 0 3.7M11.4 8.6a3.2 3.2 0 0 1 2.2 3V13" })
799
+ ] }));
800
+ const IconEraser = icon(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3.1 10.6l5.2-5.2a1.7 1.7 0 0 1 2.4 0l1 1a1.7 1.7 0 0 1 0 2.4l-4.4 4.4H5.1z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M6.6 6.1l3.3 3.3M3.1 10.6h6.3" })] }));
801
+ // src/client/focus-trap.ts
802
+ /** Shared modal focus trap and Escape handling for the pstack dialogs. */
803
+ /** Selector for the elements a modal may focus with Tab. */
804
+ const FOCUSABLE_SELECTOR = [
805
+ "button:not([disabled])",
806
+ "[href]",
807
+ "input:not([disabled])",
808
+ "select:not([disabled])",
809
+ "textarea:not([disabled])",
810
+ "[tabindex]:not([tabindex=\"-1\"])"
811
+ ].join(",");
812
+ /** Trap Tab focus inside `ref`, and handle Escape through `onEscape`, while `active`. */
813
+ function useFocusTrap(ref, onEscape, active, options = {}) {
814
+ const { suspended, lockScroll, stopEscapePropagation, skipHidden, capture } = options;
815
+ (0, react.useEffect)(() => {
816
+ if (!active) return;
817
+ const onKeyDown = (event) => {
818
+ if (event.key === "Escape") {
819
+ event.preventDefault();
820
+ if (stopEscapePropagation === true) event.stopPropagation();
821
+ onEscape();
822
+ return;
823
+ }
824
+ if (event.key !== "Tab") return;
825
+ if (suspended?.() === true) return;
826
+ const panel = ref.current;
827
+ if (panel === null) return;
828
+ const candidates = Array.from(panel.querySelectorAll(FOCUSABLE_SELECTOR));
829
+ const focusables = skipHidden === true ? candidates.filter((element) => element.closest("[hidden]") === null) : candidates;
830
+ if (focusables.length === 0) {
831
+ event.preventDefault();
832
+ panel.focus();
833
+ return;
834
+ }
835
+ const first = focusables[0];
836
+ const last = focusables[focusables.length - 1];
837
+ const current = document.activeElement;
838
+ const outside = current === null || !panel.contains(current);
839
+ if (event.shiftKey) {
840
+ if (current === first || outside) {
841
+ event.preventDefault();
842
+ last.focus();
843
+ }
844
+ return;
845
+ }
846
+ if (current === last || outside) {
847
+ event.preventDefault();
848
+ first.focus();
849
+ }
850
+ };
851
+ const previousOverflow = document.body.style.overflow;
852
+ if (lockScroll === true) document.body.style.overflow = "hidden";
853
+ document.addEventListener("keydown", onKeyDown, capture === true);
854
+ return () => {
855
+ if (lockScroll === true) document.body.style.overflow = previousOverflow;
856
+ document.removeEventListener("keydown", onKeyDown, capture === true);
857
+ };
858
+ }, [
859
+ ref,
860
+ active,
861
+ onEscape,
862
+ suspended,
863
+ lockScroll,
864
+ stopEscapePropagation,
865
+ skipHidden,
866
+ capture
867
+ ]);
868
+ }
869
+ // src/client/styles.ts
870
+ /** Stylesheet for the pstack client dialog and its pages. */
871
+ const CSS = `
872
+ .pstack-overlay {
873
+ position: fixed;
874
+ inset: 0;
875
+ z-index: 1000;
876
+ display: flex;
877
+ align-items: center;
878
+ justify-content: center;
879
+ }
880
+ .pstack-mask {
881
+ position: absolute;
882
+ inset: var(--dsh-frame-chrome-top, 0px) 0 0;
883
+ border: 0;
884
+ background: var(--dsw-alias-bg-mask-1);
885
+ backdrop-filter: var(--dsw-mask-blur);
886
+ cursor: default;
887
+ }
888
+ .pstack-panel {
889
+ position: relative;
890
+ z-index: 1;
891
+ display: grid;
892
+ grid-template-rows: auto auto minmax(0, 1fr);
893
+ width: 800px;
894
+ max-width: calc(100vw - 48px);
895
+ height: min(800px, calc(100vh - 2 * max(24px, var(--dsh-frame-overlay-top, 24px))));
896
+ border-radius: var(--dsw-radius-panel);
897
+ overflow: hidden;
898
+ background: var(--dsw-alias-bg-layer-2);
899
+ color: var(--dsw-alias-label-primary);
900
+ box-shadow: var(--dsw-elevation-prominent);
901
+ }
902
+ .pstack-panel:focus {
903
+ outline: none;
904
+ }
905
+ .pstack-header {
906
+ display: flex;
907
+ align-items: flex-start;
908
+ justify-content: space-between;
909
+ gap: 12px;
910
+ padding: 22px 16px 8px 24px;
911
+ }
912
+ .pstack-title {
913
+ margin: 0;
914
+ font-size: 16px;
915
+ font-weight: 500;
916
+ line-height: 24px;
917
+ }
918
+ .pstack-tabs-row {
919
+ padding: 6px 24px 0;
920
+ }
921
+ .pstack-segmented {
922
+ position: relative;
923
+ display: grid;
924
+ grid-auto-flow: column;
925
+ grid-auto-columns: 1fr;
926
+ gap: 2px;
927
+ padding: 4px;
928
+ border-radius: var(--dsw-radius-md);
929
+ background: var(--dsw-alias-interactive-bg-hover);
930
+ }
931
+ .pstack-segmented-indicator {
932
+ position: absolute;
933
+ top: 4px;
934
+ left: 4px;
935
+ width: calc(
936
+ (100% - 8px - 2px * (var(--pstack-segment-count) - 1)) / var(--pstack-segment-count)
937
+ );
938
+ height: calc(100% - 8px);
939
+ border-radius: var(--dsw-radius-sm);
940
+ background: var(--dsw-alias-bg-layer-1);
941
+ box-shadow: var(--dsw-elevation-soft);
942
+ transform: translate(calc(var(--pstack-segment-index) * (100% + 2px)));
943
+ transition: transform 0.16s ease;
944
+ pointer-events: none;
945
+ }
946
+ .pstack-segment {
947
+ position: relative;
948
+ z-index: 1;
949
+ box-sizing: border-box;
950
+ height: 30px;
951
+ padding: 0 16px;
952
+ border: 0;
953
+ border-radius: var(--dsw-radius-sm);
954
+ background: transparent;
955
+ color: var(--dsw-alias-label-secondary);
956
+ font: inherit;
957
+ font-size: 13px;
958
+ font-weight: 500;
959
+ line-height: 20px;
960
+ white-space: nowrap;
961
+ cursor: pointer;
962
+ transition: color 0.12s ease;
963
+ }
964
+ .pstack-segment[aria-selected="true"] {
965
+ color: var(--dsw-alias-label-primary);
966
+ text-decoration: underline;
967
+ text-decoration-thickness: 1px;
968
+ text-underline-offset: 6px;
969
+ }
970
+ .pstack-segment:hover:not(:disabled) {
971
+ color: var(--dsw-alias-label-primary);
972
+ }
973
+ .pstack-segment:focus-visible {
974
+ outline: none;
975
+ text-decoration: underline;
976
+ text-decoration-thickness: 1px;
977
+ text-underline-offset: 4px;
978
+ }
979
+ .pstack-body {
980
+ min-height: 0;
981
+ overflow-y: auto;
982
+ padding: 16px 24px 24px;
983
+ --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
984
+ --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
985
+ }
986
+ .pstack-section-head {
987
+ display: flex;
988
+ align-items: flex-start;
989
+ justify-content: space-between;
990
+ gap: 16px;
991
+ margin-bottom: 14px;
992
+ }
993
+ .pstack-section-head > div:first-child {
994
+ flex: 1;
995
+ min-width: 0;
996
+ }
997
+ .pstack-section-desc {
998
+ margin: 4px 0 0;
999
+ color: var(--dsw-alias-label-tertiary);
1000
+ font-size: 13px;
1001
+ line-height: 20px;
1002
+ }
1003
+ .pstack-section-actions {
1004
+ display: flex;
1005
+ align-items: center;
1006
+ justify-content: flex-end;
1007
+ flex-wrap: wrap;
1008
+ gap: 8px;
1009
+ flex: none;
1010
+ }
1011
+ .pstack-btn {
1012
+ box-sizing: border-box;
1013
+ display: inline-flex;
1014
+ align-items: center;
1015
+ justify-content: center;
1016
+ gap: 6px;
1017
+ height: 32px;
1018
+ padding: 0 12px;
1019
+ border: 0;
1020
+ border-radius: var(--dsw-radius-md);
1021
+ background: transparent;
1022
+ color: var(--dsw-alias-label-primary);
1023
+ font: inherit;
1024
+ font-size: 13px;
1025
+ line-height: 20px;
1026
+ white-space: nowrap;
1027
+ cursor: pointer;
1028
+ transition: background-color 0.12s ease;
1029
+ }
1030
+ .pstack-btn:disabled {
1031
+ cursor: not-allowed;
1032
+ opacity: 0.4;
1033
+ }
1034
+ .pstack-btn-primary {
1035
+ background: var(--dsw-alias-button-primary-fill);
1036
+ color: var(--dsw-alias-label-primary-foreground);
1037
+ }
1038
+ .pstack-btn-primary:hover:not(:disabled) {
1039
+ background: var(--dsw-alias-button-primary-hover);
1040
+ }
1041
+ .pstack-btn-outline {
1042
+ border: 0.5px solid var(--dsw-alias-border-l3);
1043
+ background: transparent;
1044
+ }
1045
+ .pstack-btn-outline:hover:not(:disabled) {
1046
+ background: var(--dsw-alias-interactive-bg-hover);
1047
+ }
1048
+ .pstack-btn-ghost {
1049
+ color: var(--dsw-alias-label-secondary);
1050
+ }
1051
+ .pstack-btn-ghost:hover:not(:disabled) {
1052
+ background: var(--dsw-alias-interactive-bg-hover);
1053
+ color: var(--dsw-alias-label-primary);
1054
+ }
1055
+ .pstack-btn-sm {
1056
+ height: 28px;
1057
+ gap: 4px;
1058
+ padding: 0 10px;
1059
+ border-radius: var(--dsw-radius-sm);
1060
+ font-size: 12px;
1061
+ line-height: 18px;
1062
+ }
1063
+ .pstack-icon-btn {
1064
+ box-sizing: border-box;
1065
+ display: inline-flex;
1066
+ align-items: center;
1067
+ justify-content: center;
1068
+ flex: none;
1069
+ width: 28px;
1070
+ height: 28px;
1071
+ padding: 0;
1072
+ border: 0.5px solid var(--dsw-alias-border-l3);
1073
+ border-radius: var(--dsw-radius-md);
1074
+ background: transparent;
1075
+ color: var(--dsw-alias-label-secondary);
1076
+ cursor: pointer;
1077
+ }
1078
+ .pstack-icon-btn-plain {
1079
+ border-color: transparent;
1080
+ }
1081
+ .pstack-icon-btn:hover:not(:disabled) {
1082
+ background: var(--dsw-alias-interactive-bg-hover);
1083
+ color: var(--dsw-alias-label-primary);
1084
+ }
1085
+ .pstack-icon-btn:disabled {
1086
+ cursor: default;
1087
+ opacity: 0.4;
1088
+ }
1089
+ .pstack-icon-btn-danger {
1090
+ border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);
1091
+ color: var(--dsw-alias-state-error-primary);
1092
+ }
1093
+ .pstack-icon-btn-danger:hover:not(:disabled) {
1094
+ background: var(--dsw-alias-interactive-bg-hover-danger);
1095
+ color: var(--dsw-alias-state-error-primary);
1096
+ }
1097
+ .pstack-icon-btn:focus-visible,
1098
+ .pstack-btn:focus-visible,
1099
+ .pstack-switch:focus-visible {
1100
+ outline: var(--dsw-focus-ring-width) solid
1101
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
1102
+ outline-offset: 2px;
1103
+ }
1104
+ .pstack-status {
1105
+ display: inline-flex;
1106
+ align-items: center;
1107
+ gap: 6px;
1108
+ color: var(--dsw-alias-label-tertiary);
1109
+ font-size: 12px;
1110
+ line-height: 18px;
1111
+ white-space: nowrap;
1112
+ }
1113
+ .pstack-status-dot {
1114
+ width: 8px;
1115
+ height: 8px;
1116
+ border-radius: 50%;
1117
+ background: currentColor;
1118
+ flex: none;
1119
+ }
1120
+ .pstack-status[data-tone="success"] {
1121
+ color: var(--dsw-alias-state-success-primary);
1122
+ }
1123
+ .pstack-status[data-tone="error"] {
1124
+ color: var(--dsw-alias-state-error-primary);
1125
+ }
1126
+ .pstack-status[data-tone="warn"] {
1127
+ color: var(--dsw-alias-state-warn-primary);
1128
+ }
1129
+ .pstack-status[data-tone="brand"] {
1130
+ color: var(--dsw-alias-state-business-primary);
1131
+ }
1132
+ .pstack-status[data-tone="neutral"] {
1133
+ color: var(--dsw-alias-label-tertiary);
1134
+ }
1135
+ .pstack-switch {
1136
+ box-sizing: border-box;
1137
+ position: relative;
1138
+ flex: none;
1139
+ width: 36px;
1140
+ height: 20px;
1141
+ padding: 2px;
1142
+ border: 0;
1143
+ border-radius: 999px;
1144
+ background: var(--dsw-alias-border-l3);
1145
+ cursor: pointer;
1146
+ }
1147
+ .pstack-switch[aria-checked="true"] {
1148
+ background: var(--dsw-alias-brand-primary);
1149
+ }
1150
+ .pstack-switch:disabled {
1151
+ cursor: default;
1152
+ opacity: 0.5;
1153
+ }
1154
+ .pstack-switch-thumb {
1155
+ display: block;
1156
+ width: 16px;
1157
+ height: 16px;
1158
+ border-radius: 50%;
1159
+ background: var(--dsw-alias-label-primary-foreground);
1160
+ transition: transform 0.12s ease;
1161
+ }
1162
+ .pstack-switch[aria-checked="true"] .pstack-switch-thumb {
1163
+ transform: translate(16px);
1164
+ }
1165
+ .pstack-switch[aria-checked="false"] .pstack-switch-thumb {
1166
+ background: var(--dsw-alias-switch-thumb);
1167
+ }
1168
+ .pstack-plugin-groups {
1169
+ display: flex;
1170
+ flex-direction: column;
1171
+ gap: 22px;
1172
+ }
1173
+ .pstack-plugin-group {
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ gap: 8px;
1177
+ }
1178
+ .pstack-plugin-group-head {
1179
+ display: flex;
1180
+ align-items: flex-start;
1181
+ justify-content: space-between;
1182
+ gap: 12px;
1183
+ padding: 0 2px;
1184
+ }
1185
+ .pstack-plugin-group-title {
1186
+ margin: 0;
1187
+ font-size: 13px;
1188
+ font-weight: 600;
1189
+ line-height: 20px;
1190
+ }
1191
+ .pstack-plugin-group-desc {
1192
+ margin: 2px 0 0;
1193
+ color: var(--dsw-alias-label-tertiary);
1194
+ font-size: 12px;
1195
+ line-height: 18px;
1196
+ }
1197
+ .pstack-plugin-list,
1198
+ .pstack-role-list {
1199
+ display: flex;
1200
+ flex-direction: column;
1201
+ gap: 8px;
1202
+ margin: 0;
1203
+ padding: 0;
1204
+ list-style: none;
1205
+ }
1206
+ .pstack-plugin-card {
1207
+ display: flex;
1208
+ align-items: flex-start;
1209
+ gap: 14px;
1210
+ padding: 14px 16px;
1211
+ border: 0.5px solid var(--dsw-alias-settings-card-stroke);
1212
+ border-radius: var(--dsw-radius-xl);
1213
+ background: var(--dsw-alias-settings-card-fill);
1214
+ }
1215
+ .pstack-plugin-card[data-conflict="true"] {
1216
+ border-color: color-mix(
1217
+ in srgb,
1218
+ var(--dsw-alias-state-error-primary) 35%,
1219
+ var(--dsw-alias-settings-card-stroke)
1220
+ );
1221
+ }
1222
+ .pstack-plugin-icon {
1223
+ display: grid;
1224
+ place-items: center;
1225
+ flex: none;
1226
+ width: 40px;
1227
+ height: 40px;
1228
+ border: 0.5px solid var(--dsw-alias-border-l3);
1229
+ border-radius: var(--dsw-radius-md);
1230
+ background: var(--dsw-alias-bg-layer-1);
1231
+ color: var(--dsw-alias-label-secondary);
1232
+ }
1233
+ .pstack-plugin-main {
1234
+ display: flex;
1235
+ flex: 1;
1236
+ flex-direction: column;
1237
+ gap: 3px;
1238
+ min-width: 0;
1239
+ }
1240
+ .pstack-plugin-title-row {
1241
+ display: flex;
1242
+ align-items: center;
1243
+ flex-wrap: wrap;
1244
+ gap: 8px;
1245
+ }
1246
+ .pstack-plugin-name {
1247
+ font-size: 14px;
1248
+ font-weight: 500;
1249
+ line-height: 22px;
1250
+ }
1251
+ .pstack-plugin-desc {
1252
+ margin: 0;
1253
+ color: var(--dsw-alias-label-secondary);
1254
+ font-size: 13px;
1255
+ line-height: 20px;
1256
+ }
1257
+ .pstack-plugin-id {
1258
+ color: var(--dsw-alias-label-tertiary);
1259
+ font-family: var(--ds-font-family-code);
1260
+ font-size: 11px;
1261
+ line-height: 16px;
1262
+ overflow-wrap: anywhere;
1263
+ }
1264
+ .pstack-plugin-end {
1265
+ display: flex;
1266
+ align-items: center;
1267
+ flex: none;
1268
+ gap: 8px;
1269
+ padding-top: 2px;
1270
+ }
1271
+ .pstack-role-card {
1272
+ display: flex;
1273
+ flex-direction: column;
1274
+ gap: 12px;
1275
+ padding: 14px 16px;
1276
+ border: 0.5px solid var(--dsw-alias-settings-card-stroke);
1277
+ border-radius: var(--dsw-radius-xl);
1278
+ background: var(--dsw-alias-settings-card-fill);
1279
+ }
1280
+ .pstack-role-card[data-dirty="true"] {
1281
+ border-color: color-mix(
1282
+ in srgb,
1283
+ var(--dsw-alias-state-warn-primary) 32%,
1284
+ var(--dsw-alias-settings-card-stroke)
1285
+ );
1286
+ }
1287
+ .pstack-budget-overlay {
1288
+ z-index: 1004;
1289
+ }
1290
+ .pstack-budget-panel {
1291
+ display: flex;
1292
+ flex-direction: column;
1293
+ width: 780px;
1294
+ height: min(700px, calc(100vh - 2 * max(24px, var(--dsh-frame-overlay-top, 24px))));
1295
+ }
1296
+ .pstack-budget-body {
1297
+ display: flex;
1298
+ flex: 1;
1299
+ flex-direction: column;
1300
+ gap: 12px;
1301
+ min-height: 0;
1302
+ }
1303
+ .pstack-budget-footer {
1304
+ display: flex;
1305
+ align-items: center;
1306
+ justify-content: flex-end;
1307
+ gap: 8px;
1308
+ padding: 12px 24px 18px;
1309
+ border-top: 0.5px solid var(--dsw-alias-border-l3);
1310
+ }
1311
+ .pstack-budget-bar {
1312
+ display: flex;
1313
+ align-items: center;
1314
+ justify-content: space-between;
1315
+ gap: 12px;
1316
+ margin-bottom: 12px;
1317
+ padding: 10px 12px;
1318
+ border: 0.5px solid var(--dsw-alias-settings-card-stroke);
1319
+ border-radius: var(--dsw-radius-lg);
1320
+ background: var(--dsw-alias-bg-module-platform);
1321
+ }
1322
+ .pstack-budget-status {
1323
+ display: flex;
1324
+ flex-direction: column;
1325
+ gap: 2px;
1326
+ color: var(--dsw-alias-label-secondary);
1327
+ font-size: 12px;
1328
+ line-height: 18px;
1329
+ }
1330
+ .pstack-effort-hint {
1331
+ display: flex;
1332
+ align-items: flex-start;
1333
+ gap: 8px;
1334
+ margin: 0 0 12px;
1335
+ padding: 10px 12px;
1336
+ border: 0.5px solid
1337
+ color-mix(in srgb, var(--dsw-alias-state-warn-primary) 35%, var(--dsw-alias-settings-card-stroke));
1338
+ border-radius: var(--dsw-radius-lg);
1339
+ background: color-mix(
1340
+ in srgb,
1341
+ var(--dsw-alias-state-warn-primary) 14%,
1342
+ var(--dsw-alias-bg-layer-1)
1343
+ );
1344
+ color: var(--dsw-alias-label-secondary);
1345
+ font-size: 12px;
1346
+ line-height: 18px;
1347
+ }
1348
+ .pstack-effort-hint > svg {
1349
+ flex: none;
1350
+ margin-top: 1px;
1351
+ color: var(--dsw-alias-state-warn-primary);
1352
+ }
1353
+ .pstack-budget-form {
1354
+ display: grid;
1355
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1356
+ gap: 8px;
1357
+ }
1358
+ .pstack-budget-preview {
1359
+ display: flex;
1360
+ flex-direction: column;
1361
+ gap: 8px;
1362
+ }
1363
+ .pstack-budget-preview-title {
1364
+ margin: 0;
1365
+ font-size: 12px;
1366
+ font-weight: 500;
1367
+ line-height: 18px;
1368
+ }
1369
+ .pstack-budget-list {
1370
+ display: flex;
1371
+ flex-direction: column;
1372
+ gap: 8px;
1373
+ }
1374
+ .pstack-budget-card {
1375
+ display: flex;
1376
+ flex-direction: column;
1377
+ gap: 8px;
1378
+ padding: 10px 12px;
1379
+ border: 0.5px solid var(--dsw-alias-settings-card-stroke);
1380
+ border-radius: var(--dsw-radius-lg);
1381
+ background: var(--dsw-alias-bg-layer-1);
1382
+ }
1383
+ .pstack-budget-card[data-status="needs-choice"],
1384
+ .pstack-budget-card[data-status="custom"] {
1385
+ border-color: color-mix(
1386
+ in srgb,
1387
+ var(--dsw-alias-state-error-primary) 28%,
1388
+ var(--dsw-alias-settings-card-stroke)
1389
+ );
1390
+ }
1391
+ .pstack-budget-card-head {
1392
+ display: flex;
1393
+ align-items: center;
1394
+ justify-content: space-between;
1395
+ gap: 8px;
1396
+ min-width: 0;
1397
+ }
1398
+ .pstack-budget-role {
1399
+ overflow-wrap: anywhere;
1400
+ color: var(--dsw-alias-label-primary);
1401
+ font-size: 13px;
1402
+ font-weight: 500;
1403
+ line-height: 20px;
1404
+ }
1405
+ .pstack-budget-route-grid {
1406
+ display: grid;
1407
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
1408
+ gap: 12px;
1409
+ }
1410
+ .pstack-budget-route-block {
1411
+ display: flex;
1412
+ flex-direction: column;
1413
+ gap: 3px;
1414
+ min-width: 0;
1415
+ }
1416
+ .pstack-budget-route-label {
1417
+ color: var(--dsw-alias-label-tertiary);
1418
+ font-size: 11px;
1419
+ line-height: 16px;
1420
+ }
1421
+ .pstack-budget-route-chips {
1422
+ display: flex;
1423
+ flex-wrap: wrap;
1424
+ gap: 4px;
1425
+ min-width: 0;
1426
+ }
1427
+ .pstack-budget-route-chip {
1428
+ display: inline-block;
1429
+ max-width: 100%;
1430
+ padding: 2px 6px;
1431
+ border-radius: var(--dsw-radius-xs);
1432
+ background: var(--dsw-alias-bg-module-platform);
1433
+ color: var(--dsw-alias-label-secondary);
1434
+ font-family: var(--ds-font-family-code);
1435
+ font-size: 11px;
1436
+ line-height: 16px;
1437
+ overflow-wrap: anywhere;
1438
+ }
1439
+ .pstack-role-head {
1440
+ display: flex;
1441
+ align-items: flex-start;
1442
+ gap: 12px;
1443
+ }
1444
+ .pstack-role-glyph {
1445
+ display: grid;
1446
+ place-items: center;
1447
+ flex: none;
1448
+ width: 32px;
1449
+ height: 32px;
1450
+ border-radius: var(--dsw-radius-md);
1451
+ background: var(--dsw-alias-bg-module-platform);
1452
+ color: var(--dsw-alias-label-secondary);
1453
+ }
1454
+ .pstack-role-title {
1455
+ display: flex;
1456
+ flex: 1;
1457
+ flex-direction: column;
1458
+ gap: 2px;
1459
+ min-width: 0;
1460
+ }
1461
+ .pstack-role-name-row {
1462
+ display: flex;
1463
+ align-items: center;
1464
+ flex-wrap: wrap;
1465
+ gap: 8px;
1466
+ }
1467
+ .pstack-role-name {
1468
+ margin: 0;
1469
+ font-size: 14px;
1470
+ font-weight: 500;
1471
+ line-height: 22px;
1472
+ }
1473
+ .pstack-code-chip {
1474
+ border: 0.5px solid var(--dsw-alias-border-l4);
1475
+ border-radius: var(--dsw-radius-xs);
1476
+ background: var(--dsw-alias-bg-module-platform);
1477
+ color: var(--dsw-alias-label-tertiary);
1478
+ font-family: var(--ds-font-family-code);
1479
+ font-size: 11px;
1480
+ line-height: 16px;
1481
+ padding: 1px 6px;
1482
+ }
1483
+ .pstack-tag {
1484
+ border: 0.5px solid var(--dsw-alias-border-l3);
1485
+ border-radius: var(--dsw-radius-xs);
1486
+ color: var(--dsw-alias-label-secondary);
1487
+ font-size: 11px;
1488
+ line-height: 16px;
1489
+ padding: 1px 6px;
1490
+ }
1491
+ .pstack-role-desc {
1492
+ margin: 0;
1493
+ color: var(--dsw-alias-label-tertiary);
1494
+ font-size: 12px;
1495
+ line-height: 18px;
1496
+ }
1497
+ .pstack-role-status {
1498
+ display: flex;
1499
+ align-items: center;
1500
+ gap: 8px;
1501
+ flex: none;
1502
+ padding-top: 4px;
1503
+ }
1504
+ .pstack-role-line {
1505
+ color: var(--dsw-alias-label-dimmed);
1506
+ font-size: 11px;
1507
+ line-height: 16px;
1508
+ white-space: nowrap;
1509
+ }
1510
+ .pstack-route-list {
1511
+ display: flex;
1512
+ flex-direction: column;
1513
+ gap: 8px;
1514
+ }
1515
+ .pstack-route-row {
1516
+ display: grid;
1517
+ gap: 8px;
1518
+ align-items: end;
1519
+ padding: 10px 10px 10px 12px;
1520
+ border-radius: var(--dsw-radius-lg);
1521
+ background: var(--dsw-alias-bg-module-platform);
1522
+ }
1523
+ .pstack-route-actions {
1524
+ display: flex;
1525
+ align-items: center;
1526
+ align-self: end;
1527
+ gap: 6px;
1528
+ }
1529
+ .pstack-route-actions .pstack-icon-btn {
1530
+ width: 32px;
1531
+ height: 32px;
1532
+ }
1533
+ .pstack-field {
1534
+ display: flex;
1535
+ flex-direction: column;
1536
+ gap: 4px;
1537
+ min-width: 0;
1538
+ }
1539
+ .pstack-field-label {
1540
+ color: var(--dsw-alias-label-tertiary);
1541
+ font-size: 11px;
1542
+ line-height: 16px;
1543
+ }
1544
+ .pstack-select {
1545
+ box-sizing: border-box;
1546
+ width: 100%;
1547
+ height: 32px;
1548
+ padding: 0 30px 0 10px;
1549
+ border: 0.5px solid var(--dsw-alias-border-l4);
1550
+ border-radius: var(--dsw-radius-md);
1551
+ appearance: none;
1552
+ background-color: var(--dsw-alias-bg-layer-1);
1553
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1554
+ background-position: right 10px center;
1555
+ background-repeat: no-repeat;
1556
+ background-size: 12px 12px;
1557
+ color: var(--dsw-alias-label-primary);
1558
+ font: inherit;
1559
+ font-size: 13px;
1560
+ line-height: 20px;
1561
+ cursor: pointer;
1562
+ }
1563
+ .pstack-select:focus {
1564
+ border-color: var(--dsw-alias-state-business-primary);
1565
+ outline: none;
1566
+ }
1567
+ .pstack-select:disabled {
1568
+ cursor: default;
1569
+ opacity: 0.6;
1570
+ }
1571
+ .pstack-role-foot {
1572
+ display: flex;
1573
+ align-items: center;
1574
+ justify-content: space-between;
1575
+ gap: 12px;
1576
+ }
1577
+ .pstack-role-foot-actions {
1578
+ display: flex;
1579
+ align-items: center;
1580
+ gap: 8px;
1581
+ min-width: 0;
1582
+ }
1583
+ .pstack-role-save {
1584
+ display: flex;
1585
+ align-items: center;
1586
+ gap: 8px;
1587
+ margin-left: auto;
1588
+ }
1589
+ .pstack-toast {
1590
+ position: fixed;
1591
+ z-index: 1002;
1592
+ top: max(24px, var(--dsh-frame-overlay-top, 24px));
1593
+ left: 50%;
1594
+ display: flex;
1595
+ align-items: center;
1596
+ gap: 6px;
1597
+ max-width: calc(100vw - 48px);
1598
+ padding: 8px 12px;
1599
+ border-radius: var(--dsw-radius-md);
1600
+ background: var(--dsw-alias-bg-layer-1);
1601
+ color: var(--dsw-alias-label-primary);
1602
+ box-shadow: var(--dsw-elevation-prominent);
1603
+ font-size: 12px;
1604
+ line-height: 18px;
1605
+ transform: translateX(-50%);
1606
+ }
1607
+ .pstack-toast[data-tone="success"] {
1608
+ color: var(--dsw-alias-state-success-primary);
1609
+ }
1610
+ .pstack-toast[data-tone="warn"] {
1611
+ color: var(--dsw-alias-state-warn-primary);
1612
+ }
1613
+ .pstack-toast[data-tone="error"] {
1614
+ color: var(--dsw-alias-state-error-primary);
1615
+ }
1616
+ .pstack-inline-message {
1617
+ display: inline-flex;
1618
+ align-items: center;
1619
+ gap: 4px;
1620
+ color: var(--dsw-alias-label-tertiary);
1621
+ font-size: 12px;
1622
+ line-height: 18px;
1623
+ }
1624
+ .pstack-inline-message[data-tone="success"] {
1625
+ color: var(--dsw-alias-state-success-primary);
1626
+ }
1627
+ .pstack-inline-message[data-tone="warn"] {
1628
+ color: var(--dsw-alias-state-warn-primary);
1629
+ }
1630
+ .pstack-inline-message[data-tone="error"] {
1631
+ color: var(--dsw-alias-state-error-primary);
1632
+ }
1633
+ .pstack-file-meta {
1634
+ display: flex;
1635
+ align-items: center;
1636
+ flex-wrap: nowrap;
1637
+ white-space: nowrap;
1638
+ gap: 8px;
1639
+ margin-top: 8px;
1640
+ color: var(--dsw-alias-label-tertiary);
1641
+ font-size: 12px;
1642
+ line-height: 18px;
1643
+ }
1644
+ .pstack-file-path {
1645
+ min-width: 0;
1646
+ overflow: hidden;
1647
+ text-overflow: ellipsis;
1648
+ white-space: nowrap;
1649
+ font-family: var(--ds-font-family-code);
1650
+ }
1651
+ .pstack-file-meta > span:not(.pstack-status) {
1652
+ flex: none;
1653
+ }
1654
+ .pstack-notice {
1655
+ display: flex;
1656
+ align-items: flex-start;
1657
+ gap: 8px;
1658
+ margin: 0 0 12px;
1659
+ padding: 10px 12px;
1660
+ border-radius: var(--dsw-radius-md);
1661
+ background: var(--dsw-alias-state-warn-tertiary);
1662
+ color: var(--dsw-alias-label-secondary);
1663
+ font-size: 12px;
1664
+ line-height: 18px;
1665
+ }
1666
+ .pstack-notice > svg {
1667
+ flex: none;
1668
+ margin-top: 1px;
1669
+ color: var(--dsw-alias-state-warn-primary);
1670
+ }
1671
+ .pstack-notice[data-tone="error"] {
1672
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent);
1673
+ }
1674
+ .pstack-notice[data-tone="error"] > svg {
1675
+ color: var(--dsw-alias-state-error-primary);
1676
+ }
1677
+ .pstack-notice-list {
1678
+ margin: 0;
1679
+ padding: 0 0 0 16px;
1680
+ }
1681
+ .pstack-loading {
1682
+ display: inline-flex;
1683
+ align-items: center;
1684
+ gap: 8px;
1685
+ color: var(--dsw-alias-label-tertiary);
1686
+ font-size: 13px;
1687
+ line-height: 20px;
1688
+ }
1689
+ .pstack-spinner {
1690
+ animation: pstack-spin 0.8s linear infinite;
1691
+ }
1692
+ @keyframes pstack-spin {
1693
+ to {
1694
+ transform: rotate(360deg);
1695
+ }
1696
+ }
1697
+ @media (prefers-reduced-motion: reduce) {
1698
+ .pstack-spinner,
1699
+ .pstack-segmented-indicator,
1700
+ .pstack-switch-thumb {
1701
+ animation: none;
1702
+ transition: none;
1703
+ }
1704
+ }
1705
+ @media (width <= 600px) {
1706
+ .pstack-panel {
1707
+ width: 100%;
1708
+ max-width: none;
1709
+ height: 100%;
1710
+ border-radius: 0;
1711
+ }
1712
+ .pstack-header {
1713
+ padding: 16px 12px 8px 16px;
1714
+ }
1715
+ .pstack-tabs-row {
1716
+ padding: 6px 16px 0;
1717
+ }
1718
+ .pstack-body {
1719
+ padding: 14px 16px 20px;
1720
+ }
1721
+ .pstack-section-head,
1722
+ .pstack-role-head {
1723
+ flex-wrap: wrap;
1724
+ }
1725
+ .pstack-section-actions {
1726
+ width: 100%;
1727
+ justify-content: flex-end;
1728
+ }
1729
+ .pstack-plugin-card {
1730
+ gap: 10px;
1731
+ padding: 12px;
1732
+ }
1733
+ .pstack-plugin-icon {
1734
+ display: none;
1735
+ }
1736
+ .pstack-route-row {
1737
+ grid-template-columns: minmax(0, 1fr) !important;
1738
+ }
1739
+ .pstack-role-foot {
1740
+ flex-wrap: wrap;
1741
+ }
1742
+ .pstack-role-save {
1743
+ width: 100%;
1744
+ justify-content: flex-end;
1745
+ }
1746
+ .pstack-budget-bar {
1747
+ align-items: flex-start;
1748
+ flex-direction: column;
1749
+ }
1750
+ .pstack-budget-form {
1751
+ grid-template-columns: minmax(0, 1fr);
1752
+ }
1753
+ .pstack-budget-route-grid {
1754
+ grid-template-columns: minmax(0, 1fr);
1755
+ }
1756
+ }
1757
+ `;
1758
+ let installed = false;
1759
+ /** Insert the pstack stylesheet once per document. */
1760
+ function ensurePstackStyles() {
1761
+ if (installed || typeof document === "undefined") return;
1762
+ installed = true;
1763
+ if (document.querySelector("style[data-pstack-css]") !== null) return;
1764
+ const tag = document.createElement("style");
1765
+ tag.dataset.pstackCss = "";
1766
+ tag.textContent = CSS;
1767
+ document.head.appendChild(tag);
1768
+ }
1769
+ // src/client/message.ts
1770
+ /** Convert an unknown rejection into a display string. */
1771
+ function messageOf(reason) {
1772
+ return reason instanceof Error ? reason.message : String(reason);
1773
+ }
1774
+ // src/client/toast.tsx
1775
+ /** Temporary, top-level feedback shared by Client pages. */
1776
+ ensurePstackStyles();
1777
+ /** Portal-based feedback that expires without occupying space in the calling page. */
1778
+ function Toast({ message, duration = 3e3 }) {
1779
+ const [dismissed, setDismissed] = (0, react.useState)();
1780
+ const current = message === dismissed ? void 0 : message;
1781
+ (0, react.useEffect)(() => {
1782
+ if (message === void 0 || message === dismissed) return;
1783
+ const timer = setTimeout(() => setDismissed(message), duration);
1784
+ return () => clearTimeout(timer);
1785
+ }, [
1786
+ message,
1787
+ dismissed,
1788
+ duration
1789
+ ]);
1790
+ if (current === void 0 || typeof document === "undefined") return null;
1791
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1792
+ className: "pstack-toast",
1793
+ "data-tone": current.tone,
1794
+ role: current.tone === "error" || current.tone === "warn" ? "alert" : "status",
1795
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastIcon, { tone: current.tone }), current.text]
1796
+ }), document.body);
1797
+ }
1798
+ function ToastIcon({ tone }) {
1799
+ if (tone === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, { size: 12 });
1800
+ if (tone === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 12 });
1801
+ if (tone === "warn") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconInfo, { size: 12 });
1802
+ return null;
1803
+ }
1804
+ // src/client/ui.tsx
1805
+ function Button(props) {
1806
+ const { variant = "ghost", size = "md", icon, children, className, ...rest } = props;
1807
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1808
+ type: "button",
1809
+ ...rest,
1810
+ className: cx("pstack-btn", `pstack-btn-${variant}`, size === "sm" ? "pstack-btn-sm" : void 0, className),
1811
+ children: [icon, children]
1812
+ });
1813
+ }
1814
+ function IconButton(props) {
1815
+ const { label, danger = false, children, className, ...rest } = props;
1816
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1817
+ type: "button",
1818
+ ...rest,
1819
+ "aria-label": label,
1820
+ title: label,
1821
+ className: cx("pstack-icon-btn", danger ? "pstack-icon-btn-danger" : void 0, className),
1822
+ children
1823
+ });
1824
+ }
1825
+ function Switch(props) {
1826
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1827
+ type: "button",
1828
+ role: "switch",
1829
+ "aria-checked": props.checked,
1830
+ "aria-label": props.label,
1831
+ title: props.label,
1832
+ disabled: props.disabled,
1833
+ className: "pstack-switch",
1834
+ onClick: () => props.onChange(!props.checked),
1835
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "pstack-switch-thumb" })
1836
+ });
1837
+ }
1838
+ function Status(props) {
1839
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1840
+ className: "pstack-status",
1841
+ "data-tone": props.tone,
1842
+ title: props.title,
1843
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1844
+ className: "pstack-status-dot",
1845
+ "aria-hidden": "true"
1846
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: props.label })]
1847
+ });
1848
+ }
1849
+ function Spinner({ size = 14 }) {
1850
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, {
1851
+ size,
1852
+ className: "pstack-spinner"
1853
+ });
1854
+ }
1855
+ function SegmentedTabs(props) {
1856
+ const { items, value, onChange, label } = props;
1857
+ const selected = Math.max(0, items.findIndex((item) => item.value === value));
1858
+ const style = {
1859
+ "--pstack-segment-count": items.length,
1860
+ "--pstack-segment-index": selected
1861
+ };
1862
+ const focusTab = (event, index) => {
1863
+ const next = (event.currentTarget.parentElement?.querySelectorAll("[role=\"tab\"]"))?.[index];
1864
+ if (next === void 0) return;
1865
+ onChange(items[index].value);
1866
+ next.focus();
1867
+ };
1868
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1869
+ role: "tablist",
1870
+ "aria-label": label,
1871
+ className: "pstack-segmented",
1872
+ style,
1873
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1874
+ className: "pstack-segmented-indicator",
1875
+ "aria-hidden": "true"
1876
+ }), items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1877
+ type: "button",
1878
+ role: "tab",
1879
+ id: item.id,
1880
+ "aria-controls": item.panelId,
1881
+ "aria-selected": item.value === value,
1882
+ tabIndex: item.value === value ? 0 : -1,
1883
+ className: "pstack-segment",
1884
+ onClick: () => onChange(item.value),
1885
+ onKeyDown: (event) => {
1886
+ if (event.key === "ArrowRight") {
1887
+ event.preventDefault();
1888
+ focusTab(event, (index + 1) % items.length);
1889
+ } else if (event.key === "ArrowLeft") {
1890
+ event.preventDefault();
1891
+ focusTab(event, (index - 1 + items.length) % items.length);
1892
+ } else if (event.key === "Home") {
1893
+ event.preventDefault();
1894
+ focusTab(event, 0);
1895
+ } else if (event.key === "End") {
1896
+ event.preventDefault();
1897
+ focusTab(event, items.length - 1);
1898
+ }
1899
+ },
1900
+ children: item.label
1901
+ }, item.value))]
1902
+ });
1903
+ }
1904
+ function cx(...values) {
1905
+ return values.filter((value) => typeof value === "string" && value !== "").join(" ");
1906
+ }
1907
+ /** Confirm a destructive action with the one browser dialog the plugin uses. */
1908
+ function confirmDiscard(message) {
1909
+ if (typeof window === "undefined") return true;
1910
+ const confirm = window.confirm;
1911
+ return typeof confirm !== "function" || confirm.call(window, message);
1912
+ }
1913
+ // src/client/RecommendedPluginsPage.tsx
1914
+ /** Related official plugins for the pstack plugin configuration dialog. */
1915
+ ensurePstackStyles();
1916
+ const PLUGIN_GROUPS = [
1917
+ {
1918
+ id: "recommended",
1919
+ titleKey: "groupRecommended",
1920
+ descriptionKey: "groupRecommendedDescription",
1921
+ plugins: [{
1922
+ name: "@deepseek-ai/dsh-experimental-forge-bundle",
1923
+ titleKey: "recommendedForgeTitle",
1924
+ descriptionKey: "recommendedForgeDescription",
1925
+ icon: IconGitBranch
1926
+ }, {
1927
+ name: "@deepseek-ai/dsh-experimental-session-search",
1928
+ titleKey: "recommendedSessionSearchTitle",
1929
+ descriptionKey: "recommendedSessionSearchDescription",
1930
+ icon: IconSearch
1931
+ }]
1932
+ },
1933
+ {
1934
+ id: "normal",
1935
+ titleKey: "groupNormal",
1936
+ descriptionKey: "groupNormalDescription",
1937
+ plugins: [
1938
+ {
1939
+ name: "@deepseek-ai/dsh-browser-use",
1940
+ titleKey: "recommendedBrowserTitle",
1941
+ descriptionKey: "recommendedBrowserDescription",
1942
+ icon: IconBrowser
1943
+ },
1944
+ {
1945
+ name: "@deepseek-ai/dsh-cua-bundle",
1946
+ titleKey: "recommendedCuaTitle",
1947
+ descriptionKey: "recommendedCuaDescription",
1948
+ icon: IconMonitor
1949
+ },
1950
+ {
1951
+ name: "@deepseek-ai/dsh-experimental-terminal-bundle",
1952
+ titleKey: "recommendedTerminalTitle",
1953
+ descriptionKey: "recommendedTerminalDescription",
1954
+ icon: IconTerminal
1955
+ },
1956
+ {
1957
+ name: "@deepseek-ai/dsh-mcp-client",
1958
+ titleKey: "recommendedMcpTitle",
1959
+ descriptionKey: "recommendedMcpDescription",
1960
+ icon: IconPlug
1961
+ },
1962
+ {
1963
+ name: "@deepseek-ai/dsh-experimental-ralph-bundle",
1964
+ titleKey: "recommendedRalphTitle",
1965
+ descriptionKey: "recommendedRalphDescription",
1966
+ icon: IconRepeat
1967
+ }
1968
+ ]
1969
+ },
1970
+ {
1971
+ id: "conflict",
1972
+ titleKey: "groupConflict",
1973
+ descriptionKey: "groupConflictDescription",
1974
+ plugins: [{
1975
+ name: "@deepseek-ai/dsh-experimental-agent-team-profile",
1976
+ titleKey: "recommendedAgentTeamTitle",
1977
+ descriptionKey: "recommendedAgentTeamDescription",
1978
+ icon: IconUsers
1979
+ }]
1980
+ }
1981
+ ];
1982
+ const ALL = "__all__";
1983
+ function RecommendedPluginsView(props) {
1984
+ const [bundles, setBundles] = (0, react.useState)([]);
1985
+ const [loading, setLoading] = (0, react.useState)(true);
1986
+ const [error, setError] = (0, react.useState)();
1987
+ const [busy, setBusy] = (0, react.useState)();
1988
+ const [messages, setMessages] = (0, react.useState)({});
1989
+ const [toast, setToast] = (0, react.useState)();
1990
+ const { api, translations } = props;
1991
+ const t = translations.plugins;
1992
+ const common = translations.common;
1993
+ const load = async () => {
1994
+ if (api === void 0) {
1995
+ setLoading(false);
1996
+ setError(t("loadFailed", { message: t("managerUnavailable") }));
1997
+ return;
1998
+ }
1999
+ setLoading(true);
2000
+ setError(void 0);
2001
+ try {
2002
+ setBundles(await api.listBundles());
2003
+ } catch (reason) {
2004
+ setError(t("loadFailed", { message: pluginMessage(reason, t) }));
2005
+ } finally {
2006
+ setLoading(false);
2007
+ }
2008
+ };
2009
+ (0, react.useEffect)(() => {
2010
+ load();
2011
+ }, [api, t]);
2012
+ const byName = new Map(bundles.map((bundle) => [bundle.name, bundle]));
2013
+ const available = (bundle) => bundle !== void 0 && bundle.availability !== "missing" && bundle.error === void 0 && bundle.readOnlyReason === void 0;
2014
+ const switchable = (bundle, group) => available(bundle) && (group !== "conflict" || bundle?.enabled === true);
2015
+ const recommended = PLUGIN_GROUPS.find((group) => group.id === "recommended");
2016
+ const recommendedEnabled = recommended.plugins.filter((plugin) => byName.get(plugin.name)?.enabled === true).length;
2017
+ const enableable = recommended.plugins.filter((plugin) => {
2018
+ const bundle = byName.get(plugin.name);
2019
+ return switchable(bundle, "recommended") && bundle?.enabled !== true;
2020
+ }).length;
2021
+ const toggle = async (bundle) => {
2022
+ if (api === void 0) return;
2023
+ setBusy(bundle.name);
2024
+ setToast(void 0);
2025
+ setMessages((current) => {
2026
+ const next = { ...current };
2027
+ delete next[bundle.name];
2028
+ return next;
2029
+ });
2030
+ try {
2031
+ const change = await api.setBundleEnabled(bundle.name, !bundle.enabled);
2032
+ setMessages((current) => ({
2033
+ ...current,
2034
+ [bundle.name]: changeNotice(t, bundle.name, change)
2035
+ }));
2036
+ await load();
2037
+ } catch (reason) {
2038
+ setMessages((current) => ({
2039
+ ...current,
2040
+ [bundle.name]: {
2041
+ tone: "error",
2042
+ text: t("pluginChangeFailed", {
2043
+ name: bundle.name,
2044
+ reason: pluginMessage(reason, t)
2045
+ })
2046
+ }
2047
+ }));
2048
+ } finally {
2049
+ setBusy(void 0);
2050
+ }
2051
+ };
2052
+ const enableAll = async () => {
2053
+ if (api === void 0) return;
2054
+ const targets = recommended.plugins.map((plugin) => byName.get(plugin.name)).filter((bundle) => bundle !== void 0 && switchable(bundle, "recommended") && !bundle.enabled);
2055
+ if (targets.length === 0) return;
2056
+ setBusy(ALL);
2057
+ setToast(void 0);
2058
+ let settled = 0;
2059
+ let restarts = 0;
2060
+ let unsettled = 0;
2061
+ for (const bundle of targets) try {
2062
+ const change = await api.setBundleEnabled(bundle.name, true);
2063
+ if (change.application === "restart-required") restarts += 1;
2064
+ if (change.application === "applied") settled += 1;
2065
+ else if (change.application !== "restart-required") unsettled += 1;
2066
+ } catch {
2067
+ unsettled += 1;
2068
+ }
2069
+ await load();
2070
+ setBusy(void 0);
2071
+ setToast(unsettled > 0 ? {
2072
+ tone: "error",
2073
+ text: t("enableAllPartial", {
2074
+ settled,
2075
+ restarts,
2076
+ unsettled
2077
+ })
2078
+ } : restarts > 0 ? {
2079
+ tone: "warn",
2080
+ text: t("enableAllRestart", { count: restarts })
2081
+ } : {
2082
+ tone: "success",
2083
+ text: t("enableAllDone", { count: settled })
2084
+ });
2085
+ };
2086
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [
2087
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2088
+ className: "pstack-section-head",
2089
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2090
+ className: "pstack-section-desc",
2091
+ children: t("pluginsSubtitle")
2092
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2093
+ className: "pstack-section-actions",
2094
+ children: [
2095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2096
+ tone: recommendedEnabled === recommended.plugins.length ? "success" : recommendedEnabled === 0 ? "neutral" : "brand",
2097
+ label: t("pluginsSummary", {
2098
+ enabled: recommendedEnabled,
2099
+ total: recommended.plugins.length
2100
+ })
2101
+ }),
2102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2103
+ variant: "outline",
2104
+ size: "sm",
2105
+ icon: busy === ALL ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheckCircle, { size: 13 }),
2106
+ disabled: loading || busy !== void 0 || enableable === 0,
2107
+ onClick: () => void enableAll(),
2108
+ children: busy === ALL ? t("enableAllBusy") : enableable > 0 ? t("enableAllCount", { count: enableable }) : recommendedEnabled === recommended.plugins.length ? t("enableAllComplete") : t("enableAll")
2109
+ }),
2110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2111
+ label: common("refresh"),
2112
+ disabled: loading || busy !== void 0,
2113
+ onClick: () => void load(),
2114
+ children: loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, { size: 14 })
2115
+ })
2116
+ ]
2117
+ })]
2118
+ }),
2119
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2120
+ className: "pstack-notice",
2121
+ "data-tone": "error",
2122
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error })]
2123
+ }),
2124
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toast, { message: toast }),
2125
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2126
+ className: "pstack-plugin-groups",
2127
+ children: PLUGIN_GROUPS.map((group) => {
2128
+ const enabledInGroup = group.plugins.filter((plugin) => byName.get(plugin.name)?.enabled === true).length;
2129
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2130
+ className: "pstack-plugin-group",
2131
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2132
+ className: "pstack-plugin-group-head",
2133
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2134
+ className: "pstack-plugin-group-title",
2135
+ children: t(group.titleKey)
2136
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2137
+ className: "pstack-plugin-group-desc",
2138
+ children: t(group.descriptionKey)
2139
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2140
+ tone: group.id === "conflict" ? "error" : enabledInGroup > 0 ? "success" : "neutral",
2141
+ label: `${enabledInGroup}/${group.plugins.length}`
2142
+ })]
2143
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2144
+ className: "pstack-plugin-list",
2145
+ children: group.plugins.map((plugin) => {
2146
+ const bundle = byName.get(plugin.name);
2147
+ const status = bundleStatus(bundle, group.id, t);
2148
+ const canSwitch = switchable(bundle, group.id);
2149
+ const title = t(plugin.titleKey);
2150
+ const action = bundle?.enabled === true ? t("disablePlugin") : t("enablePlugin");
2151
+ const PluginIcon = plugin.icon;
2152
+ const message = messages[plugin.name];
2153
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
2154
+ className: "pstack-plugin-card",
2155
+ "data-conflict": group.id === "conflict" ? "true" : void 0,
2156
+ children: [
2157
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2158
+ className: "pstack-plugin-icon",
2159
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginIcon, { size: 18 })
2160
+ }),
2161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2162
+ className: "pstack-plugin-main",
2163
+ children: [
2164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2165
+ className: "pstack-plugin-title-row",
2166
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2167
+ className: "pstack-plugin-name",
2168
+ children: title
2169
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2170
+ tone: status.tone,
2171
+ label: status.label,
2172
+ title: bundle?.error?.diagnostic ?? bundle?.readOnlyReason
2173
+ })]
2174
+ }),
2175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2176
+ className: "pstack-plugin-desc",
2177
+ children: t(plugin.descriptionKey)
2178
+ }),
2179
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2180
+ className: "pstack-plugin-id",
2181
+ children: plugin.name
2182
+ }),
2183
+ message === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2184
+ className: "pstack-inline-message",
2185
+ "data-tone": message.tone,
2186
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeIcon, { tone: message.tone }), message.text]
2187
+ })
2188
+ ]
2189
+ }),
2190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2191
+ className: "pstack-plugin-end",
2192
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
2193
+ checked: bundle?.enabled === true,
2194
+ disabled: !canSwitch || busy !== void 0,
2195
+ label: `${action} ${title}`,
2196
+ onChange: () => bundle === void 0 ? void 0 : void toggle(bundle)
2197
+ })
2198
+ })
2199
+ ]
2200
+ }) }, plugin.name);
2201
+ })
2202
+ })]
2203
+ }, group.id);
2204
+ })
2205
+ })
2206
+ ] });
2207
+ }
2208
+ function pluginMessage(reason, t) {
2209
+ const message = messageOf(reason);
2210
+ return message === "plugin manager is not available yet" ? t("managerUnavailable") : message;
2211
+ }
2212
+ function bundleStatus(bundle, group, t) {
2213
+ if (bundle === void 0 || bundle.availability === "missing") return {
2214
+ label: t("pluginMissing"),
2215
+ tone: "warn"
2216
+ };
2217
+ if (bundle.error !== void 0) return {
2218
+ label: t("pluginUnavailable"),
2219
+ tone: "error"
2220
+ };
2221
+ if (bundle.readOnlyReason !== void 0) return {
2222
+ label: t("pluginReadOnly"),
2223
+ tone: "neutral"
2224
+ };
2225
+ if (group === "conflict") return {
2226
+ label: t("pluginConflict"),
2227
+ tone: "error"
2228
+ };
2229
+ return bundle.enabled ? {
2230
+ label: t("pluginEnabled"),
2231
+ tone: "success"
2232
+ } : {
2233
+ label: t("pluginDisabled"),
2234
+ tone: "neutral"
2235
+ };
2236
+ }
2237
+ function changeNotice(t, name, change) {
2238
+ switch (change.application) {
2239
+ case "applied": return {
2240
+ tone: "success",
2241
+ text: t("pluginChangeApplied", { name })
2242
+ };
2243
+ case "restart-required": return {
2244
+ tone: "warn",
2245
+ text: change.restartReason === void 0 ? t("pluginChangeRestart", { name }) : t("pluginChangeRestartReason", {
2246
+ name,
2247
+ reason: change.restartReason
2248
+ })
2249
+ };
2250
+ case "overridden": return {
2251
+ tone: "warn",
2252
+ text: t("pluginChangeOverridden", { name })
2253
+ };
2254
+ case "cancelled": return {
2255
+ tone: "neutral",
2256
+ text: t("pluginChangeCancelled", { name })
2257
+ };
2258
+ case "failed": return {
2259
+ tone: "error",
2260
+ text: t("pluginChangeFailed", {
2261
+ name,
2262
+ reason: describeChangeError(t, change.error)
2263
+ })
2264
+ };
2265
+ }
2266
+ }
2267
+ function describeChangeError(t, error) {
2268
+ if (error === void 0) return t("pluginChangeUnknownReason");
2269
+ const parts = [error.code, error.diagnostic].filter((part) => part !== void 0 && part.trim() !== "");
2270
+ return parts.length === 0 ? t("pluginChangeUnknownReason") : parts.join(": ");
2271
+ }
2272
+ function NoticeIcon({ tone }) {
2273
+ if (tone === "success") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, { size: 12 });
2274
+ if (tone === "neutral") return null;
2275
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 12 });
2276
+ }
2277
+ // src/client/role-copy.ts
2278
+ /** Locale keys for the display name and scenario of every pstack role. */
2279
+ const ROLE_COPY = {
2280
+ "feature, refactoring": {
2281
+ nameKey: "roleFeatureName",
2282
+ descriptionKey: "roleFeatureDescription"
2283
+ },
2284
+ "bug-fix": {
2285
+ nameKey: "roleBugFixName",
2286
+ descriptionKey: "roleBugFixDescription"
2287
+ },
2288
+ "perf-issue": {
2289
+ nameKey: "rolePerfName",
2290
+ descriptionKey: "rolePerfDescription"
2291
+ },
2292
+ hillclimb: {
2293
+ nameKey: "roleHillclimbName",
2294
+ descriptionKey: "roleHillclimbDescription"
2295
+ },
2296
+ "judgment and prose": {
2297
+ nameKey: "roleJudgmentName",
2298
+ descriptionKey: "roleJudgmentDescription"
2299
+ },
2300
+ "hardest tasks": {
2301
+ nameKey: "roleHardestName",
2302
+ descriptionKey: "roleHardestDescription"
2303
+ },
2304
+ "how explorer": {
2305
+ nameKey: "roleHowExplorerName",
2306
+ descriptionKey: "roleHowExplorerDescription"
2307
+ },
2308
+ "how explainer": {
2309
+ nameKey: "roleHowExplainerName",
2310
+ descriptionKey: "roleHowExplainerDescription"
2311
+ },
2312
+ "why investigators": {
2313
+ nameKey: "roleWhyInvestigatorsName",
2314
+ descriptionKey: "roleWhyInvestigatorsDescription"
2315
+ },
2316
+ "why synthesizer": {
2317
+ nameKey: "roleWhySynthesizerName",
2318
+ descriptionKey: "roleWhySynthesizerDescription"
2319
+ },
2320
+ "reflect tooling": {
2321
+ nameKey: "roleReflectToolingName",
2322
+ descriptionKey: "roleReflectToolingDescription"
2323
+ },
2324
+ "reflect judgment, divergent, synthesizer": {
2325
+ nameKey: "roleReflectJudgmentName",
2326
+ descriptionKey: "roleReflectJudgmentDescription"
2327
+ },
2328
+ "arena runners": {
2329
+ nameKey: "roleArenaRunnersName",
2330
+ descriptionKey: "roleArenaRunnersDescription"
2331
+ },
2332
+ "arena cross-judge pool": {
2333
+ nameKey: "roleArenaJudgesName",
2334
+ descriptionKey: "roleArenaJudgesDescription"
2335
+ },
2336
+ "swarm workers": {
2337
+ nameKey: "roleSwarmName",
2338
+ descriptionKey: "roleSwarmDescription"
2339
+ },
2340
+ "architect runners": {
2341
+ nameKey: "roleArchitectName",
2342
+ descriptionKey: "roleArchitectDescription"
2343
+ },
2344
+ "interrogate reviewers": {
2345
+ nameKey: "roleInterrogateName",
2346
+ descriptionKey: "roleInterrogateDescription"
2347
+ }
2348
+ };
2349
+ /** User-facing role name for a manifest key, falling back to the raw key. */
2350
+ function roleLabel(key, t) {
2351
+ return isRoleKey(key) ? t(ROLE_COPY[key].nameKey) : key;
2352
+ }
2353
+ // src/client/role-selectors.ts
2354
+ function providerIssue(provider) {
2355
+ if (provider.error !== void 0) return "unavailable";
2356
+ if (!provider.models.some((model) => model.error === void 0)) return "noModels";
2357
+ }
2358
+ function providerIsUsable(provider) {
2359
+ return providerIssue(provider) === void 0;
2360
+ }
2361
+ function modelChoices(catalogue, provider) {
2362
+ return catalogue?.providers.find((entry) => entry.provider === provider)?.models ?? [];
2363
+ }
2364
+ function providerLabel(providers, providerId) {
2365
+ const provider = providers.find((entry) => entry.provider === providerId);
2366
+ if (provider === void 0) return providerId;
2367
+ return providers.some((entry) => entry.provider !== providerId && entry.name === provider.name) ? `${provider.name} (${provider.provider})` : provider.name;
2368
+ }
2369
+ function effortChoices(catalogue, provider, model) {
2370
+ return modelChoices(catalogue, provider).find((entry) => entry.id === model)?.efforts ?? [];
2371
+ }
2372
+ function routeIsIncomplete(route) {
2373
+ return route.kind === "route" && (route.provider.trim() === "" || route.model.trim() === "");
2374
+ }
2375
+ function routeIsInvalid(route, catalogue) {
2376
+ if (route.kind !== "route") return false;
2377
+ const provider = catalogue?.providers.find((entry) => entry.provider === route.provider);
2378
+ if (provider === void 0 || provider.error !== void 0) return true;
2379
+ const model = provider.models.find((entry) => entry.id === route.model);
2380
+ if (model === void 0 || model.error !== void 0) return true;
2381
+ return route.effort !== void 0 && !model.efforts.includes(route.effort);
2382
+ }
2383
+ // src/client/BudgetDialog.tsx
2384
+ /** Nested budget modal for the role settings page: preview a plan and apply it to the role file. */
2385
+ const TIER_LABEL = {
2386
+ unlimited: "budgetTierUnlimited",
2387
+ large: "budgetTierLarge",
2388
+ medium: "budgetTierMedium",
2389
+ small: "budgetTierSmall"
2390
+ };
2391
+ function BudgetDialog(props) {
2392
+ const { snapshot, catalogue, translations, onApply, onApplied, onClose, signal } = props;
2393
+ const t = translations.roles;
2394
+ const titleId = (0, react.useId)();
2395
+ const panelRef = (0, react.useRef)(null);
2396
+ const providers = catalogue.providers;
2397
+ const usableProviders = providers.filter(providerIsUsable);
2398
+ const initialProvider = usableProviders[0];
2399
+ const [scope, setScope] = (0, react.useState)("concrete");
2400
+ const [tier, setTier] = (0, react.useState)(() => budgetTierFromLabel(snapshot.budget) ?? "large");
2401
+ const [providerId, setProviderId] = (0, react.useState)(initialProvider?.provider ?? "");
2402
+ const activeProvider = usableProviders.find((provider) => provider.provider === providerId) ?? initialProvider;
2403
+ const models = activeProvider?.models.filter((model) => model.error === void 0) ?? [];
2404
+ const [modelId, setModelId] = (0, react.useState)(models[0]?.id ?? "");
2405
+ const activeModel = models.find((model) => model.id === modelId) ?? models[0];
2406
+ const [busy, setBusy] = (0, react.useState)(false);
2407
+ const [error, setError] = (0, react.useState)();
2408
+ const requestClose = (0, react.useCallback)(() => {
2409
+ if (!busy) onClose();
2410
+ }, [busy, onClose]);
2411
+ useFocusTrap(panelRef, requestClose, true, {
2412
+ stopEscapePropagation: true,
2413
+ capture: true
2414
+ });
2415
+ (0, react.useEffect)(() => {
2416
+ return () => {
2417
+ document.getElementById("pstack-budget-trigger")?.focus();
2418
+ };
2419
+ }, []);
2420
+ const request = {
2421
+ tier,
2422
+ scope,
2423
+ ...scope === "unconfigured" && activeProvider !== void 0 && activeModel !== void 0 ? { defaultModel: {
2424
+ provider: activeProvider.provider,
2425
+ model: activeModel.id
2426
+ } } : {}
2427
+ };
2428
+ const plan = (0, react.useMemo)(() => {
2429
+ try {
2430
+ return {
2431
+ plan: planBudget(snapshot.roles.map((role) => ({
2432
+ key: role.key,
2433
+ shape: role.shape,
2434
+ autoPanel: findRole(role.key)?.autoPanel ?? false,
2435
+ configured: role.configured,
2436
+ routes: role.routes,
2437
+ hasIssues: role.issues.length > 0
2438
+ })), catalogue, request),
2439
+ error: void 0
2440
+ };
2441
+ } catch (reason) {
2442
+ return {
2443
+ plan: void 0,
2444
+ error: budgetPlanErrorText(reason, t)
2445
+ };
2446
+ }
2447
+ }, [
2448
+ snapshot.roles,
2449
+ catalogue,
2450
+ request.tier,
2451
+ request.scope,
2452
+ request.defaultModel?.provider,
2453
+ request.defaultModel?.model
2454
+ ]);
2455
+ const visibleRows = plan.plan?.rows.filter((row) => row.status !== "skipped") ?? [];
2456
+ const blocked = plan.plan?.rows.some(isBudgetRowBlocked) ?? false;
2457
+ const routeChanged = plan.plan?.rows.some((row) => row.status !== "skipped" && renderRouteValue(row.routes) !== renderRouteValue(row.previous)) ?? false;
2458
+ const budgetChanged = plan.plan !== void 0 && snapshot.budget !== budgetValueFor(plan.plan.tier);
2459
+ const canApply = plan.error === void 0 && !blocked && (routeChanged || budgetChanged) && !busy;
2460
+ const apply = async () => {
2461
+ setBusy(true);
2462
+ setError(void 0);
2463
+ try {
2464
+ const result = await onApply(request, signal);
2465
+ if (result.ok) onApplied(result.snapshot);
2466
+ else setError(t("budgetFailed", { reason: result.reason }));
2467
+ } catch (reason) {
2468
+ setError(t("budgetFailed", { reason: messageOf(reason) }));
2469
+ } finally {
2470
+ setBusy(false);
2471
+ }
2472
+ };
2473
+ if (typeof document === "undefined") return null;
2474
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2475
+ className: "pstack-overlay pstack-budget-overlay",
2476
+ role: "presentation",
2477
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2478
+ className: "pstack-mask",
2479
+ "aria-hidden": "true",
2480
+ onClick: requestClose
2481
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2482
+ className: "pstack-panel pstack-budget-panel",
2483
+ role: "dialog",
2484
+ "aria-modal": "true",
2485
+ "aria-labelledby": titleId,
2486
+ tabIndex: -1,
2487
+ ref: panelRef,
2488
+ children: [
2489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2490
+ className: "pstack-header",
2491
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2492
+ className: "pstack-title",
2493
+ id: titleId,
2494
+ children: t("budgetTitle")
2495
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2496
+ className: "pstack-section-desc",
2497
+ children: t("budgetCurrent", { budget: budgetLabel(snapshot.budget, t) })
2498
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
2499
+ label: translations.common("close"),
2500
+ className: "pstack-icon-btn-plain",
2501
+ onClick: requestClose,
2502
+ autoFocus: true,
2503
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClose, { size: 14 })
2504
+ })]
2505
+ }),
2506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2507
+ className: "pstack-body pstack-budget-body",
2508
+ children: [
2509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2510
+ className: "pstack-budget-form",
2511
+ children: [
2512
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2513
+ className: "pstack-field",
2514
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2515
+ className: "pstack-field-label",
2516
+ children: t("budgetScope")
2517
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2518
+ className: "pstack-select",
2519
+ value: scope,
2520
+ disabled: busy,
2521
+ onChange: (event) => setScope(event.target.value),
2522
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2523
+ value: "concrete",
2524
+ children: t("budgetScopeConcrete")
2525
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2526
+ value: "unconfigured",
2527
+ children: t("budgetScopeUnconfigured")
2528
+ })]
2529
+ })]
2530
+ }),
2531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2532
+ className: "pstack-field",
2533
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2534
+ className: "pstack-field-label",
2535
+ children: t("budgetTier")
2536
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
2537
+ className: "pstack-select",
2538
+ value: tier,
2539
+ disabled: busy,
2540
+ onChange: (event) => setTier(event.target.value),
2541
+ children: BUDGET_TIERS.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2542
+ value,
2543
+ children: t(TIER_LABEL[value])
2544
+ }, value))
2545
+ })]
2546
+ }),
2547
+ scope === "unconfigured" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2548
+ className: "pstack-field",
2549
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2550
+ className: "pstack-field-label",
2551
+ children: t("budgetProvider")
2552
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2553
+ className: "pstack-select",
2554
+ value: activeProvider?.provider ?? "",
2555
+ disabled: busy || usableProviders.length === 0,
2556
+ onChange: (event) => {
2557
+ const next = usableProviders.find((provider) => provider.provider === event.target.value);
2558
+ setProviderId(next?.provider ?? "");
2559
+ setModelId(next?.models.find((model) => model.error === void 0)?.id ?? "");
2560
+ },
2561
+ children: [usableProviders.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2562
+ value: "",
2563
+ children: t("budgetNoProvider")
2564
+ }) : null, providers.map((provider) => {
2565
+ const issue = providerIssue(provider);
2566
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2567
+ value: provider.provider,
2568
+ disabled: issue !== void 0,
2569
+ title: provider.error,
2570
+ children: [providerLabel(providers, provider.provider), issue === void 0 ? null : ` (${t(issue === "unavailable" ? "providerUnavailable" : "noModels")})`]
2571
+ }, provider.provider);
2572
+ })]
2573
+ })]
2574
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2575
+ className: "pstack-field",
2576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2577
+ className: "pstack-field-label",
2578
+ children: t("budgetModel")
2579
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2580
+ className: "pstack-select",
2581
+ value: activeModel?.id ?? "",
2582
+ disabled: busy || models.length === 0,
2583
+ onChange: (event) => setModelId(event.target.value),
2584
+ children: [models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2585
+ value: "",
2586
+ children: t("noModels")
2587
+ }) : null, models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2588
+ value: model.id,
2589
+ children: model.id
2590
+ }, model.id))]
2591
+ })]
2592
+ })] }) : null
2593
+ ]
2594
+ }),
2595
+ plan.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2596
+ className: "pstack-notice",
2597
+ "data-tone": "error",
2598
+ children: plan.error
2599
+ }),
2600
+ plan.plan !== void 0 && plan.plan.warnings.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2601
+ className: "pstack-notice",
2602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("budgetWarnings") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2603
+ className: "pstack-notice-list",
2604
+ children: plan.plan.warnings.map((warning) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t("budgetWarningAutoPanel", { role: roleLabel(warning.role, t) }) }, `${warning.kind}:${warning.role}`))
2605
+ })]
2606
+ }) : null,
2607
+ visibleRows.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2608
+ className: "pstack-budget-preview",
2609
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h5", {
2610
+ className: "pstack-budget-preview-title",
2611
+ children: t("budgetPreview")
2612
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2613
+ className: "pstack-budget-list",
2614
+ children: visibleRows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
2615
+ className: "pstack-budget-card",
2616
+ "data-status": row.status,
2617
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2618
+ className: "pstack-budget-card-head",
2619
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2620
+ className: "pstack-budget-role",
2621
+ children: roleLabel(row.key, t)
2622
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2623
+ tone: statusTone(row.status),
2624
+ label: statusText(row, t)
2625
+ })]
2626
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2627
+ className: "pstack-budget-route-grid",
2628
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2629
+ className: "pstack-budget-route-block",
2630
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2631
+ className: "pstack-budget-route-label",
2632
+ children: t("budgetCurrentRoute")
2633
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2634
+ className: "pstack-budget-route-chips",
2635
+ children: routeTokens(row.previous, t)
2636
+ })]
2637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2638
+ className: "pstack-budget-route-block",
2639
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2640
+ className: "pstack-budget-route-label",
2641
+ children: t("budgetTargetRoute")
2642
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2643
+ className: "pstack-budget-route-chips",
2644
+ children: routeTokens(row.routes, t)
2645
+ })]
2646
+ })]
2647
+ })]
2648
+ }, row.key))
2649
+ })]
2650
+ }),
2651
+ error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2652
+ className: "pstack-notice",
2653
+ "data-tone": "error",
2654
+ children: error
2655
+ })
2656
+ ]
2657
+ }),
2658
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2659
+ className: "pstack-budget-footer",
2660
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2661
+ className: "pstack-role-save",
2662
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2663
+ variant: "outline",
2664
+ size: "sm",
2665
+ onClick: requestClose,
2666
+ disabled: busy,
2667
+ children: translations.common("close")
2668
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2669
+ variant: "primary",
2670
+ size: "sm",
2671
+ icon: busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 13 }) : void 0,
2672
+ disabled: !canApply,
2673
+ onClick: () => void apply(),
2674
+ children: busy ? t("budgetApplying") : t("budgetApply")
2675
+ })]
2676
+ })]
2677
+ })
2678
+ ]
2679
+ })]
2680
+ }), document.body);
2681
+ }
2682
+ function budgetLabel(raw, t) {
2683
+ const tier = budgetTierFromLabel(raw);
2684
+ if (tier === void 0) return raw ?? t("budgetUnset");
2685
+ switch (tier) {
2686
+ case "unlimited": return t("budgetTierUnlimited");
2687
+ case "large": return t("budgetTierLarge");
2688
+ case "medium": return t("budgetTierMedium");
2689
+ case "small": return t("budgetTierSmall");
2690
+ }
2691
+ }
2692
+ function budgetPlanErrorText(reason, t) {
2693
+ if (reason instanceof BudgetPlanError) switch (reason.code) {
2694
+ case "no-default-model": return t("budgetNoProvider");
2695
+ case "provider-not-found":
2696
+ case "provider-discovery": return t("budgetErrorProvider", { provider: reason.detail ?? "" });
2697
+ case "model-not-found":
2698
+ case "model-discovery": return t("budgetErrorModel", { model: reason.detail ?? "" });
2699
+ }
2700
+ return messageOf(reason);
2701
+ }
2702
+ function routeTokens(routes, t) {
2703
+ return (routes.length === 0 ? [{
2704
+ kind: "inherit",
2705
+ alias: "inherit-parent"
2706
+ }] : routes).map((route, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2707
+ className: "pstack-budget-route-chip",
2708
+ children: route.kind === "inherit" ? route.alias === "auto" ? t("auto") : t("inheritParent") : renderRouteValue([route])
2709
+ }, `${index}-${renderRouteValue([route])}`));
2710
+ }
2711
+ function statusTone(status) {
2712
+ switch (status) {
2713
+ case "changed": return "brand";
2714
+ case "unchanged": return "success";
2715
+ case "fallback":
2716
+ case "unsupported": return "warn";
2717
+ case "needs-choice":
2718
+ case "custom": return "error";
2719
+ case "skipped": return "neutral";
2720
+ }
2721
+ }
2722
+ function statusText(row, t) {
2723
+ switch (row.status) {
2724
+ case "changed": return t("budgetStatusChanged");
2725
+ case "unchanged": return t("budgetStatusUnchanged");
2726
+ case "fallback": return t("budgetStatusFallback", { effort: row.detail?.kind === "fallback" ? row.detail.effort : "" });
2727
+ case "needs-choice": return t("budgetStatusNeedsChoice");
2728
+ case "custom": return row.detail?.kind === "missing-model" ? t("budgetStatusMissingModel", { model: `${row.detail.provider}/${row.detail.model}` }) : t("budgetStatusCustom");
2729
+ case "unsupported": return t("budgetStatusUnsupported");
2730
+ case "skipped": return t("budgetStatusSkipped");
2731
+ }
2732
+ }
2733
+ // src/client/role-draft.ts
2734
+ function inheritedRoutes() {
2735
+ return [{
2736
+ kind: "inherit",
2737
+ alias: "inherit-parent"
2738
+ }];
2739
+ }
2740
+ /** Routes a reset restores: the configured baseline, or the inherited row for an unconfigured role. */
2741
+ function resetRoutes(draft) {
2742
+ return draft.baseline.length === 0 ? inheritedRoutes() : draft.baseline.map((route) => ({ ...route }));
2743
+ }
2744
+ function draftOf(role) {
2745
+ return {
2746
+ routes: role.routes.length === 0 ? inheritedRoutes() : role.routes.map((route) => ({ ...route })),
2747
+ baseline: role.routes,
2748
+ busy: false,
2749
+ cleared: false
2750
+ };
2751
+ }
2752
+ function isDirty(draft, role) {
2753
+ if (draft.cleared) return role.configured;
2754
+ if (!role.configured && signature(draft.routes) === signature(inheritedRoutes())) return false;
2755
+ return signature(draft.routes) !== signature(draft.baseline);
2756
+ }
2757
+ function signature(routes) {
2758
+ return JSON.stringify(routes);
2759
+ }
2760
+ function isClearDraft(draft, role) {
2761
+ return draft.cleared && role !== void 0 && role.configured && draft.routes.length === 1 && draft.routes[0]?.kind === "inherit";
2762
+ }
2763
+ // src/client/RoleCard.tsx
2764
+ function RoleCard(props) {
2765
+ const { role, draft, catalogue, translations, disabled, onRoutesChange, onClear, onSave, onReset } = props;
2766
+ const t = translations.roles;
2767
+ const common = translations.common;
2768
+ const copy = isRoleKey(role.key) ? ROLE_COPY[role.key] : void 0;
2769
+ const name = roleLabel(role.key, t);
2770
+ const description = copy === void 0 ? "" : t(copy.descriptionKey);
2771
+ const dirty = isDirty(draft, role);
2772
+ const controlsDisabled = disabled || draft.busy;
2773
+ const incomplete = draft.routes.some(routeIsIncomplete);
2774
+ const invalid = draft.routes.some((route) => routeIsInvalid(route, catalogue));
2775
+ const routeProblem = incomplete || invalid;
2776
+ const clearing = isClearDraft(draft, role);
2777
+ const canClearRole = !clearing && (role.configured || dirty);
2778
+ const status = role.issues.length > 0 ? {
2779
+ tone: "error",
2780
+ label: t("roleInvalid")
2781
+ } : routeProblem ? {
2782
+ tone: "error",
2783
+ label: t("roleRouteInvalid")
2784
+ } : role.configured ? {
2785
+ tone: "success",
2786
+ label: t("roleConfigured")
2787
+ } : {
2788
+ tone: "neutral",
2789
+ label: t("roleMissing")
2790
+ };
2791
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2792
+ className: "pstack-role-card",
2793
+ "data-dirty": dirty ? "true" : void 0,
2794
+ children: [
2795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
2796
+ className: "pstack-role-head",
2797
+ children: [
2798
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2799
+ className: "pstack-role-glyph",
2800
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRoute, { size: 16 })
2801
+ }),
2802
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2803
+ className: "pstack-role-title",
2804
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2805
+ className: "pstack-role-name-row",
2806
+ children: [
2807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
2808
+ className: "pstack-role-name",
2809
+ children: name
2810
+ }),
2811
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2812
+ className: "pstack-code-chip",
2813
+ children: role.key
2814
+ }),
2815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2816
+ className: "pstack-tag",
2817
+ title: role.shape === "single" ? t("singleRouteHint") : void 0,
2818
+ children: role.shape === "single" ? t("singleRoute") : t("listRoute")
2819
+ })
2820
+ ]
2821
+ }), description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2822
+ className: "pstack-role-desc",
2823
+ children: description
2824
+ })]
2825
+ }),
2826
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2827
+ className: "pstack-role-status",
2828
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2829
+ tone: status.tone,
2830
+ label: status.label
2831
+ }), role.line === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2832
+ className: "pstack-role-line",
2833
+ children: t("roleLine", { line: role.line })
2834
+ })]
2835
+ })
2836
+ ]
2837
+ }),
2838
+ role.issues.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2839
+ className: "pstack-notice",
2840
+ "data-tone": "error",
2841
+ style: { margin: 0 },
2842
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
2843
+ className: "pstack-notice-list",
2844
+ children: role.issues.map((issue, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: issue }, index))
2845
+ })]
2846
+ }),
2847
+ routeProblem ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2848
+ className: "pstack-notice",
2849
+ "data-tone": "error",
2850
+ style: { margin: 0 },
2851
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: incomplete ? t("routeIncompleteHint") : t("routeInvalidHint") })]
2852
+ }) : null,
2853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2854
+ className: "pstack-route-list",
2855
+ children: [draft.routes.map((route, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteRow, {
2856
+ roleName: name,
2857
+ route,
2858
+ catalogue,
2859
+ disabled: controlsDisabled,
2860
+ canRemove: role.shape === "list" && draft.routes.length > 1,
2861
+ onChange: (next) => onRoutesChange(draft.routes.map((item, position) => position === index ? next : item)),
2862
+ onRemove: () => onRoutesChange(draft.routes.filter((_item, position) => position !== index)),
2863
+ translations
2864
+ }, `${role.key}-${index}`)), clearing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2865
+ className: "pstack-role-desc",
2866
+ children: t("clearHint")
2867
+ }) : null]
2868
+ }),
2869
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
2870
+ className: "pstack-role-foot",
2871
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2872
+ className: "pstack-role-foot-actions",
2873
+ children: role.shape === "list" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2874
+ variant: "outline",
2875
+ size: "sm",
2876
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconPlus, { size: 13 }),
2877
+ disabled: controlsDisabled,
2878
+ onClick: () => onRoutesChange([...draft.routes, {
2879
+ kind: "inherit",
2880
+ alias: "inherit-parent"
2881
+ }]),
2882
+ children: t("addRoute")
2883
+ }) : null
2884
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2885
+ className: "pstack-role-save",
2886
+ children: [
2887
+ draft.message === void 0 ? dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
2888
+ tone: "warn",
2889
+ label: t("unsavedChanges")
2890
+ }) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2891
+ className: "pstack-inline-message",
2892
+ "data-tone": draft.message.tone,
2893
+ children: [draft.message.tone === "success" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheck, { size: 12 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 12 }), draft.message.text]
2894
+ }),
2895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2896
+ variant: "outline",
2897
+ size: "sm",
2898
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEraser, { size: 13 }),
2899
+ disabled: controlsDisabled || !canClearRole,
2900
+ onClick: onClear,
2901
+ children: t("clearRole")
2902
+ }),
2903
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2904
+ variant: "outline",
2905
+ size: "sm",
2906
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconUndo, { size: 13 }),
2907
+ disabled: controlsDisabled || !dirty,
2908
+ onClick: onReset,
2909
+ children: t("resetRole")
2910
+ }),
2911
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
2912
+ variant: "primary",
2913
+ size: "sm",
2914
+ icon: draft.busy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSave, { size: 13 }),
2915
+ disabled: controlsDisabled || !dirty || incomplete || invalid,
2916
+ onClick: onSave,
2917
+ children: draft.busy ? common("saving") : common("save")
2918
+ })
2919
+ ]
2920
+ })]
2921
+ })
2922
+ ]
2923
+ });
2924
+ }
2925
+ function RouteRow(props) {
2926
+ const { roleName, route, catalogue, disabled, canRemove, onChange, onRemove, translations } = props;
2927
+ const t = translations.roles;
2928
+ const providerValue = route.kind === "inherit" ? `__inherit__:${route.alias}` : route.provider;
2929
+ const providers = catalogue?.providers ?? [];
2930
+ const models = route.kind === "route" ? modelChoices(catalogue, route.provider) : [];
2931
+ const efforts = route.kind === "route" ? effortChoices(catalogue, route.provider, route.model) : [];
2932
+ const effortOptions = route.kind === "route" && route.effort !== void 0 && !efforts.includes(route.effort) ? [route.effort, ...efforts] : efforts;
2933
+ const fields = route.kind === "route" ? "minmax(0, 1.25fr) minmax(0, 1.25fr) minmax(0, 0.7fr)" : "minmax(0, 1fr)";
2934
+ const columns = canRemove ? `${fields} 32px` : fields;
2935
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2936
+ className: "pstack-route-row",
2937
+ style: { gridTemplateColumns: columns },
2938
+ children: [
2939
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2940
+ className: "pstack-field",
2941
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2942
+ className: "pstack-field-label",
2943
+ children: t("provider")
2944
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2945
+ className: "pstack-select",
2946
+ "aria-label": `${roleName} ${t("provider")}`,
2947
+ value: providerValue,
2948
+ disabled,
2949
+ onChange: (event) => {
2950
+ const value = event.target.value;
2951
+ if (value === "__inherit__:inherit-parent" || value === "__inherit__:auto") {
2952
+ onChange({
2953
+ kind: "inherit",
2954
+ alias: value.endsWith("auto") ? "auto" : "inherit-parent"
2955
+ });
2956
+ return;
2957
+ }
2958
+ const model = modelChoices(catalogue, value).find((entry) => entry.error === void 0);
2959
+ onChange({
2960
+ kind: "route",
2961
+ provider: value,
2962
+ model: model?.id ?? "",
2963
+ ...model?.defaultEffort === void 0 ? {} : { effort: model.defaultEffort }
2964
+ });
2965
+ },
2966
+ children: [
2967
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2968
+ value: "__inherit__:inherit-parent",
2969
+ children: t("inheritParent")
2970
+ }),
2971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2972
+ value: "__inherit__:auto",
2973
+ children: t("auto")
2974
+ }),
2975
+ route.kind === "route" && route.provider !== "" && providers.every((provider) => provider.provider !== route.provider) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2976
+ value: route.provider,
2977
+ children: route.provider
2978
+ }) : null,
2979
+ providers.map((provider) => {
2980
+ const issue = providerIssue(provider);
2981
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
2982
+ value: provider.provider,
2983
+ disabled: issue !== void 0,
2984
+ title: provider.error,
2985
+ children: [providerLabel(providers, provider.provider), issue === void 0 ? null : ` (${t(issue === "unavailable" ? "providerUnavailable" : "noModels")})`]
2986
+ }, provider.provider);
2987
+ })
2988
+ ]
2989
+ })]
2990
+ }),
2991
+ route.kind === "route" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2992
+ className: "pstack-field",
2993
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2994
+ className: "pstack-field-label",
2995
+ children: t("model")
2996
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2997
+ className: "pstack-select",
2998
+ "aria-label": `${roleName} ${t("model")}`,
2999
+ value: route.model,
3000
+ disabled,
3001
+ onChange: (event) => {
3002
+ const model = models.find((entry) => entry.id === event.target.value);
3003
+ onChange({
3004
+ kind: "route",
3005
+ provider: route.provider,
3006
+ model: event.target.value,
3007
+ ...model?.defaultEffort === void 0 ? {} : { effort: model.defaultEffort }
3008
+ });
3009
+ },
3010
+ children: [
3011
+ models.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3012
+ value: "",
3013
+ disabled: true,
3014
+ children: t("noModels")
3015
+ }) : null,
3016
+ route.kind === "route" && route.model !== "" && models.every((model) => model.id !== route.model) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3017
+ value: route.model,
3018
+ children: route.model
3019
+ }) : null,
3020
+ models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3021
+ value: model.id,
3022
+ disabled: model.error !== void 0,
3023
+ children: model.id
3024
+ }, model.id))
3025
+ ]
3026
+ })]
3027
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
3028
+ className: "pstack-field",
3029
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3030
+ className: "pstack-field-label",
3031
+ children: t("effort")
3032
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
3033
+ className: "pstack-select",
3034
+ "aria-label": `${roleName} ${t("effort")}`,
3035
+ value: route.effort ?? "",
3036
+ disabled,
3037
+ onChange: (event) => {
3038
+ const effort = event.target.value;
3039
+ onChange({
3040
+ kind: "route",
3041
+ provider: route.provider,
3042
+ model: route.model,
3043
+ ...effort === "" ? {} : { effort }
3044
+ });
3045
+ },
3046
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3047
+ value: "",
3048
+ children: t("effortUnset")
3049
+ }), effortOptions.map((effort) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3050
+ value: effort,
3051
+ children: effort
3052
+ }, effort))]
3053
+ })]
3054
+ })] }) : null,
3055
+ canRemove ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3056
+ className: "pstack-route-actions",
3057
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
3058
+ danger: true,
3059
+ label: `${t("removeRoute")} ${roleName}`,
3060
+ disabled,
3061
+ onClick: onRemove,
3062
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrash, { size: 14 })
3063
+ })
3064
+ }) : null
3065
+ ]
3066
+ });
3067
+ }
3068
+ // src/client/RoleSettingsPage.tsx
3069
+ /** Role model configuration page for the pstack plugin dialog. */
3070
+ ensurePstackStyles();
3071
+ function RoleSettingsView(props) {
3072
+ const { api, translations, onDirtyChange } = props;
3073
+ const t = translations.roles;
3074
+ const common = translations.common;
3075
+ const [snapshot, setSnapshot] = (0, react.useState)();
3076
+ const [catalogue, setCatalogue] = (0, react.useState)();
3077
+ const [drafts, setDrafts] = (0, react.useState)({});
3078
+ const [loading, setLoading] = (0, react.useState)(true);
3079
+ const [error, setError] = (0, react.useState)();
3080
+ const [refreshMessage, setRefreshMessage] = (0, react.useState)();
3081
+ const [bulkBusy, setBulkBusy] = (0, react.useState)();
3082
+ const [bulkMessage, setBulkMessage] = (0, react.useState)();
3083
+ const [budgetOpen, setBudgetOpen] = (0, react.useState)(false);
3084
+ const abortRef = (0, react.useRef)(void 0);
3085
+ (0, react.useEffect)(() => () => abortRef.current?.abort(), []);
3086
+ const applySnapshot = (next) => {
3087
+ setSnapshot(next);
3088
+ setDrafts(Object.fromEntries(next.roles.map((role) => [role.key, draftOf(role)])));
3089
+ };
3090
+ const load = async () => {
3091
+ abortRef.current?.abort();
3092
+ const controller = new AbortController();
3093
+ abortRef.current = controller;
3094
+ setLoading(true);
3095
+ setError(void 0);
3096
+ setRefreshMessage(void 0);
3097
+ setBulkMessage(void 0);
3098
+ try {
3099
+ const [nextSnapshot, nextCatalogue] = await Promise.all([api.status(), api.catalogue(controller.signal)]);
3100
+ applySnapshot(nextSnapshot);
3101
+ setCatalogue(nextCatalogue);
3102
+ } catch (reason) {
3103
+ if (controller.signal.aborted) return;
3104
+ const message = t("rolesLoadFailed", { message: messageOf(reason) });
3105
+ if (snapshot === void 0) setError(message);
3106
+ else setRefreshMessage({
3107
+ tone: "error",
3108
+ text: message
3109
+ });
3110
+ } finally {
3111
+ setLoading(false);
3112
+ }
3113
+ };
3114
+ (0, react.useEffect)(() => {
3115
+ load();
3116
+ }, [api, t]);
3117
+ const dirtyFlag = snapshot !== void 0 && snapshot.roles.some((role) => isDirty(drafts[role.key] ?? draftOf(role), role));
3118
+ (0, react.useEffect)(() => {
3119
+ onDirtyChange?.(dirtyFlag);
3120
+ }, [dirtyFlag, onDirtyChange]);
3121
+ if (loading && snapshot === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3122
+ className: "pstack-loading",
3123
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("rolesLoading") })]
3124
+ });
3125
+ if (snapshot === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3126
+ className: "pstack-notice",
3127
+ "data-tone": "error",
3128
+ children: [
3129
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }),
3130
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error }),
3131
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3132
+ variant: "outline",
3133
+ size: "sm",
3134
+ onClick: () => void load(),
3135
+ children: common("retry")
3136
+ })
3137
+ ]
3138
+ });
3139
+ const draftFor = (role) => drafts[role.key] ?? draftOf(role);
3140
+ const dirtyKeys = snapshot.roles.filter((role) => isDirty(draftFor(role), role)).map((role) => role.key);
3141
+ const dirtyCount = dirtyKeys.length;
3142
+ const anyBusy = Object.values(drafts).some((draft) => draft.busy);
3143
+ const anyIncomplete = dirtyKeys.some((key) => (drafts[key]?.routes ?? []).some(routeIsIncomplete));
3144
+ const anyInvalid = dirtyKeys.some((key) => (drafts[key]?.routes ?? []).some((route) => routeIsInvalid(route, catalogue)));
3145
+ const canClearAll = snapshot.roles.some((role) => {
3146
+ const draft = drafts[role.key] ?? draftOf(role);
3147
+ return !draft.cleared && (role.configured || isDirty(draft, role));
3148
+ });
3149
+ const fileStatus = statusPresentation(snapshot, t);
3150
+ const updateRoutes = (key, routes) => {
3151
+ setBulkMessage(void 0);
3152
+ setDrafts((current) => {
3153
+ const draft = current[key];
3154
+ if (draft === void 0) return current;
3155
+ return {
3156
+ ...current,
3157
+ [key]: {
3158
+ ...draft,
3159
+ routes,
3160
+ cleared: false,
3161
+ message: void 0
3162
+ }
3163
+ };
3164
+ });
3165
+ };
3166
+ const clearRole = (key) => {
3167
+ setBulkMessage(void 0);
3168
+ setDrafts((current) => {
3169
+ const draft = current[key];
3170
+ if (draft === void 0) return current;
3171
+ const role = snapshot.roles.find((entry) => entry.key === key);
3172
+ return {
3173
+ ...current,
3174
+ [key]: {
3175
+ ...draft,
3176
+ routes: inheritedRoutes(),
3177
+ cleared: role?.configured ?? false,
3178
+ message: void 0,
3179
+ busy: false
3180
+ }
3181
+ };
3182
+ });
3183
+ };
3184
+ const resetRole = (key) => {
3185
+ setBulkMessage(void 0);
3186
+ setDrafts((current) => {
3187
+ const draft = current[key];
3188
+ if (draft === void 0) return current;
3189
+ return {
3190
+ ...current,
3191
+ [key]: {
3192
+ ...draft,
3193
+ routes: resetRoutes(draft),
3194
+ cleared: false,
3195
+ message: void 0,
3196
+ busy: false
3197
+ }
3198
+ };
3199
+ });
3200
+ };
3201
+ const resetAll = () => {
3202
+ setDrafts((current) => Object.fromEntries(snapshot.roles.map((role) => {
3203
+ const draft = current[role.key] ?? draftOf(role);
3204
+ return [role.key, {
3205
+ ...draft,
3206
+ routes: resetRoutes(draft),
3207
+ cleared: false,
3208
+ message: void 0,
3209
+ busy: false
3210
+ }];
3211
+ })));
3212
+ setBulkMessage(void 0);
3213
+ };
3214
+ const clearAll = () => {
3215
+ setBulkMessage(void 0);
3216
+ setDrafts((current) => Object.fromEntries(snapshot.roles.map((role) => {
3217
+ const draft = current[role.key] ?? draftOf(role);
3218
+ return [role.key, {
3219
+ ...draft,
3220
+ routes: inheritedRoutes(),
3221
+ cleared: role.configured,
3222
+ message: void 0,
3223
+ busy: false
3224
+ }];
3225
+ })));
3226
+ };
3227
+ const saveRole = async (key) => {
3228
+ const draft = drafts[key];
3229
+ if (draft === void 0) return true;
3230
+ setDrafts((current) => {
3231
+ const currentDraft = current[key];
3232
+ if (currentDraft === void 0) return current;
3233
+ return {
3234
+ ...current,
3235
+ [key]: {
3236
+ ...currentDraft,
3237
+ busy: true,
3238
+ message: void 0
3239
+ }
3240
+ };
3241
+ });
3242
+ try {
3243
+ const result = await api.save({
3244
+ key,
3245
+ routes: isClearDraft(draft, snapshot.roles.find((role) => role.key === key)) ? [] : draft.routes
3246
+ }, abortRef.current?.signal);
3247
+ if (result.ok) {
3248
+ const savedRole = result.snapshot.roles.find((entry) => entry.key === key);
3249
+ setSnapshot(result.snapshot);
3250
+ setDrafts((current) => {
3251
+ const currentDraft = current[key];
3252
+ if (currentDraft === void 0) return current;
3253
+ return {
3254
+ ...current,
3255
+ [key]: {
3256
+ ...currentDraft,
3257
+ routes: draft.routes,
3258
+ baseline: savedRole?.routes ?? draft.routes,
3259
+ cleared: false,
3260
+ busy: false,
3261
+ message: {
3262
+ tone: "success",
3263
+ text: common("saved")
3264
+ }
3265
+ }
3266
+ };
3267
+ });
3268
+ return true;
3269
+ }
3270
+ setDrafts((current) => {
3271
+ const currentDraft = current[key];
3272
+ if (currentDraft === void 0) return current;
3273
+ return {
3274
+ ...current,
3275
+ [key]: {
3276
+ ...currentDraft,
3277
+ busy: false,
3278
+ message: {
3279
+ tone: "error",
3280
+ text: t("saveRejected", { reason: result.reason })
3281
+ }
3282
+ }
3283
+ };
3284
+ });
3285
+ return false;
3286
+ } catch (reason) {
3287
+ setDrafts((current) => {
3288
+ const currentDraft = current[key];
3289
+ if (currentDraft === void 0) return current;
3290
+ return {
3291
+ ...current,
3292
+ [key]: {
3293
+ ...currentDraft,
3294
+ busy: false,
3295
+ message: {
3296
+ tone: "error",
3297
+ text: t("saveRejected", { reason: messageOf(reason) })
3298
+ }
3299
+ }
3300
+ };
3301
+ });
3302
+ return false;
3303
+ }
3304
+ };
3305
+ const saveAll = async () => {
3306
+ if (dirtyCount === 0 || bulkBusy !== void 0 || anyBusy || anyIncomplete || anyInvalid) return;
3307
+ setBulkBusy("save");
3308
+ setBulkMessage(void 0);
3309
+ let saved = 0;
3310
+ let failed = 0;
3311
+ for (const key of dirtyKeys) if (await saveRole(key)) saved += 1;
3312
+ else failed += 1;
3313
+ setBulkBusy(void 0);
3314
+ setBulkMessage(failed === 0 ? {
3315
+ tone: "success",
3316
+ text: t("saveAllDone", { count: saved })
3317
+ } : {
3318
+ tone: "error",
3319
+ text: t("saveAllPartial", {
3320
+ count: saved,
3321
+ failed
3322
+ })
3323
+ });
3324
+ };
3325
+ const openBudget = () => {
3326
+ if (dirtyFlag && !confirmDiscard(t("confirmDiscardChanges"))) return;
3327
+ if (dirtyFlag) resetAll();
3328
+ setBudgetOpen(true);
3329
+ };
3330
+ const onBudgetApplied = (next) => {
3331
+ applySnapshot(next);
3332
+ setBudgetOpen(false);
3333
+ setBulkMessage({
3334
+ tone: "success",
3335
+ text: t("budgetDone", { budget: budgetLabel(next.budget, t) })
3336
+ });
3337
+ };
3338
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { children: [
3339
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
3340
+ className: "pstack-section-head",
3341
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3342
+ className: "pstack-section-desc",
3343
+ children: t("rolesSubtitle")
3344
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3345
+ className: "pstack-file-meta",
3346
+ children: [
3347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Status, {
3348
+ tone: fileStatus.tone,
3349
+ label: fileStatus.label
3350
+ }),
3351
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("file") }),
3352
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
3353
+ className: "pstack-file-path",
3354
+ title: snapshot.path,
3355
+ children: snapshot.path
3356
+ })
3357
+ ]
3358
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3359
+ className: "pstack-section-actions",
3360
+ children: [
3361
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3362
+ variant: "outline",
3363
+ size: "sm",
3364
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEraser, { size: 13 }),
3365
+ disabled: !canClearAll || !snapshot.canSave || bulkBusy !== void 0 || anyBusy || loading,
3366
+ onClick: clearAll,
3367
+ children: t("clearAll")
3368
+ }),
3369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3370
+ variant: "outline",
3371
+ size: "sm",
3372
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconUndo, { size: 13 }),
3373
+ disabled: dirtyCount === 0 || bulkBusy !== void 0 || anyBusy || loading,
3374
+ onClick: resetAll,
3375
+ children: t("resetAll")
3376
+ }),
3377
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3378
+ variant: "primary",
3379
+ size: "sm",
3380
+ icon: bulkBusy === "save" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSave, { size: 13 }),
3381
+ disabled: dirtyCount === 0 || bulkBusy !== void 0 || anyBusy || anyIncomplete || anyInvalid || loading || !snapshot.canSave,
3382
+ onClick: () => void saveAll(),
3383
+ children: bulkBusy === "save" ? t("savingAll") : dirtyCount > 0 ? t("saveAllCount", { count: dirtyCount }) : t("saveAll")
3384
+ }),
3385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
3386
+ label: common("refresh"),
3387
+ disabled: loading || bulkBusy !== void 0 || anyBusy,
3388
+ onClick: () => {
3389
+ if (dirtyFlag && !confirmDiscard(t("confirmDiscardChanges"))) return;
3390
+ load();
3391
+ },
3392
+ children: loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconRefresh, { size: 14 })
3393
+ })
3394
+ ]
3395
+ })]
3396
+ }),
3397
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3398
+ className: "pstack-budget-bar",
3399
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3400
+ className: "pstack-budget-status",
3401
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3402
+ className: "pstack-field-label",
3403
+ children: t("budgetTitle")
3404
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("budgetCurrent", { budget: budgetLabel(snapshot.budget, t) }) })]
3405
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3406
+ variant: "outline",
3407
+ size: "sm",
3408
+ id: "pstack-budget-trigger",
3409
+ disabled: !snapshot.canSave || catalogue === void 0 || bulkBusy !== void 0 || anyBusy || loading,
3410
+ onClick: openBudget,
3411
+ children: t("budgetAdjust")
3412
+ })]
3413
+ }),
3414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3415
+ className: "pstack-effort-hint",
3416
+ role: "note",
3417
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconInfo, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("effortBaselineHint") })]
3418
+ }),
3419
+ budgetOpen && catalogue !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BudgetDialog, {
3420
+ snapshot,
3421
+ catalogue,
3422
+ translations,
3423
+ onApply: (request, signal) => api.budget(request, signal),
3424
+ onApplied: onBudgetApplied,
3425
+ onClose: () => setBudgetOpen(false),
3426
+ signal: abortRef.current?.signal
3427
+ }) : null,
3428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toast, { message: refreshMessage }),
3429
+ snapshot.reason === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3430
+ className: "pstack-notice",
3431
+ "data-tone": "error",
3432
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("unreadable", { reason: snapshot.reason }) })]
3433
+ }),
3434
+ snapshot.status === "missing" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3435
+ className: "pstack-notice",
3436
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconInfo, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("fileMissingHint") })]
3437
+ }) : null,
3438
+ snapshot.issues.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3439
+ className: "pstack-notice",
3440
+ "data-tone": "error",
3441
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconAlert, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
3442
+ className: "pstack-notice-list",
3443
+ children: snapshot.issues.map((issue, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: issue.line === void 0 ? issue.message : t("issueAtLine", {
3444
+ line: issue.line,
3445
+ message: issue.message
3446
+ }) }, `${issue.line ?? "x"}-${index}`))
3447
+ })]
3448
+ }),
3449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Toast, { message: bulkMessage }),
3450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3451
+ className: "pstack-role-list",
3452
+ children: snapshot.roles.map((role) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleCard, {
3453
+ role,
3454
+ draft: draftFor(role),
3455
+ catalogue,
3456
+ translations,
3457
+ disabled: !snapshot.canSave || bulkBusy !== void 0 || anyBusy || loading,
3458
+ onRoutesChange: (routes) => updateRoutes(role.key, routes),
3459
+ onClear: () => clearRole(role.key),
3460
+ onSave: () => void saveRole(role.key),
3461
+ onReset: () => resetRole(role.key)
3462
+ }, role.key))
3463
+ })
3464
+ ] });
3465
+ }
3466
+ function statusPresentation(snapshot, t) {
3467
+ if (snapshot.status === "unreadable") return {
3468
+ label: t("statusUnreadable"),
3469
+ tone: "error"
3470
+ };
3471
+ if (snapshot.status === "missing") return {
3472
+ label: t("statusMissing"),
3473
+ tone: "warn"
3474
+ };
3475
+ return {
3476
+ label: t("statusLoaded"),
3477
+ tone: "success"
3478
+ };
3479
+ }
3480
+ // src/client/PstackConfigDialog.tsx
3481
+ /** Plugin-detail action and configuration dialog for dsh-pstack. */
3482
+ ensurePstackStyles();
3483
+ function PstackConfigDialog(props) {
3484
+ const [tab, setTab] = (0, react.useState)("plugins");
3485
+ const [visited, setVisited] = (0, react.useState)(() => /* @__PURE__ */ new Set(["plugins"]));
3486
+ const [rolesDirty, setRolesDirty] = (0, react.useState)(false);
3487
+ const titleId = (0, react.useId)();
3488
+ const tabsId = (0, react.useId)();
3489
+ const panelRef = (0, react.useRef)(null);
3490
+ const { roleApi, pluginApi, translations, onClose } = props;
3491
+ const dirtyRef = (0, react.useRef)(false);
3492
+ (0, react.useEffect)(() => {
3493
+ dirtyRef.current = rolesDirty;
3494
+ }, [rolesDirty]);
3495
+ const onCloseRef = (0, react.useRef)(onClose);
3496
+ (0, react.useEffect)(() => {
3497
+ onCloseRef.current = onClose;
3498
+ }, [onClose]);
3499
+ const requestClose = (0, react.useCallback)(() => {
3500
+ if (dirtyRef.current && !confirmDiscard(translations.roles("confirmDiscardChanges"))) return;
3501
+ onCloseRef.current();
3502
+ }, [translations]);
3503
+ const selectTab = (next) => {
3504
+ setTab(next);
3505
+ setVisited((current) => current.has(next) ? current : /* @__PURE__ */ new Set([...current, next]));
3506
+ };
3507
+ useFocusTrap(panelRef, requestClose, true, {
3508
+ suspended: () => document.querySelector(".pstack-budget-overlay") !== null,
3509
+ lockScroll: true,
3510
+ skipHidden: true
3511
+ });
3512
+ if (typeof document === "undefined") return null;
3513
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3514
+ className: "pstack-overlay",
3515
+ role: "presentation",
3516
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3517
+ className: "pstack-mask",
3518
+ "aria-hidden": "true",
3519
+ onClick: requestClose
3520
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3521
+ className: "pstack-panel",
3522
+ role: "dialog",
3523
+ "aria-modal": "true",
3524
+ "aria-labelledby": titleId,
3525
+ tabIndex: -1,
3526
+ ref: panelRef,
3527
+ children: [
3528
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
3529
+ className: "pstack-header",
3530
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
3531
+ className: "pstack-title",
3532
+ id: titleId,
3533
+ children: translations.config("dialogTitle")
3534
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconButton, {
3535
+ label: translations.common("close"),
3536
+ className: "pstack-icon-btn-plain",
3537
+ onClick: requestClose,
3538
+ autoFocus: true,
3539
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconClose, { size: 14 })
3540
+ })]
3541
+ }),
3542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3543
+ className: "pstack-tabs-row",
3544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SegmentedTabs, {
3545
+ label: translations.config("tabsLabel"),
3546
+ value: tab,
3547
+ onChange: selectTab,
3548
+ items: [{
3549
+ value: "plugins",
3550
+ label: translations.config("tabPlugins"),
3551
+ id: `${tabsId}-plugins`,
3552
+ panelId: `${tabsId}-plugins-panel`
3553
+ }, {
3554
+ value: "roles",
3555
+ label: translations.config("tabRoles"),
3556
+ id: `${tabsId}-roles`,
3557
+ panelId: visited.has("roles") ? `${tabsId}-roles-panel` : void 0
3558
+ }]
3559
+ })
3560
+ }),
3561
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
3562
+ className: "pstack-body",
3563
+ children: [visited.has("plugins") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3564
+ hidden: tab !== "plugins",
3565
+ id: `${tabsId}-plugins-panel`,
3566
+ role: "tabpanel",
3567
+ "aria-labelledby": `${tabsId}-plugins`,
3568
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecommendedPluginsView, {
3569
+ api: pluginApi,
3570
+ translations
3571
+ })
3572
+ }) : null, visited.has("roles") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3573
+ hidden: tab !== "roles",
3574
+ id: `${tabsId}-roles-panel`,
3575
+ role: "tabpanel",
3576
+ "aria-labelledby": `${tabsId}-roles`,
3577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleSettingsView, {
3578
+ api: roleApi,
3579
+ translations,
3580
+ onDirtyChange: setRolesDirty
3581
+ })
3582
+ }) : null]
3583
+ })
3584
+ ]
3585
+ })]
3586
+ }), document.body);
3587
+ }
3588
+ /** Create the action component registered into `plugins.detail.actions`. */
3589
+ function createPstackConfigAction(roleApi, pluginApi, translations) {
3590
+ return function PstackConfigAction(props) {
3591
+ const [open, setOpen] = (0, react.useState)(false);
3592
+ const triggerRef = (0, react.useRef)(null);
3593
+ const wasOpen = (0, react.useRef)(false);
3594
+ (0, react.useEffect)(() => {
3595
+ if (open) return;
3596
+ if (!wasOpen.current) return;
3597
+ wasOpen.current = false;
3598
+ triggerRef.current?.focus();
3599
+ }, [open]);
3600
+ if (props.subject.kind !== "bundle" || props.subject.pkg.name !== "dsh-pstack") return null;
3601
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3602
+ variant: "outline",
3603
+ size: "sm",
3604
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconSettings, { size: 13 }),
3605
+ onClick: (event) => {
3606
+ triggerRef.current = event.currentTarget;
3607
+ wasOpen.current = true;
3608
+ setOpen(true);
3609
+ },
3610
+ children: translations.config("configureAction")
3611
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PstackConfigDialog, {
3612
+ roleApi,
3613
+ pluginApi,
3614
+ translations,
3615
+ onClose: () => setOpen(false)
3616
+ }) : null] });
3617
+ };
3618
+ }
3619
+ const common = {
3620
+ en: {
3621
+ close: "Close",
3622
+ refresh: "Refresh",
3623
+ retry: "Retry",
3624
+ save: "Save",
3625
+ saving: "Saving…",
3626
+ saved: "Saved"
3627
+ },
3628
+ zh: {
3629
+ close: "关闭",
3630
+ refresh: "刷新",
3631
+ retry: "重试",
3632
+ save: "保存",
3633
+ saving: "保存中…",
3634
+ saved: "已保存"
3635
+ }
3636
+ };
3637
+ const config = {
3638
+ en: {
3639
+ configureAction: "Configure",
3640
+ dialogTitle: "Configure Pstack",
3641
+ tabsLabel: "Configuration sections",
3642
+ tabPlugins: "Recommended plugins",
3643
+ tabRoles: "Role models"
3644
+ },
3645
+ zh: {
3646
+ configureAction: "配置",
3647
+ dialogTitle: "配置 Pstack",
3648
+ tabsLabel: "配置分区",
3649
+ tabPlugins: "推荐插件",
3650
+ tabRoles: "角色模型"
3651
+ }
3652
+ };
3653
+ const plugins = {
3654
+ en: {
3655
+ pluginsSubtitle: "Optional plugins related to the pstack port review. Missing or incomplete packages cannot be enabled here.",
3656
+ pluginsSummary: "Recommended {enabled}/{total}",
3657
+ groupRecommended: "Recommended",
3658
+ groupRecommendedDescription: "Work directly with the pstack workflow and should usually be enabled.",
3659
+ groupNormal: "Optional",
3660
+ groupNormalDescription: "Useful for specific workflows. Enable only when the workflow needs it.",
3661
+ groupConflict: "Conflicts",
3662
+ groupConflictDescription: "Conflicts with the pstack root preset or pstack_subagent.",
3663
+ loadFailed: "Could not read plugin status: {message}",
3664
+ managerUnavailable: "Plugin manager unavailable",
3665
+ pluginEnabled: "Enabled",
3666
+ pluginDisabled: "Disabled",
3667
+ pluginMissing: "Not installed",
3668
+ pluginUnavailable: "Unavailable",
3669
+ pluginReadOnly: "Read-only",
3670
+ pluginConflict: "Conflict",
3671
+ enablePlugin: "Enable",
3672
+ disablePlugin: "Disable",
3673
+ enableAll: "Enable all",
3674
+ enableAllCount: "Enable all ({count})",
3675
+ enableAllComplete: "All enabled",
3676
+ enableAllBusy: "Enabling…",
3677
+ enableAllDone: "Enabled {count} recommended plugins",
3678
+ enableAllRestart: "{count} recommended plugins require a restart",
3679
+ enableAllPartial: "Changed {settled} recommended plugins, {restarts} require a restart, {unsettled} not applied",
3680
+ pluginChangeFailed: "Could not change {name}: {reason}",
3681
+ pluginChangeApplied: "Applied change to {name}",
3682
+ pluginChangeRestart: "{name} takes effect after restart",
3683
+ pluginChangeRestartReason: "{name} takes effect after restart ({reason})",
3684
+ pluginChangeOverridden: "Change to {name} was overridden",
3685
+ pluginChangeCancelled: "Change to {name} was cancelled",
3686
+ pluginChangeUnknownReason: "no further detail",
3687
+ recommendedForgeTitle: "Change requests & worktrees",
3688
+ recommendedForgeDescription: "Track GitHub PRs, checks, reviews, and local worktrees beside the conversation.",
3689
+ recommendedSessionSearchTitle: "Session search",
3690
+ recommendedSessionSearchDescription: "Search earlier sessions and inspect their events.",
3691
+ recommendedBrowserTitle: "Browser verification",
3692
+ recommendedBrowserDescription: "Drive a browser to verify pages. Requires a provider and an image-capable model.",
3693
+ recommendedCuaTitle: "Desktop verification",
3694
+ recommendedCuaDescription: "Control the desktop in macOS PTC sessions. Windows support is incomplete.",
3695
+ recommendedTerminalTitle: "Persistent terminals",
3696
+ recommendedTerminalDescription: "Keep PTY terminals alive for CLI and TUI verification.",
3697
+ recommendedMcpTitle: "MCP integrations",
3698
+ recommendedMcpDescription: "Connect configured MCP servers. No server is configured by default.",
3699
+ recommendedRalphTitle: "Ralph loop",
3700
+ recommendedRalphDescription: "Run a foreground fresh-agent loop with structured handoff.",
3701
+ recommendedAgentTeamTitle: "Agent Teams",
3702
+ recommendedAgentTeamDescription: "Conflicts with the pstack root preset and pstack_subagent. Enabling it makes the pstack root preset fail at startup."
3703
+ },
3704
+ zh: {
3705
+ pluginsSubtitle: "与本插件移植审查相关的可选插件。未安装或依赖不全的插件无法在这里启用。",
3706
+ pluginsSummary: "推荐已启用 {enabled}/{total}",
3707
+ groupRecommended: "推荐",
3708
+ groupRecommendedDescription: "与 pstack 工作流直接配合,通常应启用。",
3709
+ groupNormal: "可选",
3710
+ groupNormalDescription: "用于特定工作流,按需启用。",
3711
+ groupConflict: "冲突",
3712
+ groupConflictDescription: "与 pstack 根预设或 pstack_subagent 冲突。",
3713
+ loadFailed: "无法读取插件状态:{message}",
3714
+ managerUnavailable: "插件管理器不可用",
3715
+ pluginEnabled: "已启用",
3716
+ pluginDisabled: "未启用",
3717
+ pluginMissing: "未安装",
3718
+ pluginUnavailable: "不可用",
3719
+ pluginReadOnly: "只读",
3720
+ pluginConflict: "冲突",
3721
+ enablePlugin: "启用",
3722
+ disablePlugin: "停用",
3723
+ enableAll: "全部启用",
3724
+ enableAllCount: "全部启用({count})",
3725
+ enableAllComplete: "已全部启用",
3726
+ enableAllBusy: "正在启用…",
3727
+ enableAllDone: "已启用 {count} 个推荐插件",
3728
+ enableAllRestart: "{count} 个推荐插件需要重启后生效",
3729
+ enableAllPartial: "已修改 {settled} 个推荐插件,{restarts} 个需要重启,{unsettled} 个未生效",
3730
+ pluginChangeFailed: "无法修改 {name}:{reason}",
3731
+ pluginChangeApplied: "已应用对 {name} 的修改",
3732
+ pluginChangeRestart: "{name} 将在重启后生效",
3733
+ pluginChangeRestartReason: "{name} 将在重启后生效({reason})",
3734
+ pluginChangeOverridden: "对 {name} 的修改已被覆盖",
3735
+ pluginChangeCancelled: "对 {name} 的修改已取消",
3736
+ pluginChangeUnknownReason: "无更多信息",
3737
+ recommendedForgeTitle: "变更请求与工作树",
3738
+ recommendedForgeDescription: "在会话侧边栏跟踪 GitHub PR、检查、评审和本地工作树。",
3739
+ recommendedSessionSearchTitle: "会话搜索",
3740
+ recommendedSessionSearchDescription: "搜索历史会话并检查其中的事件。",
3741
+ recommendedBrowserTitle: "浏览器验证",
3742
+ recommendedBrowserDescription: "驱动浏览器验证页面;需要 provider 和支持图像的模型。",
3743
+ recommendedCuaTitle: "桌面验证",
3744
+ recommendedCuaDescription: "在 macOS PTC Session 中控制桌面;Windows 支持不完整。",
3745
+ recommendedTerminalTitle: "持久终端",
3746
+ recommendedTerminalDescription: "保留 PTY 终端,用于 CLI 和 TUI 验证。",
3747
+ recommendedMcpTitle: "MCP 集成",
3748
+ recommendedMcpDescription: "连接已配置的 MCP server;默认不配置任何 server。",
3749
+ recommendedRalphTitle: "Ralph 循环",
3750
+ recommendedRalphDescription: "运行带结构化交接的前台 fresh-agent 循环。",
3751
+ recommendedAgentTeamTitle: "智能体团队",
3752
+ recommendedAgentTeamDescription: "与 pstack 根预设和 pstack_subagent 冲突。启用后 pstack 根预设会启动失败。"
3753
+ }
3754
+ };
3755
+ const roles = {
3756
+ en: {
3757
+ rolesSubtitle: "Choose each role's provider, model, and reasoning effort. Only supported efforts are listed.",
3758
+ effortBaselineHint: "Reasoning effort is a baseline. It may move up or down at dispatch to fit the task.",
3759
+ file: "File",
3760
+ statusMissing: "missing",
3761
+ statusLoaded: "loaded",
3762
+ statusUnreadable: "unreadable",
3763
+ fileMissingHint: "The role file does not exist yet. Saving a role creates it.",
3764
+ issueAtLine: "Line {line}: {message}",
3765
+ rolesLoading: "Loading role configuration…",
3766
+ rolesLoadFailed: "Could not read the role configuration: {message}",
3767
+ saveRejected: "Save rejected: {reason}",
3768
+ saveAll: "Save all",
3769
+ saveAllCount: "Save all ({count})",
3770
+ savingAll: "Saving…",
3771
+ saveAllDone: "Saved {count} roles",
3772
+ saveAllPartial: "Saved {count} roles, {failed} failed",
3773
+ resetRole: "Reset",
3774
+ resetAll: "Reset all",
3775
+ clearAll: "Clear all",
3776
+ unsavedChanges: "Unsaved changes",
3777
+ clearHint: "Saving removes this role's line.",
3778
+ confirmDiscardChanges: "Discard unsaved role changes?",
3779
+ inheritParent: "Inherit parent",
3780
+ auto: "Auto",
3781
+ provider: "Provider",
3782
+ model: "Model",
3783
+ effort: "Reasoning effort",
3784
+ effortUnset: "Not set",
3785
+ noModels: "No models",
3786
+ providerUnavailable: "Unavailable",
3787
+ addRoute: "Add route",
3788
+ removeRoute: "Remove route",
3789
+ clearRole: "Clear",
3790
+ roleConfigured: "Configured",
3791
+ roleInvalid: "Invalid configuration",
3792
+ roleRouteInvalid: "Route needs attention",
3793
+ routeIncompleteHint: "Fill in both the provider and the model before saving.",
3794
+ routeInvalidHint: "This route points at a provider, model, or effort that is not available.",
3795
+ roleMissing: "Not configured",
3796
+ roleLine: "Line {line}",
3797
+ singleRoute: "Single route",
3798
+ listRoute: "Route list",
3799
+ singleRouteHint: "A single-route role keeps at most one route. An empty list removes its line.",
3800
+ unreadable: "This file cannot be edited until it becomes readable: {reason}",
3801
+ roleFeatureName: "Feature & refactoring",
3802
+ roleFeatureDescription: "Implement a new feature or restructure existing code.",
3803
+ roleBugFixName: "Bug fix",
3804
+ roleBugFixDescription: "Reproduce, locate, and fix a reported defect.",
3805
+ rolePerfName: "Performance issue",
3806
+ rolePerfDescription: "Trace and fix a one-off performance regression.",
3807
+ roleHillclimbName: "Hillclimb",
3808
+ roleHillclimbDescription: "Run sustained experiments against a single metric target.",
3809
+ roleJudgmentName: "Judgment & prose",
3810
+ roleJudgmentDescription: "Tasks that need product judgment or polished writing.",
3811
+ roleHardestName: "Hardest tasks",
3812
+ roleHardestDescription: "Cross-cutting design, concurrency, or subtle algorithms.",
3813
+ roleHowExplorerName: "how explorer",
3814
+ roleHowExplorerDescription: "Explore the codebase for architecture and runtime questions.",
3815
+ roleHowExplainerName: "how explainer",
3816
+ roleHowExplainerDescription: "Turn exploration into a readable architecture explanation.",
3817
+ roleWhyInvestigatorsName: "why investigators",
3818
+ roleWhyInvestigatorsDescription: "Collect decision history and motivation in parallel.",
3819
+ roleWhySynthesizerName: "why synthesizer",
3820
+ roleWhySynthesizerDescription: "Combine evidence into one cited conclusion.",
3821
+ roleReflectToolingName: "reflect tooling",
3822
+ roleReflectToolingDescription: "Review tooling and process during a reflect pass.",
3823
+ roleReflectJudgmentName: "reflect judgment & synthesis",
3824
+ roleReflectJudgmentDescription: "Review the judgment, divergent, and synthesis steps.",
3825
+ roleArenaRunnersName: "arena runners",
3826
+ roleArenaRunnersDescription: "Produce candidate artifacts for cross-judging.",
3827
+ roleArenaJudgesName: "arena cross-judge pool",
3828
+ roleArenaJudgesDescription: "Cross-model judges for arena candidates.",
3829
+ roleSwarmName: "swarm workers",
3830
+ roleSwarmDescription: "Large parallel coverage, races, and exploration.",
3831
+ roleArchitectName: "architect runners",
3832
+ roleArchitectDescription: "Draft types and module structure in parallel.",
3833
+ roleInterrogateName: "interrogate reviewers",
3834
+ roleInterrogateDescription: "Adversarial review that keeps only reproducible findings.",
3835
+ budgetTitle: "Reasoning budget",
3836
+ budgetCurrent: "Last applied: {budget}",
3837
+ budgetUnset: "Not set",
3838
+ budgetAdjust: "Adjust budget",
3839
+ budgetScope: "Apply to",
3840
+ budgetScopeConcrete: "Existing concrete routes only",
3841
+ budgetScopeUnconfigured: "Fill unconfigured roles with a default model",
3842
+ budgetTier: "Budget tier",
3843
+ budgetTierUnlimited: "Unlimited (max)",
3844
+ budgetTierLarge: "Large (xhigh)",
3845
+ budgetTierMedium: "Medium (high)",
3846
+ budgetTierSmall: "Small (medium)",
3847
+ budgetProvider: "Provider",
3848
+ budgetModel: "Model",
3849
+ budgetNoProvider: "No provider with a usable model",
3850
+ budgetPreview: "Preview",
3851
+ budgetCurrentRoute: "Current",
3852
+ budgetTargetRoute: "Target",
3853
+ budgetStatusChanged: "Will change",
3854
+ budgetStatusUnchanged: "Already matches",
3855
+ budgetStatusFallback: "Fallback: {effort}",
3856
+ budgetStatusNeedsChoice: "Needs manual choice",
3857
+ budgetStatusCustom: "Custom effort",
3858
+ budgetStatusMissingModel: "Model unavailable: {model}",
3859
+ budgetStatusUnsupported: "No reasoning effort",
3860
+ budgetStatusSkipped: "Unchanged",
3861
+ budgetApply: "Apply budget",
3862
+ budgetApplying: "Applying…",
3863
+ budgetFailed: "Budget apply failed: {reason}",
3864
+ budgetDone: "Applied {budget} budget",
3865
+ budgetNoModel: "Select a model",
3866
+ budgetWarnings: "Warnings",
3867
+ budgetWarningAutoPanel: "Role \"{role}\" is an automatic panel; the default model fills only one entry, so it is not a cross-model panel.",
3868
+ budgetErrorProvider: "Provider {provider} is unavailable",
3869
+ budgetErrorModel: "Model {model} is unavailable"
3870
+ },
3871
+ zh: {
3872
+ rolesSubtitle: "为每个角色选择提供方、模型和推理强度;只列出模型支持的值。",
3873
+ effortBaselineHint: "推理强度为基准档位,实际派发时会根据任务需要上下浮动调整。",
3874
+ file: "文件",
3875
+ statusMissing: "缺失",
3876
+ statusLoaded: "已加载",
3877
+ statusUnreadable: "不可读",
3878
+ fileMissingHint: "角色文件尚不存在。保存任意角色后会创建该文件。",
3879
+ issueAtLine: "第 {line} 行:{message}",
3880
+ rolesLoading: "正在读取角色配置…",
3881
+ rolesLoadFailed: "无法读取角色配置:{message}",
3882
+ saveRejected: "保存被拒绝:{reason}",
3883
+ saveAll: "全部保存",
3884
+ saveAllCount: "全部保存({count})",
3885
+ savingAll: "正在保存…",
3886
+ saveAllDone: "已保存 {count} 个角色",
3887
+ saveAllPartial: "已保存 {count} 个角色,{failed} 个失败",
3888
+ resetRole: "重置",
3889
+ resetAll: "全部重置",
3890
+ clearAll: "全部清除",
3891
+ unsavedChanges: "未保存",
3892
+ clearHint: "保存后将删除该角色的行。",
3893
+ confirmDiscardChanges: "放弃未保存的角色修改?",
3894
+ inheritParent: "继承父级",
3895
+ auto: "自动",
3896
+ provider: "提供方",
3897
+ model: "模型",
3898
+ effort: "推理强度",
3899
+ effortUnset: "未指定",
3900
+ noModels: "没有模型",
3901
+ providerUnavailable: "不可用",
3902
+ addRoute: "添加路由",
3903
+ removeRoute: "删除路由",
3904
+ clearRole: "清除",
3905
+ roleConfigured: "已配置",
3906
+ roleInvalid: "配置有误",
3907
+ roleRouteInvalid: "路由需要处理",
3908
+ routeIncompleteHint: "请先补齐提供方与模型后再保存。",
3909
+ routeInvalidHint: "该路由指向当前不可用的提供方、模型或推理档位。",
3910
+ roleMissing: "未配置",
3911
+ roleLine: "第 {line} 行",
3912
+ singleRoute: "单路由",
3913
+ listRoute: "路由列表",
3914
+ singleRouteHint: "单路由角色最多保留一条路由,空列表会删除角色行。",
3915
+ unreadable: "文件可读之前无法编辑:{reason}",
3916
+ roleFeatureName: "功能开发与重构",
3917
+ roleFeatureDescription: "实现新功能,或重构既有代码结构。",
3918
+ roleBugFixName: "缺陷修复",
3919
+ roleBugFixDescription: "复现、定位并修复用户报告的缺陷。",
3920
+ rolePerfName: "性能问题",
3921
+ rolePerfDescription: "追踪并修复一次性的性能退化。",
3922
+ roleHillclimbName: "指标爬坡",
3923
+ roleHillclimbDescription: "围绕单一指标目标持续实验和优化。",
3924
+ roleJudgmentName: "判断与文案",
3925
+ roleJudgmentDescription: "需要产品判断力或高质量文案的任务。",
3926
+ roleHardestName: "最艰难任务",
3927
+ roleHardestDescription: "跨模块设计、并发或算法等最难的任务。",
3928
+ roleHowExplorerName: "how 探索",
3929
+ roleHowExplorerDescription: "探索代码库,回答架构与运行机制问题。",
3930
+ roleHowExplainerName: "how 讲解",
3931
+ roleHowExplainerDescription: "把探索结果整理成可理解的架构说明。",
3932
+ roleWhyInvestigatorsName: "why 调查",
3933
+ roleWhyInvestigatorsDescription: "并行搜集决策历史与动机证据。",
3934
+ roleWhySynthesizerName: "why 综合",
3935
+ roleWhySynthesizerDescription: "把多路证据汇成一份带引用的结论。",
3936
+ roleReflectToolingName: "reflect 工具",
3937
+ roleReflectToolingDescription: "复盘时审查工具与流程问题。",
3938
+ roleReflectJudgmentName: "reflect 判断与综合",
3939
+ roleReflectJudgmentDescription: "复盘判断、发散与综合环节。",
3940
+ roleArenaRunnersName: "arena 参赛",
3941
+ roleArenaRunnersDescription: "并行产出候选方案,供交叉评审挑选。",
3942
+ roleArenaJudgesName: "arena 交叉裁判池",
3943
+ roleArenaJudgesDescription: "为候选方案提供跨模型评审。",
3944
+ roleSwarmName: "swarm 工作者",
3945
+ roleSwarmDescription: "大规模并行执行覆盖、竞速或勘察任务。",
3946
+ roleArchitectName: "architect 设计",
3947
+ roleArchitectDescription: "并行给出类型与模块结构草案。",
3948
+ roleInterrogateName: "interrogate 评审",
3949
+ roleInterrogateDescription: "以对抗姿态审查改动,只保留可复现的问题。",
3950
+ budgetTitle: "推理预算",
3951
+ budgetCurrent: "上次应用:{budget}",
3952
+ budgetUnset: "未设置",
3953
+ budgetAdjust: "调整预算",
3954
+ budgetScope: "应用到",
3955
+ budgetScopeConcrete: "仅已有具体路由",
3956
+ budgetScopeUnconfigured: "为未配置角色填入默认模型",
3957
+ budgetTier: "预算档位",
3958
+ budgetTierUnlimited: "无限制(max)",
3959
+ budgetTierLarge: "大(xhigh)",
3960
+ budgetTierMedium: "中(high)",
3961
+ budgetTierSmall: "小(medium)",
3962
+ budgetProvider: "提供方",
3963
+ budgetModel: "模型",
3964
+ budgetNoProvider: "没有带可用模型的提供方",
3965
+ budgetPreview: "预演",
3966
+ budgetCurrentRoute: "当前",
3967
+ budgetTargetRoute: "目标",
3968
+ budgetStatusChanged: "将调整",
3969
+ budgetStatusUnchanged: "已符合",
3970
+ budgetStatusFallback: "回退到 {effort}",
3971
+ budgetStatusNeedsChoice: "需要手动选择",
3972
+ budgetStatusCustom: "自定义 effort",
3973
+ budgetStatusMissingModel: "模型不可用:{model}",
3974
+ budgetStatusUnsupported: "无推理档位",
3975
+ budgetStatusSkipped: "保持不变",
3976
+ budgetApply: "应用预算",
3977
+ budgetApplying: "正在应用…",
3978
+ budgetFailed: "应用预算失败:{reason}",
3979
+ budgetDone: "已应用 {budget} 预算",
3980
+ budgetNoModel: "请选择模型",
3981
+ budgetWarnings: "警告",
3982
+ budgetWarningAutoPanel: "角色「{role}」是自动面板;默认模型只填入一个条目,它不是跨模型面板。",
3983
+ budgetErrorProvider: "提供方 {provider} 不可用",
3984
+ budgetErrorModel: "模型 {model} 不可用"
3985
+ }
3986
+ };
3987
+ // src/client/locales/namespaces.ts
3988
+ /** Locale namespace ids for the pstack client surfaces. */
3989
+ const NS = {
3990
+ common: "pstack.common",
3991
+ config: "pstack.config",
3992
+ plugins: "pstack.plugins",
3993
+ roles: "pstack.roles"
3994
+ };
3995
+ // src/client/locales/index.ts
3996
+ /** Locale namespaces and catalogs for pstack client surfaces. */
3997
+ const catalogs = {
3998
+ common,
3999
+ config,
4000
+ plugins,
4001
+ roles
4002
+ };
4003
+ // src/client/index.ts
4004
+ /** Browser services this plugin needs. `remote.pluginManager` stays optional, so roles load without it. */
4005
+ const inject = [
4006
+ "remote",
4007
+ "slots",
4008
+ "locale"
4009
+ ];
4010
+ /**
4011
+ * Build a stable API whose methods resolve the manager at call time.
4012
+ *
4013
+ * The plugin-manager Remote namespace can arrive before or after this plugin's
4014
+ * `apply`, so it must not be captured once here. A missing manager fails only the
4015
+ * recommendation tab when it is used, and a later arrival recovers on refresh.
4016
+ */
4017
+ function createPluginManagerApi(getPluginManager) {
4018
+ const manager = () => {
4019
+ const value = getPluginManager();
4020
+ if (value === void 0) throw new Error("plugin manager is not available yet");
4021
+ return value;
4022
+ };
4023
+ return {
4024
+ listBundles: async () => unwrap(await manager().listBundles()),
4025
+ setBundleEnabled: async (name, enabled) => unwrap(await manager().setBundleEnabled(name, enabled))
4026
+ };
4027
+ }
4028
+ async function apply(ctx) {
4029
+ const disposeRemote = await ctx.remote.$mount(pstackRolesRemote);
4030
+ ctx.effect(() => ctx.locale.register(NS.common, catalogs.common), "pstack-config.locale.common");
4031
+ ctx.effect(() => ctx.locale.register(NS.config, catalogs.config), "pstack-config.locale.config");
4032
+ ctx.effect(() => ctx.locale.register(NS.plugins, catalogs.plugins), "pstack-config.locale.plugins");
4033
+ ctx.effect(() => ctx.locale.register(NS.roles, catalogs.roles), "pstack-config.locale.roles");
4034
+ let pluginManager;
4035
+ ctx.inject(["remote.pluginManager"], (scope) => {
4036
+ pluginManager = scope.remote.pluginManager;
4037
+ });
4038
+ const pluginApi = createPluginManagerApi(() => pluginManager);
4039
+ ctx.inject(["remote.pstackRoles"], (scope) => {
4040
+ const pstack = scope.remote.pstackRoles;
4041
+ if (pstack === void 0) return;
4042
+ const roleApi = {
4043
+ status: async () => unwrap(await pstack.status()),
4044
+ catalogue: async (signal) => unwrap(await pstack.catalogue(signal)),
4045
+ save: async (request, signal) => unwrap(await pstack.save(request, signal)),
4046
+ budget: async (request, signal) => unwrap(await pstack.budget(request, signal))
4047
+ };
4048
+ const translations = {
4049
+ common: ctx.locale.bind(NS.common),
4050
+ config: ctx.locale.bind(NS.config),
4051
+ plugins: ctx.locale.bind(NS.plugins),
4052
+ roles: ctx.locale.bind(NS.roles)
4053
+ };
4054
+ scope.slots.inject("plugins.detail.actions", () => scope.slots.register({
4055
+ name: "plugins.detail.actions",
4056
+ id: "pstack",
4057
+ order: 20,
4058
+ label: translations.config("configureAction")
4059
+ }, createPstackConfigAction(roleApi, pluginApi, translations)));
4060
+ });
4061
+ return disposeRemote;
4062
+ }
4063
+ function unwrap(result) {
4064
+ if (!result.ok) throw result.error;
4065
+ return result.value;
4066
+ }
4067
+ exports.apply = apply;
4068
+ exports.createPluginManagerApi = createPluginManagerApi;
4069
+ exports.inject = inject;
4070
+ return module.exports;
4071
+ }
4072
+ });