@zerwiz/ymir 0.1.33 → 0.1.35

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 (930) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/apps/README.md +4 -0
  3. package/apps/hlidskjalf/README.md +68 -0
  4. package/apps/hlidskjalf/android/app/build.gradle +73 -0
  5. package/apps/hlidskjalf/android/app/capacitor.build.gradle +19 -0
  6. package/apps/hlidskjalf/android/app/proguard-rules.pro +21 -0
  7. package/apps/hlidskjalf/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  8. package/apps/hlidskjalf/android/app/src/main/AndroidManifest.xml +41 -0
  9. package/apps/hlidskjalf/android/app/src/main/java/org/zerwiz/ymir/MainActivity.java +5 -0
  10. package/apps/hlidskjalf/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  11. package/apps/hlidskjalf/android/app/src/main/res/drawable/splash.png +0 -0
  12. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  13. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  14. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  15. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  16. package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  17. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  18. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  19. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  20. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  21. package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  22. package/apps/hlidskjalf/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  23. package/apps/hlidskjalf/android/app/src/main/res/layout/activity_main.xml +12 -0
  24. package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  25. package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
  26. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  27. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  28. package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  29. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  30. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  31. package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  32. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  33. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  34. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  35. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  36. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  37. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  38. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  39. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  40. package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  41. package/apps/hlidskjalf/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  42. package/apps/hlidskjalf/android/app/src/main/res/values/strings.xml +7 -0
  43. package/apps/hlidskjalf/android/app/src/main/res/values/styles.xml +22 -0
  44. package/apps/hlidskjalf/android/app/src/main/res/xml/file_paths.xml +5 -0
  45. package/apps/hlidskjalf/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  46. package/apps/hlidskjalf/android/build.gradle +29 -0
  47. package/apps/hlidskjalf/android/capacitor.settings.gradle +3 -0
  48. package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  49. package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  50. package/apps/hlidskjalf/android/gradle.properties +22 -0
  51. package/apps/hlidskjalf/android/gradlew +248 -0
  52. package/apps/hlidskjalf/android/gradlew.bat +92 -0
  53. package/apps/hlidskjalf/android/settings.gradle +5 -0
  54. package/apps/hlidskjalf/android/variables.gradle +16 -0
  55. package/apps/hlidskjalf/capacitor.config.ts +26 -0
  56. package/apps/hlidskjalf/dist/apple-touch-icon.png +0 -0
  57. package/apps/hlidskjalf/dist/assets/index-BWmNIyaG.css +1 -0
  58. package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js +88 -0
  59. package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js.map +1 -0
  60. package/apps/hlidskjalf/dist/icon.svg +7 -0
  61. package/apps/hlidskjalf/dist/index.html +64 -0
  62. package/apps/hlidskjalf/dist/manifest.webmanifest +31 -0
  63. package/apps/hlidskjalf/dist/og.jpg +0 -0
  64. package/apps/hlidskjalf/dist/og.png +0 -0
  65. package/apps/hlidskjalf/dist/og.svg +64 -0
  66. package/apps/hlidskjalf/dist/sw.js +39 -0
  67. package/apps/hlidskjalf/dist/ymir-icon.png +0 -0
  68. package/apps/hlidskjalf/dist/ymir-mark.svg +62 -0
  69. package/apps/hlidskjalf/electron/icon.png +0 -0
  70. package/apps/hlidskjalf/electron/main.cjs +236 -0
  71. package/apps/hlidskjalf/electron/preload.cjs +8 -0
  72. package/apps/hlidskjalf/electron/smidja-icon.png +0 -0
  73. package/apps/hlidskjalf/electron/ymir-hlidskjalf.desktop.in +12 -0
  74. package/apps/hlidskjalf/electron/ymir-smidja.desktop.in +12 -0
  75. package/apps/hlidskjalf/index.html +63 -0
  76. package/apps/hlidskjalf/package-lock.json +5398 -0
  77. package/apps/hlidskjalf/package.json +55 -0
  78. package/apps/hlidskjalf/public/apple-touch-icon.png +0 -0
  79. package/apps/hlidskjalf/public/icon.svg +7 -0
  80. package/apps/hlidskjalf/public/manifest.webmanifest +31 -0
  81. package/apps/hlidskjalf/public/og.jpg +0 -0
  82. package/apps/hlidskjalf/public/og.png +0 -0
  83. package/apps/hlidskjalf/public/og.svg +64 -0
  84. package/apps/hlidskjalf/public/sw.js +39 -0
  85. package/apps/hlidskjalf/public/ymir-icon.png +0 -0
  86. package/apps/hlidskjalf/public/ymir-mark.svg +62 -0
  87. package/apps/hlidskjalf/server/accounts.ts +159 -0
  88. package/apps/hlidskjalf/server/index.ts +2045 -0
  89. package/apps/hlidskjalf/src/app/App.tsx +116 -0
  90. package/apps/hlidskjalf/src/app/BottomStream.tsx +128 -0
  91. package/apps/hlidskjalf/src/app/MobileNav.tsx +87 -0
  92. package/apps/hlidskjalf/src/app/Rail.tsx +106 -0
  93. package/apps/hlidskjalf/src/app/Shell.tsx +135 -0
  94. package/apps/hlidskjalf/src/app/Topbar.tsx +130 -0
  95. package/apps/hlidskjalf/src/components/AccentPicker.tsx +207 -0
  96. package/apps/hlidskjalf/src/components/AccountMenu.tsx +115 -0
  97. package/apps/hlidskjalf/src/components/AgentCard.tsx +75 -0
  98. package/apps/hlidskjalf/src/components/EmberBackground.tsx +36 -0
  99. package/apps/hlidskjalf/src/components/Emblem.tsx +48 -0
  100. package/apps/hlidskjalf/src/components/Halls.tsx +155 -0
  101. package/apps/hlidskjalf/src/components/LoginModal.tsx +329 -0
  102. package/apps/hlidskjalf/src/components/Markdown.tsx +10 -0
  103. package/apps/hlidskjalf/src/components/MetricTile.tsx +40 -0
  104. package/apps/hlidskjalf/src/components/ModelPicker.tsx +100 -0
  105. package/apps/hlidskjalf/src/components/Overlay.tsx +134 -0
  106. package/apps/hlidskjalf/src/components/PRCard.tsx +154 -0
  107. package/apps/hlidskjalf/src/components/RecallPanel.tsx +116 -0
  108. package/apps/hlidskjalf/src/components/RuneTag.tsx +23 -0
  109. package/apps/hlidskjalf/src/components/Status.tsx +35 -0
  110. package/apps/hlidskjalf/src/components/TaskChip.tsx +23 -0
  111. package/apps/hlidskjalf/src/components/TraceRow.tsx +28 -0
  112. package/apps/hlidskjalf/src/data/lore.ts +127 -0
  113. package/apps/hlidskjalf/src/data/metadata.ts +147 -0
  114. package/apps/hlidskjalf/src/data/mythology.ts +95 -0
  115. package/apps/hlidskjalf/src/data/realms.ts +167 -0
  116. package/apps/hlidskjalf/src/gates/Cron.tsx +77 -0
  117. package/apps/hlidskjalf/src/gates/Decisions.tsx +67 -0
  118. package/apps/hlidskjalf/src/gates/Files.tsx +138 -0
  119. package/apps/hlidskjalf/src/gates/Fleet.tsx +184 -0
  120. package/apps/hlidskjalf/src/gates/Forge.tsx +486 -0
  121. package/apps/hlidskjalf/src/gates/OmniChat.tsx +217 -0
  122. package/apps/hlidskjalf/src/gates/Processes.tsx +162 -0
  123. package/apps/hlidskjalf/src/gates/Profile.tsx +225 -0
  124. package/apps/hlidskjalf/src/gates/Reviews.tsx +33 -0
  125. package/apps/hlidskjalf/src/gates/Runes.tsx +116 -0
  126. package/apps/hlidskjalf/src/gates/Runtime.tsx +106 -0
  127. package/apps/hlidskjalf/src/gates/Sessions.tsx +109 -0
  128. package/apps/hlidskjalf/src/gates/Stats.tsx +268 -0
  129. package/apps/hlidskjalf/src/gates/Tasks.tsx +173 -0
  130. package/apps/hlidskjalf/src/gates/Trace.tsx +155 -0
  131. package/apps/hlidskjalf/src/gates/Well.tsx +154 -0
  132. package/apps/hlidskjalf/src/gates/Worktrees.tsx +48 -0
  133. package/apps/hlidskjalf/src/hooks/useTenantDef.ts +13 -0
  134. package/apps/hlidskjalf/src/main.tsx +31 -0
  135. package/apps/hlidskjalf/src/services/api.ts +404 -0
  136. package/apps/hlidskjalf/src/services/auth.ts +107 -0
  137. package/apps/hlidskjalf/src/services/stream.ts +35 -0
  138. package/apps/hlidskjalf/src/state/store.ts +648 -0
  139. package/apps/hlidskjalf/src/state/ui.ts +62 -0
  140. package/apps/hlidskjalf/src/styles/auth.css +485 -0
  141. package/apps/hlidskjalf/src/styles/components.css +1259 -0
  142. package/apps/hlidskjalf/src/styles/forge.css +115 -0
  143. package/apps/hlidskjalf/src/styles/global.css +167 -0
  144. package/apps/hlidskjalf/src/styles/menus.css +175 -0
  145. package/apps/hlidskjalf/src/styles/mobile.css +298 -0
  146. package/apps/hlidskjalf/src/styles/overlays.css +205 -0
  147. package/apps/hlidskjalf/src/styles/shell.css +641 -0
  148. package/apps/hlidskjalf/src/types.ts +251 -0
  149. package/apps/hlidskjalf/src/vite-env.d.ts +9 -0
  150. package/apps/hlidskjalf/static/landing.md +6 -0
  151. package/apps/hlidskjalf/tsconfig.json +25 -0
  152. package/apps/hlidskjalf/vite.config.ts +49 -0
  153. package/apps/hlidskjalf-mobile/App.tsx +93 -0
  154. package/apps/hlidskjalf-mobile/app.json +13 -0
  155. package/apps/hlidskjalf-mobile/index.ts +4 -0
  156. package/apps/hlidskjalf-mobile/package.json +35 -0
  157. package/apps/hlidskjalf-mobile/public/icon.svg +7 -0
  158. package/apps/hlidskjalf-mobile/tsconfig.json +4 -0
  159. package/apps/odrerir/.astro/content.d.ts +159 -0
  160. package/apps/odrerir/.astro/dev.json +24 -0
  161. package/apps/odrerir/.astro/settings.json +5 -0
  162. package/apps/odrerir/.astro/types.d.ts +2 -0
  163. package/apps/odrerir/astro.config.mjs +79 -0
  164. package/apps/odrerir/dist/apple-touch-icon.png +0 -0
  165. package/apps/odrerir/dist/favicon-192.png +0 -0
  166. package/apps/odrerir/dist/favicon-32.png +0 -0
  167. package/apps/odrerir/dist/favicon-512.png +0 -0
  168. package/apps/odrerir/dist/favicon.svg +53 -0
  169. package/apps/odrerir/dist/icon.svg +7 -0
  170. package/apps/odrerir/dist/index.html +837 -0
  171. package/apps/odrerir/dist/og-square.png +0 -0
  172. package/apps/odrerir/dist/og-story.png +0 -0
  173. package/apps/odrerir/dist/og.png +0 -0
  174. package/apps/odrerir/dist/safari-pinned-tab.svg +7 -0
  175. package/apps/odrerir/dist/site.webmanifest +16 -0
  176. package/apps/odrerir/dist/styles/cloth.css +112 -0
  177. package/apps/odrerir/dist/styles/livehall.css +278 -0
  178. package/apps/odrerir/electron/icon.png +0 -0
  179. package/apps/odrerir/electron/main.cjs +183 -0
  180. package/apps/odrerir/electron/ymir-odrerir.desktop.in +12 -0
  181. package/apps/odrerir/package-lock.json +4195 -0
  182. package/apps/odrerir/package.json +30 -0
  183. package/apps/odrerir/public/apple-touch-icon.png +0 -0
  184. package/apps/odrerir/public/favicon-192.png +0 -0
  185. package/apps/odrerir/public/favicon-32.png +0 -0
  186. package/apps/odrerir/public/favicon-512.png +0 -0
  187. package/apps/odrerir/public/favicon.svg +53 -0
  188. package/apps/odrerir/public/icon.svg +7 -0
  189. package/apps/odrerir/public/og-square.png +0 -0
  190. package/apps/odrerir/public/og-story.png +0 -0
  191. package/apps/odrerir/public/og.png +0 -0
  192. package/apps/odrerir/public/safari-pinned-tab.svg +7 -0
  193. package/apps/odrerir/public/site.webmanifest +16 -0
  194. package/apps/odrerir/scripts/extract-cloth.mjs +101 -0
  195. package/apps/odrerir/src/index.html +837 -0
  196. package/apps/odrerir/src/styles/cloth.css +112 -0
  197. package/apps/odrerir/src/styles/livehall.css +278 -0
  198. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/bug_report.yml +99 -0
  199. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/config.yml +8 -0
  200. package/apps/sessrumnir/.github/ISSUE_TEMPLATE/feature_request.yml +46 -0
  201. package/apps/sessrumnir/.github/PULL_REQUEST_TEMPLATE.md +56 -0
  202. package/apps/sessrumnir/.github/SUPPORT.md +18 -0
  203. package/apps/sessrumnir/.github/workflows/build.yml +133 -0
  204. package/apps/sessrumnir/AGENTS.md +540 -0
  205. package/apps/sessrumnir/CLA.md +63 -0
  206. package/apps/sessrumnir/CONTRIBUTING.md +233 -0
  207. package/apps/sessrumnir/LICENSE +200 -0
  208. package/apps/sessrumnir/NOTICE +33 -0
  209. package/apps/sessrumnir/README.md +329 -0
  210. package/apps/sessrumnir/bin/sessrumnir-web.sh +117 -0
  211. package/apps/sessrumnir/bin/sessrumnir.js +169 -0
  212. package/apps/sessrumnir/docs/CNAME +1 -0
  213. package/apps/sessrumnir/docs/favicon.ico +0 -0
  214. package/apps/sessrumnir/docs/icon-512.png +0 -0
  215. package/apps/sessrumnir/docs/index.html +441 -0
  216. package/apps/sessrumnir/docs/robots.txt +4 -0
  217. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_181929.png +0 -0
  218. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182005.png +0 -0
  219. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182020.png +0 -0
  220. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182039.png +0 -0
  221. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182117.png +0 -0
  222. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182225.png +0 -0
  223. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182248.png +0 -0
  224. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182322.png +0 -0
  225. package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182340.png +0 -0
  226. package/apps/sessrumnir/docs/sitemap.xml +9 -0
  227. package/apps/sessrumnir/electron.vite.config.ts +49 -0
  228. package/apps/sessrumnir/eslint.config.mjs +75 -0
  229. package/apps/sessrumnir/i18next.config.ts +33 -0
  230. package/apps/sessrumnir/install.sh +144 -0
  231. package/apps/sessrumnir/out/main/index.js +10881 -0
  232. package/apps/sessrumnir/out/preload/index.js +722 -0
  233. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-C8QS47vb.woff2 +0 -0
  234. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-D3EsxgFc.woff +0 -0
  235. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-B7dJQtg-.woff +0 -0
  236. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-BLlg2W5x.woff2 +0 -0
  237. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-BcIsDXzC.woff2 +0 -0
  238. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-CBymlcNF.woff +0 -0
  239. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-Bgrpe4p1.woff +0 -0
  240. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-BlcaxZtM.woff2 +0 -0
  241. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-CdQuyvtc.woff +0 -0
  242. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-pZw22qtS.woff2 +0 -0
  243. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-Dr-wLyOm.woff +0 -0
  244. package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-dLR2-a_v.woff2 +0 -0
  245. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-BGH8Vunh.woff2 +0 -0
  246. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-C3_-2Ua-.woff +0 -0
  247. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-Dj3SQ6fR.woff +0 -0
  248. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-EBdSCOD3.woff2 +0 -0
  249. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-B254Xisw.woff2 +0 -0
  250. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-DUW-TI76.woff +0 -0
  251. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuQ88yz3.woff2 +0 -0
  252. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuXFa1Dr.woff +0 -0
  253. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-AH9qog1s.woff2 +0 -0
  254. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-DAuUCO41.woff +0 -0
  255. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-CD-dlDpD.woff +0 -0
  256. package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-D3T6BBXG.woff2 +0 -0
  257. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-C-RiYxEf.woff2 +0 -0
  258. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-DmUuA7Y2.woff +0 -0
  259. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-DsPuwQHi.woff2 +0 -0
  260. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-tGBW_mI7.woff +0 -0
  261. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-Dar_cCT6.woff2 +0 -0
  262. package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-GIs8CF2x.woff +0 -0
  263. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-Ba-HN6uq.woff +0 -0
  264. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-ByB2hQUF.woff2 +0 -0
  265. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  266. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  267. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  268. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  269. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-B8O3uHbr.woff +0 -0
  270. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-BbtBtHoB.woff2 +0 -0
  271. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  272. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  273. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  274. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  275. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BPU54Tw3.woff +0 -0
  276. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BRS-C4KA.woff2 +0 -0
  277. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  278. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  279. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  280. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  281. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-BEtQ2wm2.woff2 +0 -0
  282. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-CQbCaAQ4.woff +0 -0
  283. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  284. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  285. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  286. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  287. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-B6fSpYYV.woff +0 -0
  288. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-CpdbWO_2.woff2 +0 -0
  289. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  290. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  291. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  292. package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  293. package/apps/sessrumnir/out/renderer/assets/index-BxqsRq2x.css +4858 -0
  294. package/apps/sessrumnir/out/renderer/assets/index-TaD69rFk.js +99435 -0
  295. package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  296. package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  297. package/apps/sessrumnir/out/renderer/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  298. package/apps/sessrumnir/out/renderer/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  299. package/apps/sessrumnir/out/renderer/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  300. package/apps/sessrumnir/out/renderer/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  301. package/apps/sessrumnir/out/renderer/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  302. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  303. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  304. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  305. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  306. package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  307. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-italic-DFS986qL.woff2 +0 -0
  308. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
  309. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2 +0 -0
  310. package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
  311. package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-italic-xAqemagA.woff2 +0 -0
  312. package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
  313. package/apps/sessrumnir/out/renderer/assets/openmoji-color-9qo_2DLG.woff2 +0 -0
  314. package/apps/sessrumnir/out/renderer/assets/pi-logo-B6SVnhuS.svg +7 -0
  315. package/apps/sessrumnir/out/renderer/index.html +19 -0
  316. package/apps/sessrumnir/out/renderer/theme-boot.js +30 -0
  317. package/apps/sessrumnir/package-lock.json +10636 -0
  318. package/apps/sessrumnir/package.json +183 -0
  319. package/apps/sessrumnir/public/apple-touch-icon.png +0 -0
  320. package/apps/sessrumnir/public/icon.svg +7 -0
  321. package/apps/sessrumnir/resources/icons/generate_icons.py +38 -0
  322. package/apps/sessrumnir/resources/icons/icon-128.png +0 -0
  323. package/apps/sessrumnir/resources/icons/icon-16.png +0 -0
  324. package/apps/sessrumnir/resources/icons/icon-256.png +0 -0
  325. package/apps/sessrumnir/resources/icons/icon-32.png +0 -0
  326. package/apps/sessrumnir/resources/icons/icon-48.png +0 -0
  327. package/apps/sessrumnir/resources/icons/icon-512.png +0 -0
  328. package/apps/sessrumnir/resources/icons/icon-64.png +0 -0
  329. package/apps/sessrumnir/resources/icons/icon.icns +0 -0
  330. package/apps/sessrumnir/resources/icons/icon.ico +0 -0
  331. package/apps/sessrumnir/resources/icons/icon.png +0 -0
  332. package/apps/sessrumnir/resources/icons/icon.svg +7 -0
  333. package/apps/sessrumnir/resources/linux/after-install.sh +23 -0
  334. package/apps/sessrumnir/resources/linux/after-remove.sh +18 -0
  335. package/apps/sessrumnir/resources/locales/en/translation.json +1735 -0
  336. package/apps/sessrumnir/resources/permission-prompt-text.test.ts +111 -0
  337. package/apps/sessrumnir/resources/permission-prompt-text.ts +84 -0
  338. package/apps/sessrumnir/resources/permission-rules.test.ts +351 -0
  339. package/apps/sessrumnir/resources/permission-rules.ts +293 -0
  340. package/apps/sessrumnir/resources/pi-desktop-permissions.ts +62 -0
  341. package/apps/sessrumnir/resources/ymir-sessrumnir.desktop.in +12 -0
  342. package/apps/sessrumnir/scripts/check-semantic-colors.mjs +57 -0
  343. package/apps/sessrumnir/src/main/activity-stats.test.ts +240 -0
  344. package/apps/sessrumnir/src/main/activity-stats.ts +596 -0
  345. package/apps/sessrumnir/src/main/agent-detection.test.ts +25 -0
  346. package/apps/sessrumnir/src/main/agent-detection.ts +114 -0
  347. package/apps/sessrumnir/src/main/app-data-paths.test.ts +90 -0
  348. package/apps/sessrumnir/src/main/app-data-paths.ts +95 -0
  349. package/apps/sessrumnir/src/main/app-log.test.ts +114 -0
  350. package/apps/sessrumnir/src/main/app-log.ts +210 -0
  351. package/apps/sessrumnir/src/main/archived-sessions.test.ts +50 -0
  352. package/apps/sessrumnir/src/main/archived-sessions.ts +95 -0
  353. package/apps/sessrumnir/src/main/attachment-reader.test.ts +56 -0
  354. package/apps/sessrumnir/src/main/attachment-reader.ts +52 -0
  355. package/apps/sessrumnir/src/main/auto-tag.test.ts +38 -0
  356. package/apps/sessrumnir/src/main/auto-tag.ts +75 -0
  357. package/apps/sessrumnir/src/main/autostart-linux.test.ts +56 -0
  358. package/apps/sessrumnir/src/main/autostart-linux.ts +58 -0
  359. package/apps/sessrumnir/src/main/cmd-escape.test.ts +424 -0
  360. package/apps/sessrumnir/src/main/cmd-escape.ts +186 -0
  361. package/apps/sessrumnir/src/main/council-manager.test.ts +167 -0
  362. package/apps/sessrumnir/src/main/council-manager.ts +265 -0
  363. package/apps/sessrumnir/src/main/diagnostics-report.test.ts +140 -0
  364. package/apps/sessrumnir/src/main/diagnostics-report.ts +91 -0
  365. package/apps/sessrumnir/src/main/diagnostics.ts +111 -0
  366. package/apps/sessrumnir/src/main/editor-guard.test.ts +59 -0
  367. package/apps/sessrumnir/src/main/editor-guard.ts +50 -0
  368. package/apps/sessrumnir/src/main/extension-ui-ipc.test.ts +299 -0
  369. package/apps/sessrumnir/src/main/extension-ui-ipc.ts +117 -0
  370. package/apps/sessrumnir/src/main/file-service.test.ts +279 -0
  371. package/apps/sessrumnir/src/main/file-service.ts +652 -0
  372. package/apps/sessrumnir/src/main/fs-errors.ts +42 -0
  373. package/apps/sessrumnir/src/main/get-messages-trim.test.ts +77 -0
  374. package/apps/sessrumnir/src/main/get-messages-trim.ts +97 -0
  375. package/apps/sessrumnir/src/main/git-conveyor.test.ts +305 -0
  376. package/apps/sessrumnir/src/main/git-conveyor.ts +338 -0
  377. package/apps/sessrumnir/src/main/git-worktree.test.ts +101 -0
  378. package/apps/sessrumnir/src/main/git-worktree.ts +201 -0
  379. package/apps/sessrumnir/src/main/i18n.ts +31 -0
  380. package/apps/sessrumnir/src/main/index.ts +619 -0
  381. package/apps/sessrumnir/src/main/ipc/active-engine.ts +15 -0
  382. package/apps/sessrumnir/src/main/ipc/context.ts +58 -0
  383. package/apps/sessrumnir/src/main/ipc/council-handlers.ts +126 -0
  384. package/apps/sessrumnir/src/main/ipc/diagnostics-handlers.ts +8 -0
  385. package/apps/sessrumnir/src/main/ipc/file-handlers.ts +96 -0
  386. package/apps/sessrumnir/src/main/ipc/git-conveyor-handlers.ts +55 -0
  387. package/apps/sessrumnir/src/main/ipc/model-handlers.ts +33 -0
  388. package/apps/sessrumnir/src/main/ipc/models-config-handlers.ts +80 -0
  389. package/apps/sessrumnir/src/main/ipc/notes-handlers.ts +65 -0
  390. package/apps/sessrumnir/src/main/ipc/package-handlers.ts +330 -0
  391. package/apps/sessrumnir/src/main/ipc/permission-rules-handlers.ts +223 -0
  392. package/apps/sessrumnir/src/main/ipc/pi-handlers.ts +136 -0
  393. package/apps/sessrumnir/src/main/ipc/pi-start-options.ts +150 -0
  394. package/apps/sessrumnir/src/main/ipc/run-pi-cli.ts +53 -0
  395. package/apps/sessrumnir/src/main/ipc/session-handlers.ts +491 -0
  396. package/apps/sessrumnir/src/main/ipc/settings.ts +120 -0
  397. package/apps/sessrumnir/src/main/ipc/skills-mcp-handlers.ts +192 -0
  398. package/apps/sessrumnir/src/main/ipc/system-handlers.ts +131 -0
  399. package/apps/sessrumnir/src/main/ipc/tag-handlers.ts +69 -0
  400. package/apps/sessrumnir/src/main/ipc/terminal-handlers.ts +41 -0
  401. package/apps/sessrumnir/src/main/ipc/theme-handlers.ts +131 -0
  402. package/apps/sessrumnir/src/main/ipc/update-handlers.ts +61 -0
  403. package/apps/sessrumnir/src/main/ipc/validation.ts +45 -0
  404. package/apps/sessrumnir/src/main/ipc/workflow-handlers.ts +108 -0
  405. package/apps/sessrumnir/src/main/ipc/workspace-activity-wiring.ts +179 -0
  406. package/apps/sessrumnir/src/main/ipc/workspace-handlers.ts +133 -0
  407. package/apps/sessrumnir/src/main/ipc-handlers.ts +98 -0
  408. package/apps/sessrumnir/src/main/map-concurrent.test.ts +80 -0
  409. package/apps/sessrumnir/src/main/map-concurrent.ts +30 -0
  410. package/apps/sessrumnir/src/main/models-file.test.ts +114 -0
  411. package/apps/sessrumnir/src/main/models-file.ts +89 -0
  412. package/apps/sessrumnir/src/main/notes-manager.test.ts +64 -0
  413. package/apps/sessrumnir/src/main/notes-manager.ts +183 -0
  414. package/apps/sessrumnir/src/main/notify-decision.test.ts +39 -0
  415. package/apps/sessrumnir/src/main/notify-decision.ts +25 -0
  416. package/apps/sessrumnir/src/main/omp-fork-points.test.ts +119 -0
  417. package/apps/sessrumnir/src/main/omp-fork-points.ts +105 -0
  418. package/apps/sessrumnir/src/main/omp-plugin-list.test.ts +94 -0
  419. package/apps/sessrumnir/src/main/omp-plugin-list.ts +103 -0
  420. package/apps/sessrumnir/src/main/package-catalog.test.ts +157 -0
  421. package/apps/sessrumnir/src/main/package-catalog.ts +166 -0
  422. package/apps/sessrumnir/src/main/package-updates.test.ts +149 -0
  423. package/apps/sessrumnir/src/main/package-updates.ts +185 -0
  424. package/apps/sessrumnir/src/main/path-authorization.test.ts +64 -0
  425. package/apps/sessrumnir/src/main/path-authorization.ts +31 -0
  426. package/apps/sessrumnir/src/main/pi-binary-resolution.test.ts +582 -0
  427. package/apps/sessrumnir/src/main/pi-binary-resolution.ts +649 -0
  428. package/apps/sessrumnir/src/main/pi-event-router.test.ts +388 -0
  429. package/apps/sessrumnir/src/main/pi-event-router.ts +290 -0
  430. package/apps/sessrumnir/src/main/pi-paths.test.ts +134 -0
  431. package/apps/sessrumnir/src/main/pi-paths.ts +74 -0
  432. package/apps/sessrumnir/src/main/pi-rpc-manager.test.ts +467 -0
  433. package/apps/sessrumnir/src/main/pi-rpc-manager.ts +1319 -0
  434. package/apps/sessrumnir/src/main/process-tree.test.ts +94 -0
  435. package/apps/sessrumnir/src/main/renderer-origin.test.ts +33 -0
  436. package/apps/sessrumnir/src/main/renderer-origin.ts +35 -0
  437. package/apps/sessrumnir/src/main/session-lineage-reader.test.ts +317 -0
  438. package/apps/sessrumnir/src/main/session-lineage-reader.ts +111 -0
  439. package/apps/sessrumnir/src/main/session-metadata.test.ts +508 -0
  440. package/apps/sessrumnir/src/main/session-metadata.ts +526 -0
  441. package/apps/sessrumnir/src/main/session-name.test.ts +71 -0
  442. package/apps/sessrumnir/src/main/session-name.ts +69 -0
  443. package/apps/sessrumnir/src/main/session-paths.test.ts +110 -0
  444. package/apps/sessrumnir/src/main/session-paths.ts +88 -0
  445. package/apps/sessrumnir/src/main/session-tags.test.ts +61 -0
  446. package/apps/sessrumnir/src/main/session-tags.ts +253 -0
  447. package/apps/sessrumnir/src/main/session-trash.test.ts +66 -0
  448. package/apps/sessrumnir/src/main/session-trash.ts +65 -0
  449. package/apps/sessrumnir/src/main/settings-logic.ts +66 -0
  450. package/apps/sessrumnir/src/main/skills-discovery.test.ts +149 -0
  451. package/apps/sessrumnir/src/main/skills-discovery.ts +179 -0
  452. package/apps/sessrumnir/src/main/startup-launch.ts +63 -0
  453. package/apps/sessrumnir/src/main/terminal-service.ts +96 -0
  454. package/apps/sessrumnir/src/main/theme-store.test.ts +440 -0
  455. package/apps/sessrumnir/src/main/theme-store.ts +495 -0
  456. package/apps/sessrumnir/src/main/tray-decision.test.ts +46 -0
  457. package/apps/sessrumnir/src/main/tray-decision.ts +49 -0
  458. package/apps/sessrumnir/src/main/tray-manager.ts +220 -0
  459. package/apps/sessrumnir/src/main/tsconfig.json +20 -0
  460. package/apps/sessrumnir/src/main/window-background.test.ts +29 -0
  461. package/apps/sessrumnir/src/main/window-background.ts +16 -0
  462. package/apps/sessrumnir/src/main/workflow-discovery.test.ts +252 -0
  463. package/apps/sessrumnir/src/main/workflow-monitor.test.ts +371 -0
  464. package/apps/sessrumnir/src/main/workflow-monitor.ts +1020 -0
  465. package/apps/sessrumnir/src/main/workspace-activity.test.ts +227 -0
  466. package/apps/sessrumnir/src/main/workspace-activity.ts +215 -0
  467. package/apps/sessrumnir/src/main/workspace-manager.test.ts +680 -0
  468. package/apps/sessrumnir/src/main/workspace-manager.ts +1199 -0
  469. package/apps/sessrumnir/src/main/workspace-trust.test.ts +78 -0
  470. package/apps/sessrumnir/src/main/workspace-trust.ts +84 -0
  471. package/apps/sessrumnir/src/preload/index.ts +358 -0
  472. package/apps/sessrumnir/src/preload/tsconfig.json +20 -0
  473. package/apps/sessrumnir/src/renderer/index.html +18 -0
  474. package/apps/sessrumnir/src/renderer/public/theme-boot.js +30 -0
  475. package/apps/sessrumnir/src/renderer/src/app.tsx +194 -0
  476. package/apps/sessrumnir/src/renderer/src/assets/openmoji-color.woff2 +0 -0
  477. package/apps/sessrumnir/src/renderer/src/assets/pi-logo.svg +7 -0
  478. package/apps/sessrumnir/src/renderer/src/assets/ymir-mark.svg +55 -0
  479. package/apps/sessrumnir/src/renderer/src/assets.d.ts +7 -0
  480. package/apps/sessrumnir/src/renderer/src/components/chat-code-highlight.ts +239 -0
  481. package/apps/sessrumnir/src/renderer/src/components/chat-file-link.ts +140 -0
  482. package/apps/sessrumnir/src/renderer/src/components/chat-input.tsx +729 -0
  483. package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.test.ts +175 -0
  484. package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.ts +100 -0
  485. package/apps/sessrumnir/src/renderer/src/components/chat-panel.tsx +523 -0
  486. package/apps/sessrumnir/src/renderer/src/components/chat-project-picker.tsx +193 -0
  487. package/apps/sessrumnir/src/renderer/src/components/chat-search.tsx +251 -0
  488. package/apps/sessrumnir/src/renderer/src/components/code-editor-highlight.ts +85 -0
  489. package/apps/sessrumnir/src/renderer/src/components/code-editor-language.test.ts +19 -0
  490. package/apps/sessrumnir/src/renderer/src/components/code-editor-language.ts +88 -0
  491. package/apps/sessrumnir/src/renderer/src/components/code-editor.tsx +128 -0
  492. package/apps/sessrumnir/src/renderer/src/components/command-palette.tsx +315 -0
  493. package/apps/sessrumnir/src/renderer/src/components/command-results.tsx +80 -0
  494. package/apps/sessrumnir/src/renderer/src/components/composer-permission-menu.tsx +91 -0
  495. package/apps/sessrumnir/src/renderer/src/components/context-menu.tsx +457 -0
  496. package/apps/sessrumnir/src/renderer/src/components/copy-button.tsx +36 -0
  497. package/apps/sessrumnir/src/renderer/src/components/council-panels.tsx +193 -0
  498. package/apps/sessrumnir/src/renderer/src/components/custom-models-editor.tsx +330 -0
  499. package/apps/sessrumnir/src/renderer/src/components/diagnostics-panel.tsx +380 -0
  500. package/apps/sessrumnir/src/renderer/src/components/diff-viewer.tsx +318 -0
  501. package/apps/sessrumnir/src/renderer/src/components/ember-background.tsx +113 -0
  502. package/apps/sessrumnir/src/renderer/src/components/error-boundary.tsx +38 -0
  503. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.test.ts +43 -0
  504. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.ts +51 -0
  505. package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog.tsx +486 -0
  506. package/apps/sessrumnir/src/renderer/src/components/file-tree.tsx +784 -0
  507. package/apps/sessrumnir/src/renderer/src/components/git-conveyor-actions.tsx +246 -0
  508. package/apps/sessrumnir/src/renderer/src/components/home-screen.tsx +396 -0
  509. package/apps/sessrumnir/src/renderer/src/components/image-viewer.tsx +100 -0
  510. package/apps/sessrumnir/src/renderer/src/components/line-numbered-code.tsx +58 -0
  511. package/apps/sessrumnir/src/renderer/src/components/markdown-renderer.tsx +237 -0
  512. package/apps/sessrumnir/src/renderer/src/components/message-bubble.tsx +1006 -0
  513. package/apps/sessrumnir/src/renderer/src/components/mission-control.tsx +341 -0
  514. package/apps/sessrumnir/src/renderer/src/components/model-selector.tsx +219 -0
  515. package/apps/sessrumnir/src/renderer/src/components/note-picker.tsx +155 -0
  516. package/apps/sessrumnir/src/renderer/src/components/notes-panel.tsx +437 -0
  517. package/apps/sessrumnir/src/renderer/src/components/package-browser.tsx +604 -0
  518. package/apps/sessrumnir/src/renderer/src/components/permission-mode.test.ts +22 -0
  519. package/apps/sessrumnir/src/renderer/src/components/permission-mode.ts +49 -0
  520. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.test.ts +44 -0
  521. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.ts +35 -0
  522. package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor.tsx +226 -0
  523. package/apps/sessrumnir/src/renderer/src/components/permission-selector.tsx +104 -0
  524. package/apps/sessrumnir/src/renderer/src/components/resize-handle.tsx +51 -0
  525. package/apps/sessrumnir/src/renderer/src/components/review-rail.tsx +194 -0
  526. package/apps/sessrumnir/src/renderer/src/components/session-menu-position.test.ts +22 -0
  527. package/apps/sessrumnir/src/renderer/src/components/session-menu-position.ts +34 -0
  528. package/apps/sessrumnir/src/renderer/src/components/session-panel.tsx +711 -0
  529. package/apps/sessrumnir/src/renderer/src/components/session-runtime-indicator.tsx +43 -0
  530. package/apps/sessrumnir/src/renderer/src/components/settings-panel.tsx +1360 -0
  531. package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.test.ts +47 -0
  532. package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.ts +76 -0
  533. package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.test.ts +73 -0
  534. package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.ts +47 -0
  535. package/apps/sessrumnir/src/renderer/src/components/sidebar.tsx +1045 -0
  536. package/apps/sessrumnir/src/renderer/src/components/skills-panel.tsx +159 -0
  537. package/apps/sessrumnir/src/renderer/src/components/stats-panel.tsx +368 -0
  538. package/apps/sessrumnir/src/renderer/src/components/status-bar.tsx +274 -0
  539. package/apps/sessrumnir/src/renderer/src/components/status-popover.tsx +481 -0
  540. package/apps/sessrumnir/src/renderer/src/components/streaming-bubble.tsx +128 -0
  541. package/apps/sessrumnir/src/renderer/src/components/subagent-progress.tsx +254 -0
  542. package/apps/sessrumnir/src/renderer/src/components/task-launcher.tsx +168 -0
  543. package/apps/sessrumnir/src/renderer/src/components/terminal.tsx +203 -0
  544. package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.test.ts +35 -0
  545. package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.ts +35 -0
  546. package/apps/sessrumnir/src/renderer/src/components/theme-editor.tsx +440 -0
  547. package/apps/sessrumnir/src/renderer/src/components/theme-gallery.tsx +373 -0
  548. package/apps/sessrumnir/src/renderer/src/components/thinking-level-selector.tsx +85 -0
  549. package/apps/sessrumnir/src/renderer/src/components/timeline.tsx +289 -0
  550. package/apps/sessrumnir/src/renderer/src/components/tool-call-icon.ts +32 -0
  551. package/apps/sessrumnir/src/renderer/src/components/workflow-navigator.tsx +901 -0
  552. package/apps/sessrumnir/src/renderer/src/components/workspace-tabs.tsx +241 -0
  553. package/apps/sessrumnir/src/renderer/src/global.d.ts +8 -0
  554. package/apps/sessrumnir/src/renderer/src/hooks/use-folder-drop.ts +101 -0
  555. package/apps/sessrumnir/src/renderer/src/hooks.test.ts +88 -0
  556. package/apps/sessrumnir/src/renderer/src/hooks.ts +574 -0
  557. package/apps/sessrumnir/src/renderer/src/i18n.test.ts +54 -0
  558. package/apps/sessrumnir/src/renderer/src/i18n.ts +89 -0
  559. package/apps/sessrumnir/src/renderer/src/index.css +425 -0
  560. package/apps/sessrumnir/src/renderer/src/main.tsx +42 -0
  561. package/apps/sessrumnir/src/renderer/src/message-grouping.test.ts +422 -0
  562. package/apps/sessrumnir/src/renderer/src/message-grouping.ts +410 -0
  563. package/apps/sessrumnir/src/renderer/src/message-parsing.test.ts +86 -0
  564. package/apps/sessrumnir/src/renderer/src/message-parsing.ts +166 -0
  565. package/apps/sessrumnir/src/renderer/src/store-external-prompts.test.ts +161 -0
  566. package/apps/sessrumnir/src/renderer/src/store-session-transitions.test.ts +2090 -0
  567. package/apps/sessrumnir/src/renderer/src/store-timeline.test.ts +52 -0
  568. package/apps/sessrumnir/src/renderer/src/store-turn-error.test.ts +136 -0
  569. package/apps/sessrumnir/src/renderer/src/store.ts +3726 -0
  570. package/apps/sessrumnir/src/renderer/src/subagent-detection.test.ts +55 -0
  571. package/apps/sessrumnir/src/renderer/src/theme/engine.test.ts +29 -0
  572. package/apps/sessrumnir/src/renderer/src/theme/engine.ts +20 -0
  573. package/apps/sessrumnir/src/renderer/src/themes/breeze-claudius.json +69 -0
  574. package/apps/sessrumnir/src/renderer/src/themes/breeze-dark.json +69 -0
  575. package/apps/sessrumnir/src/renderer/src/themes/breeze-light.json +69 -0
  576. package/apps/sessrumnir/src/renderer/src/themes/builtin-themes.test.ts +95 -0
  577. package/apps/sessrumnir/src/renderer/src/themes/dark.json +46 -0
  578. package/apps/sessrumnir/src/renderer/src/themes/fensalir.json +82 -0
  579. package/apps/sessrumnir/src/renderer/src/themes/gruvbox.json +69 -0
  580. package/apps/sessrumnir/src/renderer/src/themes/index.ts +60 -0
  581. package/apps/sessrumnir/src/renderer/src/themes/light.json +46 -0
  582. package/apps/sessrumnir/src/renderer/src/themes/nord.json +69 -0
  583. package/apps/sessrumnir/src/renderer/src/themes/sessrumnir.json +82 -0
  584. package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.test.ts +81 -0
  585. package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.ts +57 -0
  586. package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.test.ts +62 -0
  587. package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.ts +28 -0
  588. package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.test.ts +57 -0
  589. package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.ts +41 -0
  590. package/apps/sessrumnir/src/renderer/src/utils/ipc-error.test.ts +22 -0
  591. package/apps/sessrumnir/src/renderer/src/utils/ipc-error.ts +12 -0
  592. package/apps/sessrumnir/src/renderer/src/utils/model-search.test.ts +63 -0
  593. package/apps/sessrumnir/src/renderer/src/utils/model-search.ts +20 -0
  594. package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.test.ts +31 -0
  595. package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.ts +27 -0
  596. package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.test.ts +15 -0
  597. package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.ts +22 -0
  598. package/apps/sessrumnir/src/renderer/src/utils/process-status-label.test.ts +13 -0
  599. package/apps/sessrumnir/src/renderer/src/utils/process-status-label.ts +19 -0
  600. package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.test.ts +68 -0
  601. package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.ts +52 -0
  602. package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.test.ts +46 -0
  603. package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.ts +23 -0
  604. package/apps/sessrumnir/src/renderer/src/utils/relative-time.tsx +25 -0
  605. package/apps/sessrumnir/src/renderer/src/utils/session-title.test.ts +98 -0
  606. package/apps/sessrumnir/src/renderer/src/utils/session-title.ts +58 -0
  607. package/apps/sessrumnir/src/renderer/src/utils/stale-guard.test.ts +29 -0
  608. package/apps/sessrumnir/src/renderer/src/utils/stale-guard.ts +20 -0
  609. package/apps/sessrumnir/src/renderer/src/utils/theme.test.ts +174 -0
  610. package/apps/sessrumnir/src/renderer/src/utils/theme.ts +159 -0
  611. package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.test.ts +130 -0
  612. package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.ts +84 -0
  613. package/apps/sessrumnir/src/renderer/tsconfig.json +24 -0
  614. package/apps/sessrumnir/src/shared/agent-engine-label.test.ts +45 -0
  615. package/apps/sessrumnir/src/shared/agent-engine-label.ts +53 -0
  616. package/apps/sessrumnir/src/shared/app-settings.test.ts +25 -0
  617. package/apps/sessrumnir/src/shared/app-settings.ts +23 -0
  618. package/apps/sessrumnir/src/shared/bridge-http.ts +341 -0
  619. package/apps/sessrumnir/src/shared/bridge.ts +337 -0
  620. package/apps/sessrumnir/src/shared/council-config.test.ts +300 -0
  621. package/apps/sessrumnir/src/shared/council-config.ts +356 -0
  622. package/apps/sessrumnir/src/shared/default-settings.ts +39 -0
  623. package/apps/sessrumnir/src/shared/folder-drop.test.ts +118 -0
  624. package/apps/sessrumnir/src/shared/folder-drop.ts +74 -0
  625. package/apps/sessrumnir/src/shared/fork-point.test.ts +30 -0
  626. package/apps/sessrumnir/src/shared/fork-point.ts +24 -0
  627. package/apps/sessrumnir/src/shared/i18n/i18next.d.ts +10 -0
  628. package/apps/sessrumnir/src/shared/i18n/index.test.ts +51 -0
  629. package/apps/sessrumnir/src/shared/i18n/index.ts +50 -0
  630. package/apps/sessrumnir/src/shared/i18n/languages.ts +12 -0
  631. package/apps/sessrumnir/src/shared/i18n/locale-checks.ts +98 -0
  632. package/apps/sessrumnir/src/shared/i18n/locales.test.ts +92 -0
  633. package/apps/sessrumnir/src/shared/i18n/pseudo.test.ts +39 -0
  634. package/apps/sessrumnir/src/shared/i18n/pseudo.ts +44 -0
  635. package/apps/sessrumnir/src/shared/i18n/resolve.test.ts +43 -0
  636. package/apps/sessrumnir/src/shared/i18n/resolve.ts +41 -0
  637. package/apps/sessrumnir/src/shared/i18n/resources.ts +11 -0
  638. package/apps/sessrumnir/src/shared/ipc-contracts.ts +1539 -0
  639. package/apps/sessrumnir/src/shared/models-config.test.ts +124 -0
  640. package/apps/sessrumnir/src/shared/models-config.ts +167 -0
  641. package/apps/sessrumnir/src/shared/package-filter.ts +19 -0
  642. package/apps/sessrumnir/src/shared/package-spec.test.ts +51 -0
  643. package/apps/sessrumnir/src/shared/package-spec.ts +25 -0
  644. package/apps/sessrumnir/src/shared/path-compare.test.ts +38 -0
  645. package/apps/sessrumnir/src/shared/path-compare.ts +76 -0
  646. package/apps/sessrumnir/src/shared/permission-mode.ts +8 -0
  647. package/apps/sessrumnir/src/shared/pi-command.test.ts +165 -0
  648. package/apps/sessrumnir/src/shared/pi-command.ts +139 -0
  649. package/apps/sessrumnir/src/shared/product-name.ts +7 -0
  650. package/apps/sessrumnir/src/shared/session-lineage.test.ts +103 -0
  651. package/apps/sessrumnir/src/shared/session-lineage.ts +50 -0
  652. package/apps/sessrumnir/src/shared/session-preview.test.ts +93 -0
  653. package/apps/sessrumnir/src/shared/session-preview.ts +56 -0
  654. package/apps/sessrumnir/src/shared/sidebar-width.test.ts +58 -0
  655. package/apps/sessrumnir/src/shared/sidebar-width.ts +37 -0
  656. package/apps/sessrumnir/src/shared/theme/builtin-ids.ts +16 -0
  657. package/apps/sessrumnir/src/shared/theme/resolve.test.ts +66 -0
  658. package/apps/sessrumnir/src/shared/theme/resolve.ts +29 -0
  659. package/apps/sessrumnir/src/shared/theme/syntax-defaults.ts +24 -0
  660. package/apps/sessrumnir/src/shared/theme/theme-file.test.ts +88 -0
  661. package/apps/sessrumnir/src/shared/theme/theme-file.ts +132 -0
  662. package/apps/sessrumnir/src/shared/theme/tokens.test.ts +35 -0
  663. package/apps/sessrumnir/src/shared/theme/tokens.ts +86 -0
  664. package/apps/sessrumnir/src/shared/tsconfig.json +17 -0
  665. package/apps/sessrumnir/src/shared/untrusted-data.test.ts +32 -0
  666. package/apps/sessrumnir/src/shared/untrusted-data.ts +22 -0
  667. package/apps/sessrumnir/src/shared/version-compare.test.ts +33 -0
  668. package/apps/sessrumnir/src/shared/version-compare.ts +50 -0
  669. package/apps/sessrumnir/src/shared/workflow-control.ts +33 -0
  670. package/apps/sessrumnir/src/web-server.ts +315 -0
  671. package/apps/sessrumnir/tsconfig.json +26 -0
  672. package/apps/smidja/smidja_build.py +45 -0
  673. package/apps/smidja/smidja_build_review.py +76 -0
  674. package/apps/smidja/smidja_build_test.py +79 -0
  675. package/apps/smidja/smidja_document.py +76 -0
  676. package/apps/smidja/smidja_modules/agent_cc.py +15 -0
  677. package/apps/smidja/smidja_modules/agent_opencode.py +194 -0
  678. package/apps/smidja/smidja_modules/agent_pi.py +392 -0
  679. package/apps/smidja/smidja_modules/agents.py +993 -0
  680. package/apps/smidja/smidja_modules/changes.py +103 -0
  681. package/apps/smidja/smidja_modules/console.py +131 -0
  682. package/apps/smidja/smidja_modules/data_types.py +447 -0
  683. package/apps/smidja/smidja_modules/gates.py +108 -0
  684. package/apps/smidja/smidja_modules/git_helper.py +120 -0
  685. package/apps/smidja/smidja_modules/lm_local.py +144 -0
  686. package/apps/smidja/smidja_modules/permissions.py +194 -0
  687. package/apps/smidja/smidja_modules/prompts.py +21 -0
  688. package/apps/smidja/smidja_modules/quality.py +240 -0
  689. package/apps/smidja/smidja_modules/runner.py +155 -0
  690. package/apps/smidja/smidja_modules/session.py +50 -0
  691. package/apps/smidja/smidja_modules/stall_watch.py +149 -0
  692. package/apps/smidja/smidja_modules/tracer.py +271 -0
  693. package/apps/smidja/smidja_modules/utils.py +77 -0
  694. package/apps/smidja/smidja_orchestrate.py +162 -0
  695. package/apps/smidja/smidja_plan.py +45 -0
  696. package/apps/smidja/smidja_plan_build.py +55 -0
  697. package/apps/smidja/smidja_plan_build_test.py +86 -0
  698. package/apps/smidja/smidja_plan_build_test_quality.py +98 -0
  699. package/apps/smidja/smidja_prompt.py +44 -0
  700. package/apps/smidja/smidja_quality.py +49 -0
  701. package/apps/smidja/smidja_recon_iv.py +202 -0
  702. package/apps/smidja/smidja_scout.py +45 -0
  703. package/apps/smidja/smidja_simple_sdlc.py +183 -0
  704. package/apps/smidja/smidja_smidja_config/smidja.config.yaml +143 -0
  705. package/apps/smidja-factory/AGENTS.md +165 -0
  706. package/apps/smidja-factory/README.md +3 -0
  707. package/apps/smidja-factory/SKILL.md +18 -0
  708. package/apps/smidja-factory/apps/visualizer/.oxlintrc.json +18 -0
  709. package/apps/smidja-factory/apps/visualizer/bun.lock +269 -0
  710. package/apps/smidja-factory/apps/visualizer/desktop/icon.png +0 -0
  711. package/apps/smidja-factory/apps/visualizer/desktop/main.js +140 -0
  712. package/apps/smidja-factory/apps/visualizer/desktop/package-lock.json +871 -0
  713. package/apps/smidja-factory/apps/visualizer/desktop/package.json +18 -0
  714. package/apps/smidja-factory/apps/visualizer/desktop/preload.js +19 -0
  715. package/apps/smidja-factory/apps/visualizer/index.html +28 -0
  716. package/apps/smidja-factory/apps/visualizer/package.json +31 -0
  717. package/apps/smidja-factory/apps/visualizer/public/icon.svg +7 -0
  718. package/apps/smidja-factory/apps/visualizer/public/models/claude.png +0 -0
  719. package/apps/smidja-factory/apps/visualizer/public/models/gemini.png +0 -0
  720. package/apps/smidja-factory/apps/visualizer/public/models/kimi.png +0 -0
  721. package/apps/smidja-factory/apps/visualizer/public/models/openai.png +0 -0
  722. package/apps/smidja-factory/apps/visualizer/public/models/zai.png +0 -0
  723. package/apps/smidja-factory/apps/visualizer/server/chat.ts +425 -0
  724. package/apps/smidja-factory/apps/visualizer/server/db.ts +1050 -0
  725. package/apps/smidja-factory/apps/visualizer/server/index.ts +648 -0
  726. package/apps/smidja-factory/apps/visualizer/server/model-catalog.ts +90 -0
  727. package/apps/smidja-factory/apps/visualizer/server/roster-api.ts +75 -0
  728. package/apps/smidja-factory/apps/visualizer/server/roster_resolve.py +170 -0
  729. package/apps/smidja-factory/apps/visualizer/server/settings.ts +106 -0
  730. package/apps/smidja-factory/apps/visualizer/shared/types.ts +630 -0
  731. package/apps/smidja-factory/apps/visualizer/src/App.vue +379 -0
  732. package/apps/smidja-factory/apps/visualizer/src/components/ChatInput.vue +155 -0
  733. package/apps/smidja-factory/apps/visualizer/src/components/ChatThread.vue +219 -0
  734. package/apps/smidja-factory/apps/visualizer/src/components/ChatView.vue +335 -0
  735. package/apps/smidja-factory/apps/visualizer/src/components/DecisionsView.vue +177 -0
  736. package/apps/smidja-factory/apps/visualizer/src/components/DetailSection.vue +76 -0
  737. package/apps/smidja-factory/apps/visualizer/src/components/MemoryView.vue +397 -0
  738. package/apps/smidja-factory/apps/visualizer/src/components/PhaseDetail.vue +1354 -0
  739. package/apps/smidja-factory/apps/visualizer/src/components/PhaseDots.vue +55 -0
  740. package/apps/smidja-factory/apps/visualizer/src/components/SessionCard.vue +583 -0
  741. package/apps/smidja-factory/apps/visualizer/src/components/SessionMiniCard.vue +58 -0
  742. package/apps/smidja-factory/apps/visualizer/src/components/SessionPanel.vue +272 -0
  743. package/apps/smidja-factory/apps/visualizer/src/components/SessionStartForm.vue +152 -0
  744. package/apps/smidja-factory/apps/visualizer/src/components/SessionTrace.vue +1157 -0
  745. package/apps/smidja-factory/apps/visualizer/src/components/SessionsList.vue +96 -0
  746. package/apps/smidja-factory/apps/visualizer/src/components/SettingsView.vue +152 -0
  747. package/apps/smidja-factory/apps/visualizer/src/components/StatChip.vue +98 -0
  748. package/apps/smidja-factory/apps/visualizer/src/components/StatsView.vue +382 -0
  749. package/apps/smidja-factory/apps/visualizer/src/components/StatusChip.vue +73 -0
  750. package/apps/smidja-factory/apps/visualizer/src/components/ToolCallCard.vue +156 -0
  751. package/apps/smidja-factory/apps/visualizer/src/lib/api.ts +177 -0
  752. package/apps/smidja-factory/apps/visualizer/src/lib/chat-api.ts +95 -0
  753. package/apps/smidja-factory/apps/visualizer/src/lib/chat-store.ts +351 -0
  754. package/apps/smidja-factory/apps/visualizer/src/lib/events.ts +131 -0
  755. package/apps/smidja-factory/apps/visualizer/src/lib/format.ts +92 -0
  756. package/apps/smidja-factory/apps/visualizer/src/lib/highlight.ts +54 -0
  757. package/apps/smidja-factory/apps/visualizer/src/lib/markdown.ts +120 -0
  758. package/apps/smidja-factory/apps/visualizer/src/lib/models.ts +42 -0
  759. package/apps/smidja-factory/apps/visualizer/src/lib/router.ts +67 -0
  760. package/apps/smidja-factory/apps/visualizer/src/lib/types.ts +61 -0
  761. package/apps/smidja-factory/apps/visualizer/src/main.ts +13 -0
  762. package/apps/smidja-factory/apps/visualizer/src/style.css +592 -0
  763. package/apps/smidja-factory/apps/visualizer/tsconfig.json +30 -0
  764. package/apps/smidja-factory/apps/visualizer/vite.config.js +32 -0
  765. package/apps/smidja-factory/apps/visualizer/vite.config.ts +31 -0
  766. package/apps/smidja-factory/cookbooks/create_config.md +61 -0
  767. package/apps/smidja-factory/cookbooks/create_smidja.md +120 -0
  768. package/apps/smidja-factory/cookbooks/how_to_prompt_for_the_eng.md +110 -0
  769. package/apps/smidja-factory/cookbooks/install.md +56 -0
  770. package/apps/smidja-factory/cookbooks/run_smidja.md +122 -0
  771. package/apps/smidja-factory/cookbooks/smidja_overview.md +88 -0
  772. package/apps/smidja-factory/cookbooks/update_config.md +103 -0
  773. package/apps/smidja-factory/cookbooks/update_modules.md +100 -0
  774. package/apps/smidja-factory/cookbooks/update_smidja.md +90 -0
  775. package/apps/smidja-factory/docs/enhancement-plan.md +420 -0
  776. package/apps/smidja-factory/references/config.md +202 -0
  777. package/apps/smidja-factory/references/handoff.md +161 -0
  778. package/apps/smidja-factory/references/observability.md +166 -0
  779. package/apps/smidja-factory/scripts/install.py +113 -0
  780. package/apps/smidja-factory/scripts/make_config.py +35 -0
  781. package/apps/smidja-factory/scripts/make_smidja.py +119 -0
  782. package/apps/smidja-factory/skills/add-or-edit-ai-models/SKILL.md +151 -0
  783. package/apps/smidja-factory/skills/create-new-agent/SKILL.md +125 -0
  784. package/apps/smidja-factory/skills/create-new-teams/SKILL.md +151 -0
  785. package/apps/smidja-factory/skills/git-ops/README.md +25 -0
  786. package/apps/smidja-factory/skills/git-ops/SKILL.md +18 -0
  787. package/apps/smidja-factory/skills/git-ops/create_branch.sh +79 -0
  788. package/apps/smidja-factory/skills/git-ops/safe_commit.sh +82 -0
  789. package/apps/smidja-factory/skills/git-ops/sync_upstream.sh +94 -0
  790. package/apps/smidja-factory/skills/how-to-run-agent-teams/SKILL.md +117 -0
  791. package/apps/smidja-factory/skills/smidja-instructions/SKILL.md +168 -0
  792. package/apps/smidja-factory/skills/smidja-launcher/SKILL.md +155 -0
  793. package/apps/smidja-factory/skills/smidja-start/SKILL.md +266 -0
  794. package/apps/smidja-factory/skills/volundr/SKILL.md +232 -0
  795. package/apps/smidja-factory/skills/volundr/prompt/dispatch-prompt.md +91 -0
  796. package/apps/smidja-factory/skills/volundr/prompt/volundr-system.md +63 -0
  797. package/apps/smidja-factory/skills/volundr/scripts/volundr-observe.py +52 -0
  798. package/apps/smidja-factory/skills/volundr/scripts/volundr-recall.py +54 -0
  799. package/apps/smidja-factory/skills/volundr/scripts/volundr-status.py +51 -0
  800. package/apps/smidja-factory/skills/volundr/scripts/volundr-teach.py +47 -0
  801. package/apps/smidja-factory/templates/env.sample +28 -0
  802. package/apps/smidja-factory/templates/harness_engineering/subagents.ts +706 -0
  803. package/apps/smidja-factory/templates/harness_engineering/themeMap.ts +145 -0
  804. package/apps/smidja-factory/templates/justfile +89 -0
  805. package/apps/smidja-factory/templates/prompt_engineering/builder/system.md +13 -0
  806. package/apps/smidja-factory/templates/prompt_engineering/builder/user.md +34 -0
  807. package/apps/smidja-factory/templates/prompt_engineering/documenter/system.md +17 -0
  808. package/apps/smidja-factory/templates/prompt_engineering/documenter/user.md +48 -0
  809. package/apps/smidja-factory/templates/prompt_engineering/orchestrator/system.md +55 -0
  810. package/apps/smidja-factory/templates/prompt_engineering/orchestrator/user.md +39 -0
  811. package/apps/smidja-factory/templates/prompt_engineering/planner/system.md +21 -0
  812. package/apps/smidja-factory/templates/prompt_engineering/planner/user.md +45 -0
  813. package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/system.md +51 -0
  814. package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/user.md +26 -0
  815. package/apps/smidja-factory/templates/prompt_engineering/reviewer/system.md +16 -0
  816. package/apps/smidja-factory/templates/prompt_engineering/reviewer/user.md +44 -0
  817. package/apps/smidja-factory/templates/prompt_engineering/scout/system.md +32 -0
  818. package/apps/smidja-factory/templates/prompt_engineering/scout/user.md +34 -0
  819. package/apps/smidja-factory/templates/smidja/smidja_build.py +45 -0
  820. package/apps/smidja-factory/templates/smidja/smidja_build_review.py +76 -0
  821. package/apps/smidja-factory/templates/smidja/smidja_build_test.py +79 -0
  822. package/apps/smidja-factory/templates/smidja/smidja_document.py +76 -0
  823. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_cc.py +15 -0
  824. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_opencode.py +194 -0
  825. package/apps/smidja-factory/templates/smidja/smidja_modules/agent_pi.py +392 -0
  826. package/apps/smidja-factory/templates/smidja/smidja_modules/agents.py +993 -0
  827. package/apps/smidja-factory/templates/smidja/smidja_modules/changes.py +103 -0
  828. package/apps/smidja-factory/templates/smidja/smidja_modules/console.py +131 -0
  829. package/apps/smidja-factory/templates/smidja/smidja_modules/data_types.py +447 -0
  830. package/apps/smidja-factory/templates/smidja/smidja_modules/gates.py +108 -0
  831. package/apps/smidja-factory/templates/smidja/smidja_modules/git_helper.py +120 -0
  832. package/apps/smidja-factory/templates/smidja/smidja_modules/lm_local.py +144 -0
  833. package/apps/smidja-factory/templates/smidja/smidja_modules/permissions.py +194 -0
  834. package/apps/smidja-factory/templates/smidja/smidja_modules/prompts.py +21 -0
  835. package/apps/smidja-factory/templates/smidja/smidja_modules/quality.py +240 -0
  836. package/apps/smidja-factory/templates/smidja/smidja_modules/runner.py +155 -0
  837. package/apps/smidja-factory/templates/smidja/smidja_modules/session.py +50 -0
  838. package/apps/smidja-factory/templates/smidja/smidja_modules/stall_watch.py +149 -0
  839. package/apps/smidja-factory/templates/smidja/smidja_modules/tracer.py +271 -0
  840. package/apps/smidja-factory/templates/smidja/smidja_modules/utils.py +77 -0
  841. package/apps/smidja-factory/templates/smidja/smidja_orchestrate.py +162 -0
  842. package/apps/smidja-factory/templates/smidja/smidja_plan.py +45 -0
  843. package/apps/smidja-factory/templates/smidja/smidja_plan_build.py +55 -0
  844. package/apps/smidja-factory/templates/smidja/smidja_plan_build_test.py +86 -0
  845. package/apps/smidja-factory/templates/smidja/smidja_plan_build_test_quality.py +98 -0
  846. package/apps/smidja-factory/templates/smidja/smidja_prompt.py +44 -0
  847. package/apps/smidja-factory/templates/smidja/smidja_quality.py +49 -0
  848. package/apps/smidja-factory/templates/smidja/smidja_recon_iv.py +202 -0
  849. package/apps/smidja-factory/templates/smidja/smidja_scout.py +45 -0
  850. package/apps/smidja-factory/templates/smidja/smidja_simple_sdlc.py +183 -0
  851. package/apps/smidja-factory/templates/smidja.config.yaml +143 -0
  852. package/bin/app-build.sh +36 -0
  853. package/bin/electron-lib.sh +25 -0
  854. package/bin/sessrumnir.sh +1 -1
  855. package/bin/ymir-install.sh +7 -0
  856. package/install.sh +88 -0
  857. package/package.json +16 -7
  858. package/scripts/electron.sh +6 -2
  859. package/.agents/bus/messages.json +0 -1
  860. package/.agents/config/agents.machine.example.yaml +0 -16
  861. package/.pi/mcp.json +0 -16
  862. package/assets/reference/state/.branch-eligible-owner +0 -4
  863. package/assets/reference/state/.branch-mirror-cursor +0 -1
  864. package/assets/reference/state/.branch-outcomes-cursor +0 -1
  865. package/assets/reference/state/.branch-session +0 -1
  866. package/assets/reference/state/.count-default_wF_p5 +0 -1
  867. package/assets/reference/state/.count-default_wF_p6 +0 -1
  868. package/assets/reference/state/.count-default_wR_p2 +0 -1
  869. package/assets/reference/state/.count-default_wS_p2 +0 -1
  870. package/assets/reference/state/.count-firstmate_fm-server-knowledge +0 -1
  871. package/assets/reference/state/.hash-default_wF_p5 +0 -1
  872. package/assets/reference/state/.hash-default_wF_p6 +0 -1
  873. package/assets/reference/state/.hash-default_wR_p2 +0 -1
  874. package/assets/reference/state/.hash-default_wS_p2 +0 -1
  875. package/assets/reference/state/.hash-firstmate_fm-server-knowledge +0 -1
  876. package/assets/reference/state/.heartbeat-streak +0 -1
  877. package/assets/reference/state/.inactive-outcome-reconcile +0 -2
  878. package/assets/reference/state/.lock +0 -1
  879. package/assets/reference/state/.pi-branch-extension-loaded +0 -1
  880. package/assets/reference/state/.pi-turnend-extension-loaded +0 -2
  881. package/assets/reference/state/.pi-watch-extension-loaded +0 -2
  882. package/assets/reference/state/.seen-server-knowledge_turn-ended +0 -1
  883. package/assets/reference/state/.session-start-agents-baseline +0 -2
  884. package/assets/reference/state/.session-start-complete +0 -1
  885. package/assets/reference/state/.stale-default_wF_p5 +0 -1
  886. package/assets/reference/state/.stale-default_wR_p2 +0 -1
  887. package/assets/reference/state/.stale-firstmate_fm-server-knowledge +0 -1
  888. package/assets/reference/state/.stale-since-default_wF_p5 +0 -1
  889. package/assets/reference/state/.stale-since-default_wR_p2 +0 -1
  890. package/assets/reference/state/.stale-since-firstmate_fm-server-knowledge +0 -1
  891. package/assets/reference/state/.startup-network.delivered +0 -1
  892. package/assets/reference/state/.startup-network.report +0 -1
  893. package/assets/reference/state/.startup-network.status +0 -10
  894. package/assets/reference/state/.startup-network.timings +0 -6
  895. package/assets/reference/state/.trace-context-effective +0 -1
  896. package/assets/reference/state/.wake-queue +0 -0
  897. package/assets/reference/state/.wake-queue.seq +0 -1
  898. package/assets/reference/state/.wake-rows.consume.BcqXSq +0 -0
  899. package/assets/reference/state/.wake-rows.consume.NDZZei +0 -0
  900. package/assets/reference/state/.wake-rows.consume.VGkhYm +0 -0
  901. package/assets/reference/state/.wake-rows.consume.tR2rTy +0 -0
  902. package/assets/reference/state/.watch-arm-output.3581p6 +0 -0
  903. package/assets/reference/state/.watch-arm-output.3ciDNP +0 -0
  904. package/assets/reference/state/.watch-arm-output.SksR6k +0 -0
  905. package/assets/reference/state/.watch-cycle-exits.log +0 -22
  906. package/assets/reference/state/.watch-deliveries.log +0 -19
  907. package/assets/reference/state/.watch-triage.log +0 -38
  908. package/assets/reference/state/.watch.lock/fm-home +0 -1
  909. package/assets/reference/state/.watch.lock/pid +0 -1
  910. package/assets/reference/state/.watch.lock/pid-identity +0 -1
  911. package/assets/reference/state/.watch.lock/watcher-path +0 -1
  912. package/assets/reference/state/.watch.lock.owner.qqbxmS/fm-home +0 -1
  913. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid +0 -1
  914. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid-identity +0 -1
  915. package/assets/reference/state/.watch.lock.owner.qqbxmS/watcher-path +0 -1
  916. package/assets/reference/state/.watcher-down +0 -1
  917. package/assets/reference/state/branch-outcomes.jsonl +0 -1
  918. package/assets/reference/state/branch-session/2026-09-04T16-49-38-671Z_01a06d53-6b6f-7619-930d-e6dfd9a4a29e.jsonl +0 -211
  919. package/assets/reference/state/home-summary.json +0 -42
  920. package/assets/reference/state/server-knowledge.busy-gen +0 -1
  921. package/assets/reference/state/server-knowledge.busy-state +0 -1
  922. package/assets/reference/state/server-knowledge.herdr-presentation +0 -12
  923. package/assets/reference/state/server-knowledge.meta +0 -18
  924. package/assets/reference/state/server-knowledge.pi-ext.ts +0 -26
  925. package/assets/reference/state/server-knowledge.turn-ended +0 -0
  926. /package/{assets/reference/state/.last-check → apps/hlidskjalf/src/app/.gitkeep} +0 -0
  927. /package/{assets/reference/state/.last-heartbeat → apps/hlidskjalf/src/components/.gitkeep} +0 -0
  928. /package/{assets/reference/state/.last-watcher-beat → apps/hlidskjalf/src/services/.gitkeep} +0 -0
  929. /package/{assets/reference/state/.main-eligible-rows.tmp.p2HMKo → apps/smidja/smidja_modules/__init__.py} +0 -0
  930. /package/{assets/reference/state/.main-eligible-rows.tmp.Exxm82 → apps/smidja-factory/templates/smidja/smidja_modules/__init__.py} +0 -0
@@ -0,0 +1,1006 @@
1
+ import { memo, useState, useRef } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+ import { useAppStore, type DisplayMessage } from '../store'
4
+ import { modelDisplayName } from '../../../shared/models-config'
5
+ import { DEFAULT_SETTINGS } from '../../../shared/default-settings'
6
+ import {
7
+ toolCallLabel,
8
+ toolCallStatusLabel,
9
+ toolKind,
10
+ toolCallFile,
11
+ parseEdits,
12
+ editStats,
13
+ splitReadTruncationNote,
14
+ type EditBlock,
15
+ } from '../message-grouping'
16
+ import { toolCallIconFor } from './tool-call-icon'
17
+ import { getCodeEditorLanguageName } from './code-editor-language'
18
+ import { highlightCodeToHtml } from './chat-code-highlight'
19
+ import { LineNumberedCode } from './line-numbered-code'
20
+ import { MarkdownRenderer } from './markdown-renderer'
21
+ import { CopyButton } from './copy-button'
22
+ import { useContextMenu, buildMessageContextMenu } from './context-menu'
23
+ import { RelativeTime } from '../utils/relative-time'
24
+ import { clsx } from 'clsx'
25
+ import {
26
+ Copy,
27
+ Check,
28
+ ChevronDown,
29
+ ChevronRight,
30
+ Brain,
31
+ Bot,
32
+ Edit3,
33
+ GitBranch,
34
+ RotateCcw,
35
+ Download,
36
+ Send,
37
+ } from 'lucide-react'
38
+
39
+ function MessageBubbleImpl({
40
+ message,
41
+ onRetry,
42
+ hideModelHeader,
43
+ }: {
44
+ message: DisplayMessage
45
+ onRetry?: (messageId: string) => void
46
+ // When rendered inside a tool group that shows a single shared model header,
47
+ // suppress this message's own provider · model line to avoid repetition.
48
+ hideModelHeader?: boolean
49
+ }): React.JSX.Element {
50
+ const { t } = useTranslation()
51
+ const [copied, setCopied] = useState(false)
52
+ const [showThinking, setShowThinking] = useState(false)
53
+ const [isEditing, setIsEditing] = useState(false)
54
+ const [editContent, setEditContent] = useState(message.content)
55
+
56
+ const handleCopy = async () => {
57
+ await navigator.clipboard.writeText(message.content)
58
+ setCopied(true)
59
+ setTimeout(() => setCopied(false), 2000)
60
+ }
61
+
62
+ const handleEdit = () => {
63
+ setEditContent(message.content)
64
+ setIsEditing(true)
65
+ }
66
+
67
+ const handleSaveEdit = async () => {
68
+ if (editContent.trim() !== message.content) {
69
+ // Resend the edited message
70
+ await useAppStore.getState().sendPrompt(editContent.trim())
71
+ }
72
+ setIsEditing(false)
73
+ }
74
+
75
+ const handleCancelEdit = () => {
76
+ setIsEditing(false)
77
+ }
78
+
79
+ const handleBranch = () => {
80
+ // Branch from this message's position
81
+ const excerpt = message.content.slice(0, 100) + (message.content.length > 100 ? '...' : '')
82
+ useAppStore.getState().addMessage({
83
+ id: `branch-${Date.now()}`,
84
+ role: 'system',
85
+ content: t('chat.message.branchedFrom', { excerpt }),
86
+ timestamp: Date.now(),
87
+ })
88
+ }
89
+
90
+ const handleExport = () => {
91
+ const blob = new Blob([message.content], { type: 'text/markdown' })
92
+ const url = URL.createObjectURL(blob)
93
+ const a = document.createElement('a')
94
+ a.href = url
95
+ const label = message.role === 'assistant' ? 'desktop' : message.role
96
+ const d = new Date(message.timestamp ?? Date.now())
97
+ const p = (n: number): string => String(n).padStart(2, '0')
98
+ // Format: yyyy-mm-ddThh-mm-ss (local time)
99
+ const stamp = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`
100
+ a.download = `pi-${label}-${stamp}.md`
101
+ document.body.appendChild(a)
102
+ a.click()
103
+ a.remove()
104
+ URL.revokeObjectURL(url)
105
+ }
106
+
107
+ // Context menu for message-specific actions
108
+ const { show: showContextMenu, ContextMenuComponent: MessageContextMenu } = useContextMenu()
109
+
110
+ const startNoteFromText = useAppStore((state) => state.startNoteFromText)
111
+
112
+ const handleMessageContextMenu = (e: React.MouseEvent) => {
113
+ showContextMenu(e, buildMessageContextMenu(message.content, startNoteFromText))
114
+ }
115
+
116
+ if (message.role === 'user') {
117
+ return (
118
+ <>
119
+ <div data-scroll-anchor={message.id} onContextMenu={handleMessageContextMenu}>
120
+ <UserMessage
121
+ message={message}
122
+ isEditing={isEditing}
123
+ editContent={editContent}
124
+ onEditContentChange={setEditContent}
125
+ onCopy={handleCopy}
126
+ onEdit={handleEdit}
127
+ onSaveEdit={handleSaveEdit}
128
+ onCancelEdit={handleCancelEdit}
129
+ onBranch={handleBranch}
130
+ onRetry={onRetry}
131
+ onExport={handleExport}
132
+ />
133
+ </div>
134
+ {MessageContextMenu}
135
+ </>
136
+ )
137
+ }
138
+
139
+ if (message.role === 'assistant') {
140
+ return (
141
+ <>
142
+ <div onContextMenu={handleMessageContextMenu}>
143
+ <AssistantMessage
144
+ message={message}
145
+ onCopy={handleCopy}
146
+ copied={copied}
147
+ showThinking={showThinking}
148
+ onToggleThinking={() => setShowThinking(!showThinking)}
149
+ onExport={handleExport}
150
+ hideModelHeader={hideModelHeader}
151
+ />
152
+ </div>
153
+ {MessageContextMenu}
154
+ </>
155
+ )
156
+ }
157
+
158
+ if (message.role === 'toolResult') {
159
+ return (
160
+ <>
161
+ <div onContextMenu={handleMessageContextMenu}>
162
+ <ToolResultMessage message={message} />
163
+ </div>
164
+ {MessageContextMenu}
165
+ </>
166
+ )
167
+ }
168
+
169
+ if (message.role === 'system') {
170
+ return <SystemMessage message={message} />
171
+ }
172
+
173
+ return <></>
174
+ }
175
+
176
+ // Memoized so finalized messages don't re-parse markdown on every store update
177
+ // (e.g. during streaming of a later message). Relies on stable `message`
178
+ // references and a stable `onRetry` callback from the caller.
179
+ export const MessageBubble = memo(MessageBubbleImpl)
180
+
181
+ // ─── User Message ────────────────────────────────────────────────────────────
182
+
183
+ function UserMessage({
184
+ message,
185
+ isEditing,
186
+ editContent,
187
+ onEditContentChange,
188
+ onCopy,
189
+ onEdit,
190
+ onSaveEdit,
191
+ onCancelEdit,
192
+ onBranch,
193
+ onRetry,
194
+ onExport,
195
+ }: {
196
+ message: DisplayMessage
197
+ isEditing: boolean
198
+ editContent: string
199
+ onEditContentChange: (v: string) => void
200
+ onCopy: () => void
201
+ onEdit: () => void
202
+ onSaveEdit: () => void
203
+ onCancelEdit: () => void
204
+ onBranch: () => void
205
+ onRetry?: (id: string) => void
206
+ onExport: () => void
207
+ }): React.JSX.Element {
208
+ const { t } = useTranslation()
209
+ const editRef = useRef<HTMLTextAreaElement>(null)
210
+
211
+ if (isEditing) {
212
+ return (
213
+ <div className="group mb-4 flex justify-end animate-fade-in">
214
+ <div className="w-full max-w-[80%]">
215
+ <textarea
216
+ ref={editRef}
217
+ value={editContent}
218
+ onChange={(e) => onEditContentChange(e.target.value)}
219
+ className="font-chat w-full rounded-2xl rounded-br-md bg-card px-4 py-2.5 text-sm text-primary resize-none min-h-[40px] max-h-48 outline-none"
220
+ rows={1}
221
+ onInput={(e) => {
222
+ const t = e.currentTarget
223
+ t.style.height = 'auto'
224
+ t.style.height = `${Math.min(t.scrollHeight, 192)}px`
225
+ }}
226
+ autoFocus
227
+ />
228
+ <div className="flex items-center justify-end gap-1 mt-1">
229
+ <button
230
+ onClick={onCancelEdit}
231
+ className="rounded px-2 py-1 text-xs text-muted hover:text-primary transition-colors"
232
+ >
233
+ {t('common.cancel')}
234
+ </button>
235
+ <button
236
+ onClick={onSaveEdit}
237
+ className="flex items-center gap-1 rounded bg-accent px-2 py-1 text-xs text-inverse hover:bg-accent-hover transition-colors"
238
+ >
239
+ <Send size={10} />
240
+ {t('common.send')}
241
+ </button>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ )
246
+ }
247
+
248
+ return (
249
+ <div className="group mb-4 flex justify-end animate-fade-in">
250
+ <div className="relative max-w-[80%]">
251
+ <div className="rounded-2xl rounded-br-md bg-card px-4 py-2.5 text-sm text-primary">
252
+ {message.attachments && message.attachments.length > 0 && (
253
+ <div className={clsx('flex flex-wrap gap-2', message.content && 'mb-2')}>
254
+ {message.attachments.map((attachment, index) => (
255
+ <div
256
+ key={`${attachment.name}-${index}`}
257
+ className="overflow-hidden rounded-md border border-white/20 bg-black/10"
258
+ title={attachment.name}
259
+ >
260
+ <img
261
+ src={`data:${attachment.mimeType};base64,${attachment.data}`}
262
+ alt={attachment.name}
263
+ className="h-16 w-16 object-cover"
264
+ />
265
+ </div>
266
+ ))}
267
+ </div>
268
+ )}
269
+ <div className="font-chat whitespace-pre-wrap break-words">{message.content}</div>
270
+ </div>
271
+ {/* Actions */}
272
+ <div className="mt-1 flex items-center justify-end gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
273
+ <ActionButton icon={<Copy size={11} />} onClick={onCopy} title={t('common.copy')} />
274
+ <ActionButton icon={<Edit3 size={11} />} onClick={onEdit} title={t('chat.message.editAndResend')} />
275
+ <ActionButton icon={<GitBranch size={11} />} onClick={onBranch} title={t('chat.message.branchFromHere')} />
276
+ {onRetry && (
277
+ <ActionButton icon={<RotateCcw size={11} />} onClick={() => onRetry(message.id)} title={t('common.retry')} />
278
+ )}
279
+ <ActionButton icon={<Download size={11} />} onClick={onExport} title={t('common.export')} />
280
+ </div>
281
+ </div>
282
+ </div>
283
+ )
284
+ }
285
+
286
+ // ─── Assistant Message ───────────────────────────────────────────────────────
287
+
288
+ function AssistantMessage({
289
+ message,
290
+ onCopy,
291
+ copied,
292
+ showThinking,
293
+ onToggleThinking,
294
+ onExport,
295
+ hideModelHeader,
296
+ }: {
297
+ message: DisplayMessage
298
+ onCopy: () => void
299
+ copied: boolean
300
+ showThinking: boolean
301
+ onToggleThinking: () => void
302
+ onExport: () => void
303
+ hideModelHeader?: boolean
304
+ }): React.JSX.Element {
305
+ const { t } = useTranslation()
306
+ const customModels = useAppStore((state) => state.customModels)
307
+ const thinkingEnabled = useAppStore(
308
+ (state) => state.settingsDraft.showThinking ?? state.settings?.showThinking ?? DEFAULT_SETTINGS.showThinking
309
+ )
310
+
311
+ // With thinking off, a thinking-only turn (no text, no tool calls) would
312
+ // collapse to just an orphaned model/provider header. Suppress it entirely so
313
+ // the header doesn't appear to repeat. Only applies when thinking is hidden —
314
+ // with thinking on, the block itself gives the header something to sit above.
315
+ const hasVisibleBody = message.content.trim().length > 0 || (message.toolCalls?.length ?? 0) > 0
316
+ if (!thinkingEnabled && !hasVisibleBody) {
317
+ return <></>
318
+ }
319
+
320
+ // A turn whose only body is tool calls (no prose) is a pure tool operation, so
321
+ // its avatar mirrors the operation (globe for a fetch, document for a read, …)
322
+ // instead of the Bot avatar / group spacer.
323
+ const ToolIcon =
324
+ message.content.trim().length === 0 && message.toolCalls && message.toolCalls.length > 0
325
+ ? toolCallIconFor(message.toolCalls[0].name)
326
+ : null
327
+
328
+ // Show the provider · model header whenever Pi attributes the turn — including
329
+ // a standalone tool call, so you can see who ran it. It's suppressed only
330
+ // inside a group (hideModelHeader), where the group's one shared header stands
331
+ // in and repeating it per row would just be noise.
332
+ const showModelHeader = !!message.model && !hideModelHeader
333
+
334
+ // A grouped thinking-only turn (thinking, no text, no tool calls). It sits
335
+ // between the call/result rows of a serial run; with the default mb-4 rhythm it
336
+ // ends up 16px from its neighbours, which reads as too airy for a bare
337
+ // "Thinking" toggle. Tighten it to 8px on both sides: pull it up 8px against the
338
+ // previous row's mb-4 (-mt-2) and give it an 8px bottom (mb-2) instead of mb-4.
339
+ const isGroupedPureThinking =
340
+ hideModelHeader &&
341
+ !!message.thinking &&
342
+ thinkingEnabled &&
343
+ message.content.trim().length === 0 &&
344
+ (message.toolCalls?.length ?? 0) === 0
345
+
346
+ // A pure-tool turn (body is only tool calls): render every call on its own row
347
+ // behind its own operation icon, so each call — including parallel calls in a
348
+ // single turn — is labelled with the right glyph. Standalone (attributed) turns
349
+ // also show a Bot avatar + provider·model header; grouped turns omit the header
350
+ // (the group's shared header stands in) but keep the same per-row-icon layout so
351
+ // a single-call and a multi-call turn align identically.
352
+ if (ToolIcon) {
353
+ const showThinkingBlock = !!message.thinking && thinkingEnabled
354
+ const hasHeaderArea = showModelHeader || showThinkingBlock
355
+ // The collapsible thinking block, reused in the attributed (header) layout and
356
+ // the grouped (padded-block) layout below.
357
+ const thinkingBlock = showThinkingBlock ? (
358
+ <div className="thinking-hover">
359
+ <div className="flex h-7 items-center gap-1">
360
+ <button
361
+ onClick={onToggleThinking}
362
+ className="flex items-center gap-1 text-sm text-dim hover:text-muted transition-colors"
363
+ >
364
+ <Brain size={12} />
365
+ {showThinking ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
366
+ {t('common.thinking')}
367
+ </button>
368
+ <CopyButton text={message.thinking!} className="thinking-copy-btn" />
369
+ </div>
370
+ {showThinking && (
371
+ <div className="markdown-body min-w-0 font-sans italic text-sm text-muted break-words [overflow-wrap:anywhere]">
372
+ <MarkdownRenderer content={message.thinking!} />
373
+ </div>
374
+ )}
375
+ </div>
376
+ ) : null
377
+ return (
378
+ <div className="group mb-4 animate-fade-in">
379
+ {/* Attributed (standalone) turn: Bot avatar + provider·model header, with
380
+ the thinking block tucked under it. */}
381
+ {showModelHeader && (
382
+ <div className="flex items-start gap-3">
383
+ <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-card">
384
+ <Bot size={14} className="text-muted" />
385
+ </div>
386
+ <div className="min-w-0 flex-1">
387
+ <div className="flex h-7 items-center gap-2 text-sm text-dim">
388
+ <span>{message.provider}</span>
389
+ <span className="text-ghost">·</span>
390
+ <span>{modelDisplayName(message.model!, customModels)}</span>
391
+ {message.cost !== undefined && (
392
+ <>
393
+ <span className="text-ghost">·</span>
394
+ <span>${message.cost.toFixed(4)}</span>
395
+ </>
396
+ )}
397
+ <span className="text-ghost">·</span>
398
+ <RelativeTime timestamp={message.timestamp} />
399
+ </div>
400
+ {thinkingBlock && <div className="mt-2">{thinkingBlock}</div>}
401
+ </div>
402
+ </div>
403
+ )}
404
+ {/* Grouped turn: no header (the group's shared header stands in). Render the
405
+ thinking block as a plain padded block — pl-10 aligns it with the tool
406
+ badges (w-7 icon + gap-3), and controlling its own margins directly
407
+ (-mt-2 above vs the group's mt-4, the tool row's mt-2 below) keeps it
408
+ evenly 8px on both sides whether collapsed or expanded. Nesting it in the
409
+ icon-row flex (as the tool rows do) let the 28px icon spacer floor the
410
+ row height, so the gap below collapsed once the expanded text outgrew it. */}
411
+ {!showModelHeader && thinkingBlock && (
412
+ <div className="-mt-2 pl-10">{thinkingBlock}</div>
413
+ )}
414
+ {/* One row per tool-call box, each behind its own operation icon. mt-2 sets
415
+ the gap under the header/thinking area; space-y-4 keeps the box-to-box gap
416
+ equal to the mb-4 rhythm between separate call/result rows. */}
417
+ <div className={clsx('space-y-4', hasHeaderArea && 'mt-2')}>
418
+ {message.toolCalls!.map((tc) => {
419
+ const RowIcon = toolCallIconFor(tc.name)
420
+ return (
421
+ <div key={tc.id} className="flex items-start gap-3">
422
+ {/* Center the icon on the box's header row (h-8 ≈ py-2 + text-xs). */}
423
+ <div className="flex h-8 w-7 shrink-0 items-center justify-center">
424
+ <div className="flex h-7 w-7 items-center justify-center rounded-full bg-card">
425
+ <RowIcon size={14} className="text-dim" />
426
+ </div>
427
+ </div>
428
+ <div className="min-w-0 flex-1">
429
+ <ToolCallBadge toolCall={tc} />
430
+ </div>
431
+ </div>
432
+ )
433
+ })}
434
+ </div>
435
+ </div>
436
+ )
437
+ }
438
+
439
+ return (
440
+ <div className={clsx('group animate-fade-in', isGroupedPureThinking ? '-mt-2 mb-2' : 'mb-4')}>
441
+ <div className="flex items-start gap-3">
442
+ {/* Avatar — the Bot avatar for a prose turn, except inside a tool group
443
+ (the group shows one shared header above) where it keeps an empty
444
+ spacer so its body stays aligned with the icon-avatared rows.
445
+ Pure-tool turns are handled earlier (each call gets its own icon). */}
446
+ {hideModelHeader ? (
447
+ <div className="h-7 w-7 shrink-0" />
448
+ ) : (
449
+ <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-card">
450
+ <Bot size={14} className="text-muted" />
451
+ </div>
452
+ )}
453
+
454
+ {/* Content */}
455
+ <div className="min-w-0 flex-1">
456
+ {/* Model info */}
457
+ {showModelHeader && (
458
+ <div className="flex h-7 items-center gap-2 text-sm text-dim">
459
+ <span>{message.provider}</span>
460
+ <span className="text-ghost">·</span>
461
+ <span>{modelDisplayName(message.model!, customModels)}</span>
462
+ {message.cost !== undefined && (
463
+ <>
464
+ <span className="text-ghost">·</span>
465
+ <span>${message.cost.toFixed(4)}</span>
466
+ </>
467
+ )}
468
+ <span className="text-ghost">·</span>
469
+ <RelativeTime timestamp={message.timestamp} />
470
+ </div>
471
+ )}
472
+
473
+ {/* Thinking block — gated by the Show Thinking setting. mb-2 only when a
474
+ body (text/tool calls) follows it; on a pure-thinking turn there's
475
+ nothing below, so the mb-2 would stack on the message's own mb-4 and
476
+ leave the block with a bigger gap below than above. */}
477
+ {message.thinking && thinkingEnabled && (
478
+ <div
479
+ className={clsx(
480
+ 'thinking-hover',
481
+ (message.content.trim().length > 0 || (message.toolCalls?.length ?? 0) > 0) && 'mb-2'
482
+ )}
483
+ >
484
+ <div className="flex h-7 items-center gap-1">
485
+ <button
486
+ onClick={onToggleThinking}
487
+ className="flex items-center gap-1 text-sm text-dim hover:text-muted transition-colors"
488
+ >
489
+ <Brain size={12} />
490
+ {showThinking ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
491
+ {t('common.thinking')}
492
+ </button>
493
+ <CopyButton text={message.thinking} className="thinking-copy-btn" />
494
+ </div>
495
+ {showThinking && (
496
+ <div className="markdown-body min-w-0 font-sans italic text-sm text-muted break-words [overflow-wrap:anywhere]">
497
+ <MarkdownRenderer content={message.thinking} />
498
+ </div>
499
+ )}
500
+ </div>
501
+ )}
502
+
503
+ {/* Text content */}
504
+ {message.content.trim() && (
505
+ <div
506
+ data-scroll-anchor={message.id}
507
+ className={clsx(
508
+ 'markdown-body text-sm',
509
+ // Sit just below the model/provider header — 6px, 2px tighter than
510
+ // the tool-call box's 8px top gap.
511
+ (message.model || message.thinking) && 'mt-1.5'
512
+ )}>
513
+ <MarkdownRenderer content={message.content} />
514
+ </div>
515
+ )}
516
+
517
+ {/* Actions — copy/export the response text. Rendered directly beneath
518
+ the text (before any tool calls) so on a turn that mixes prose with
519
+ tool calls the buttons stay attached to the prose instead of landing
520
+ under the tool-call box and splitting a run of grouped tool badges.
521
+ Only shown when there's real response text — a tool-only message's
522
+ content is just whitespace/newlines, which is truthy but has nothing
523
+ to copy/export. Shown on hover only, matching the user-message actions. */}
524
+ {message.content.trim() && (
525
+ <div className="mt-2 flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
526
+ <ActionButton
527
+ icon={copied ? <Check size={11} /> : <Copy size={11} />}
528
+ onClick={onCopy}
529
+ title={copied ? t('common.copied') : t('common.copy')}
530
+ label={copied ? t('common.copied') : t('common.copy')}
531
+ />
532
+ <ActionButton icon={<Download size={11} />} onClick={onExport} title={t('common.export')} />
533
+ </div>
534
+ )}
535
+
536
+ {/* Tool calls */}
537
+ {message.toolCalls && message.toolCalls.length > 0 && (
538
+ <div className={clsx(
539
+ // space-y-4 so parallel calls in one turn (multiple badges here) keep
540
+ // the same 16px rhythm as separate call/result rows (each mb-4),
541
+ // rather than bunching up ~4px apart.
542
+ 'space-y-4',
543
+ // Pad the top only when something actually renders above the tool
544
+ // box — a visible model header, a thinking block, or response text.
545
+ // A grouped tool row has no header (showModelHeader is false), so it
546
+ // top-aligns with its icon avatar; that also keeps the gap between
547
+ // grouped call/result pairs equal to the gap within a pair (no stray
548
+ // 8px from an mt-2 sitting under a suppressed header).
549
+ (showModelHeader ||
550
+ (message.thinking && thinkingEnabled) ||
551
+ message.content.trim().length > 0) && 'mt-2'
552
+ )}>
553
+ {message.toolCalls.map((tc) => (
554
+ <ToolCallBadge key={tc.id} toolCall={tc} />
555
+ ))}
556
+ </div>
557
+ )}
558
+ </div>
559
+ </div>
560
+ </div>
561
+ )
562
+ }
563
+
564
+ // ─── Tool Call Badge ─────────────────────────────────────────────────────────
565
+
566
+ function ToolCallBadge({
567
+ toolCall,
568
+ }: {
569
+ toolCall: NonNullable<DisplayMessage['toolCalls']>[number]
570
+ }): React.JSX.Element {
571
+ const { t } = useTranslation()
572
+ // Edit diffs open expanded so the change is visible without a second result pill.
573
+ const edits = toolKind(toolCall.name) === 'edit' ? parseEdits(toolCall.arguments) : null
574
+ const stats = edits ? editStats(edits) : null
575
+ const editFile = edits ? toolCallFile(toolCall.name, toolCall.arguments) : null
576
+ const editLang = editFile ? getCodeEditorLanguageName(editFile) : 'plain text'
577
+ const [expanded, setExpanded] = useState(() => Boolean(edits))
578
+
579
+ const status = toolCall.isExecuting
580
+ ? 'running'
581
+ : toolCall.isError === true
582
+ ? 'error'
583
+ : toolCall.isError === false || toolCall.result !== undefined
584
+ ? 'done'
585
+ : null
586
+
587
+ return (
588
+ <div className="relative min-w-0 overflow-hidden rounded-lg border border-border bg-surface/50">
589
+ <CopyButton text={toolCallCopyText(toolCall)} className="absolute right-1.5 top-1.5 z-10" />
590
+ <button
591
+ type="button"
592
+ onClick={() => setExpanded(!expanded)}
593
+ className="flex w-full min-w-0 items-center gap-2 py-2 pl-3 pr-9 text-xs text-muted hover:text-secondary transition-colors"
594
+ >
595
+ <span className="font-jetbrains min-w-0 truncate">
596
+ {toolCallLabel(toolCall.name, toolCall.arguments, t)}
597
+ </span>
598
+ {stats && (
599
+ <span className="shrink-0 font-jetbrains">
600
+ <span className="text-success">+{stats.added}</span>{' '}
601
+ <span className="text-error">-{stats.removed}</span>
602
+ </span>
603
+ )}
604
+ {toolCall.durationMs !== undefined && !toolCall.isExecuting && (
605
+ <span className="shrink-0 text-faint">{formatDuration(toolCall.durationMs)}</span>
606
+ )}
607
+ {status && (
608
+ <span
609
+ className={clsx(
610
+ 'shrink-0 capitalize',
611
+ status === 'running' && 'text-warning animate-pulse',
612
+ status === 'error' && 'text-error',
613
+ status === 'done' && 'text-success'
614
+ )}
615
+ >
616
+ {toolCallStatusLabel(status, t)}
617
+ </span>
618
+ )}
619
+ {expanded ? (
620
+ <ChevronDown size={12} className="ml-auto shrink-0" />
621
+ ) : (
622
+ <ChevronRight size={12} className="ml-auto shrink-0" />
623
+ )}
624
+ </button>
625
+ {expanded && (
626
+ <div className="border-t border-border px-3 py-2">
627
+ {edits ? (
628
+ <EditDiff blocks={edits} lang={editLang} />
629
+ ) : (
630
+ <pre className="font-jetbrains overflow-x-auto text-xs text-dim">
631
+ {formatToolCallArgs(toolCall.arguments)}
632
+ </pre>
633
+ )}
634
+ {toolCall.result && (
635
+ <div className="mt-2 border-t border-border pt-2">
636
+ <pre className="font-jetbrains overflow-x-auto text-xs text-muted">
637
+ {toolCall.result}
638
+ </pre>
639
+ </div>
640
+ )}
641
+ </div>
642
+ )}
643
+ </div>
644
+ )
645
+ }
646
+
647
+ // Renders an edit call's blocks as a diff: each block shows its old lines removed
648
+ // (red) then its new lines added (green), with the code syntax-highlighted. A
649
+ // block replacement, not a line-level diff — enough to read the change at a glance.
650
+ function EditDiff({ blocks, lang }: { blocks: EditBlock[]; lang: string }): React.JSX.Element {
651
+ return (
652
+ <div className="overflow-x-auto font-jetbrains text-xs leading-relaxed text-secondary">
653
+ {blocks.map((b, i) => (
654
+ <div key={i} className={i > 0 ? 'mt-2 border-t border-border pt-2' : ''}>
655
+ <DiffLines text={b.oldText} lang={lang} kind="remove" />
656
+ <DiffLines text={b.newText} lang={lang} kind="add" />
657
+ </div>
658
+ ))}
659
+ </div>
660
+ )
661
+ }
662
+
663
+ // One side of a diff block (all removed, or all added), syntax-highlighted.
664
+ // highlightCodeToHtml emits newlines separately, so its output splits cleanly
665
+ // into per-line HTML; falls back to plain text when no parser matches.
666
+ function DiffLines({
667
+ text,
668
+ lang,
669
+ kind,
670
+ }: {
671
+ text: string
672
+ lang: string
673
+ kind: 'add' | 'remove'
674
+ }): React.JSX.Element | null {
675
+ if (text === '') return null
676
+ const html = highlightCodeToHtml(text, lang)
677
+ const lines = (html ?? text).split('\n')
678
+ const rowBg = kind === 'add' ? 'bg-success-bg' : 'bg-error-bg'
679
+ const markColor = kind === 'add' ? 'text-success' : 'text-error'
680
+ const sign = kind === 'add' ? '+ ' : '- '
681
+ return (
682
+ <>
683
+ {lines.map((line, j) => (
684
+ <div key={j} className={clsx('whitespace-pre', rowBg)}>
685
+ <span className={clsx('select-none', markColor)}>{sign}</span>
686
+ {html !== null ? (
687
+ <span dangerouslySetInnerHTML={{ __html: line || ' ' }} />
688
+ ) : (
689
+ <span>{line || ' '}</span>
690
+ )}
691
+ </div>
692
+ ))}
693
+ </>
694
+ )
695
+ }
696
+
697
+ // ─── Tool Result Message ─────────────────────────────────────────────────────
698
+
699
+ function ToolResultMessage({ message }: { message: DisplayMessage }): React.JSX.Element {
700
+ const { t } = useTranslation()
701
+ // Collapsed by default — tool results can be huge and otherwise dominate the
702
+ // scrollback. Mirrors ToolCallBadge's expand/collapse affordance.
703
+ const [expanded, setExpanded] = useState(false)
704
+ const newlineIdx = message.content.indexOf('\n')
705
+ const firstLine = newlineIdx === -1 ? message.content : message.content.slice(0, newlineIdx)
706
+ // Everything after the first line; that line already shows in the header row.
707
+ const rest = newlineIdx === -1 ? '' : message.content.slice(newlineIdx + 1)
708
+ // Nothing beyond the first line → nothing to expand, so no chevron.
709
+ const expandable = rest.trim() !== ''
710
+
711
+ // Only a file-read result is the file's content, so only it renders as
712
+ // line-numbered, syntax-highlighted code. Everything else stays plain text —
713
+ // notably writes/creates, whose result is a "wrote N bytes" success line (not
714
+ // the file), plus CSV, command output, and fetches.
715
+ const codeLang =
716
+ message.toolName && toolKind(message.toolName) === 'read' && message.toolFile
717
+ ? getCodeEditorLanguageName(message.toolFile)
718
+ : 'plain text'
719
+ const isCode = codeLang !== 'plain text'
720
+
721
+ return (
722
+ <div className="mb-4 animate-fade-in">
723
+ <div className="flex items-start gap-3">
724
+ {/* No result icon — an empty spacer (same width as a tool-call avatar)
725
+ keeps the result box left-aligned with the tool-call box above it. */}
726
+ <div className="w-7 shrink-0" />
727
+ <div className="min-w-0 flex-1">
728
+ <div className="relative rounded-lg border border-border bg-surface/50">
729
+ <CopyButton text={message.content} className="absolute right-1.5 top-1.5" />
730
+ {!expandable ? (
731
+ // Single line of content — nothing to expand, so no chevron.
732
+ <div className="flex w-full items-center py-2 pl-3 pr-9 text-xs text-muted">
733
+ <span className="font-jetbrains min-w-0 flex-1 truncate text-left">{firstLine}</span>
734
+ </div>
735
+ ) : expanded && isCode ? (
736
+ // Code view: no header row (its first line repeats as line 1 of the
737
+ // body). Float the collapse control top-right, beside copy, so the
738
+ // code sits flush at the top.
739
+ <button
740
+ onClick={() => setExpanded(false)}
741
+ className="absolute right-8 top-1.5 rounded p-1 text-dim transition-colors hover:text-secondary"
742
+ title={t('common.collapse')}
743
+ aria-label={t('common.collapse')}
744
+ >
745
+ <ChevronDown size={12} />
746
+ </button>
747
+ ) : (
748
+ <button
749
+ onClick={() => setExpanded(!expanded)}
750
+ className="flex w-full items-center gap-2 py-2 pl-3 pr-9 text-xs text-muted hover:text-secondary transition-colors"
751
+ >
752
+ <span className="font-jetbrains min-w-0 flex-1 truncate text-left">{firstLine}</span>
753
+ {expanded ? (
754
+ <ChevronDown size={12} className="shrink-0" />
755
+ ) : (
756
+ <ChevronRight size={12} className="shrink-0" />
757
+ )}
758
+ </button>
759
+ )}
760
+ {expandable &&
761
+ expanded &&
762
+ (isCode ? (
763
+ <div className="px-3 py-2">
764
+ <CodeResultView
765
+ content={message.content}
766
+ lang={codeLang}
767
+ onCollapse={() => setExpanded(false)}
768
+ />
769
+ </div>
770
+ ) : (
771
+ rest.trim() && (
772
+ <div className="px-3 pb-2">
773
+ <pre className="font-jetbrains overflow-x-auto text-xs text-muted">
774
+ {rest.slice(0, 2000)}
775
+ {rest.length > 2000 && '\n…'}
776
+ </pre>
777
+ </div>
778
+ )
779
+ ))}
780
+ </div>
781
+ </div>
782
+ </div>
783
+ </div>
784
+ )
785
+ }
786
+
787
+ // Guard against re-parsing a whole huge file for highlighting; the source is
788
+ // already truncated by Pi, this is just a ceiling.
789
+ const MAX_CODE_RESULT_CHARS = 20000
790
+
791
+ // Renders file content as line-numbered, syntax-highlighted code. highlightCodeToHtml
792
+ // emits newlines separately from its <span> runs, so splitting on '\n' yields
793
+ // self-contained per-line HTML; when no parser exists it falls back to plain text.
794
+ function CodeResultView({
795
+ content,
796
+ lang,
797
+ onCollapse,
798
+ }: {
799
+ content: string
800
+ lang: string
801
+ onCollapse: () => void
802
+ }): React.JSX.Element {
803
+ const clipped = content.length > MAX_CODE_RESULT_CHARS
804
+ const clippedContent = clipped ? content.slice(0, MAX_CODE_RESULT_CHARS) : content
805
+ // Peel off Pi's "[N more lines in file…]" footer so it renders as a note, not code.
806
+ const { code, note } = splitReadTruncationNote(clippedContent)
807
+
808
+ return (
809
+ // The first line doubles as a collapse trigger (a large click target, like the
810
+ // collapsed header). A drag to select text isn't a click, so selection works.
811
+ <div className="overflow-x-auto font-jetbrains text-xs leading-relaxed text-secondary">
812
+ <LineNumberedCode content={code} lang={lang} onFirstLineClick={onCollapse} />
813
+ {note && <div className="mt-2 italic text-dim">{note}</div>}
814
+ {clipped && <div className="mt-1 text-faint">…</div>}
815
+ </div>
816
+ )
817
+ }
818
+
819
+ // ─── Tool Group ──────────────────────────────────────────────────────────────
820
+
821
+ // A run of consecutive tool-activity messages (tool calls + their results, plus
822
+ // any thinking-only turns among them) folded into one collapsed block. Collapsed
823
+ // by default to keep repetitive runs from dominating the scrollback; expanding
824
+ // reveals the original messages rendered exactly as they would appear ungrouped.
825
+ function ToolGroupBubbleImpl({
826
+ title,
827
+ messages,
828
+ onRetry,
829
+ }: {
830
+ title: string
831
+ messages: DisplayMessage[]
832
+ onRetry?: (messageId: string) => void
833
+ }): React.JSX.Element {
834
+ const [expanded, setExpanded] = useState(false)
835
+ const customModels = useAppStore((state) => state.customModels)
836
+
837
+ // If every assistant turn in the group used the same model, show one shared
838
+ // provider · model header above the body and suppress the per-turn headers. If
839
+ // they differ (a mid-run model switch), keep each turn's own header so the
840
+ // distinction isn't lost.
841
+ const modelKeys = new Set<string>()
842
+ let sharedModel: string | undefined
843
+ let sharedProvider: string | undefined
844
+ for (const m of messages) {
845
+ if (m.role === 'assistant' && m.model) {
846
+ modelKeys.add(`${m.provider ?? ''}|${m.model}`)
847
+ if (sharedModel === undefined) {
848
+ sharedModel = m.model
849
+ sharedProvider = m.provider
850
+ }
851
+ }
852
+ }
853
+ const showSharedHeader = modelKeys.size === 1 && sharedModel !== undefined
854
+ const groupTimestamp = messages[messages.length - 1]?.timestamp
855
+
856
+ return (
857
+ <div className="group mb-4 animate-fade-in">
858
+ <div className="flex items-start gap-3">
859
+ {/* Bot avatar + model header so a grouped run reads like any other
860
+ assistant message, not a distinct kind of block. */}
861
+ <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-card">
862
+ <Bot size={14} className="text-muted" />
863
+ </div>
864
+ <div className="min-w-0 flex-1">
865
+ {showSharedHeader && (
866
+ <div className="flex h-7 items-center gap-2 text-sm text-dim">
867
+ <span>{sharedProvider}</span>
868
+ <span className="text-ghost">·</span>
869
+ <span>{modelDisplayName(sharedModel as string, customModels)}</span>
870
+ {groupTimestamp !== undefined && (
871
+ <>
872
+ <span className="text-ghost">·</span>
873
+ <RelativeTime timestamp={groupTimestamp} />
874
+ </>
875
+ )}
876
+ </div>
877
+ )}
878
+ <div
879
+ className={clsx(
880
+ 'relative rounded-lg border border-border bg-surface/50',
881
+ showSharedHeader && 'mt-1.5'
882
+ )}
883
+ >
884
+ <CopyButton text={groupCopyText(messages)} className="absolute right-1.5 top-1.5" />
885
+ <button
886
+ onClick={() => setExpanded(!expanded)}
887
+ className="flex w-full items-center gap-2 py-2 pl-3 pr-9 text-xs text-muted hover:text-secondary transition-colors"
888
+ >
889
+ <span className="font-jetbrains">{title}</span>
890
+ {expanded ? (
891
+ <ChevronDown size={12} className="ml-auto shrink-0" />
892
+ ) : (
893
+ <ChevronRight size={12} className="ml-auto shrink-0" />
894
+ )}
895
+ </button>
896
+ </div>
897
+ {/* Expanded body: the original messages, slightly indented to signal
898
+ they belong to the group. Per-turn model headers are suppressed since
899
+ the group already shows one above. mt-4 matches the mb-4 gap between
900
+ the rows inside, so the header→first-row gap equals the row-to-row
901
+ gap. Last child's bottom margin trimmed so it doesn't double up on
902
+ the group's own mb-4. */}
903
+ {expanded && (
904
+ <div className="mt-4 pl-3 [&>*:last-child]:mb-0">
905
+ {messages.map((m) => (
906
+ <MessageBubble
907
+ key={m.id}
908
+ message={m}
909
+ onRetry={onRetry}
910
+ hideModelHeader={showSharedHeader}
911
+ />
912
+ ))}
913
+ </div>
914
+ )}
915
+ </div>
916
+ </div>
917
+ </div>
918
+ )
919
+ }
920
+
921
+ export const ToolGroupBubble = memo(ToolGroupBubbleImpl)
922
+
923
+ // ─── System Message ──────────────────────────────────────────────────────────
924
+
925
+ function SystemMessage({ message }: { message: DisplayMessage }): React.JSX.Element {
926
+ return (
927
+ <div className="mb-4 flex justify-center animate-fade-in">
928
+ <div className="rounded-full bg-surface px-3 py-1 text-xs text-dim">
929
+ {message.content}
930
+ </div>
931
+ </div>
932
+ )
933
+ }
934
+
935
+ // ─── Action Button ───────────────────────────────────────────────────────────
936
+
937
+ function ActionButton({
938
+ icon,
939
+ onClick,
940
+ title,
941
+ label,
942
+ }: {
943
+ icon: React.ReactNode
944
+ onClick: () => void
945
+ title: string
946
+ label?: string
947
+ }): React.JSX.Element {
948
+ return (
949
+ <button
950
+ onClick={onClick}
951
+ className="flex items-center gap-1 rounded px-1.5 py-1 text-xs text-dim hover:bg-surface-hover hover:text-secondary transition-colors"
952
+ title={title}
953
+ aria-label={title}
954
+ >
955
+ {icon}
956
+ {label && <span>{label}</span>}
957
+ </button>
958
+ )
959
+ }
960
+
961
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
962
+
963
+ function formatDuration(ms: number): string {
964
+ if (ms < 1000) return `${ms}ms`
965
+ return `${(ms / 1000).toFixed(ms < 10000 ? 1 : 0)}s`
966
+ }
967
+
968
+ function formatToolCallArgs(args: string): string {
969
+ try {
970
+ const parsed = JSON.parse(args)
971
+ return JSON.stringify(parsed, null, 2)
972
+ } catch {
973
+ return args
974
+ }
975
+ }
976
+
977
+ // The group's copy button yields every call and result in order: each tool
978
+ // call's copy text (raw command / formatted args) followed by its result.
979
+ function groupCopyText(messages: DisplayMessage[]): string {
980
+ const parts: string[] = []
981
+ for (const m of messages) {
982
+ if (m.role === 'assistant') {
983
+ for (const tc of m.toolCalls ?? []) parts.push(toolCallCopyText(tc))
984
+ } else if (m.role === 'toolResult') {
985
+ parts.push(m.content)
986
+ }
987
+ }
988
+ return parts.join('\n\n')
989
+ }
990
+
991
+ // What the copy button on a tool-call box yields: the raw command for
992
+ // shell-style tools (so it pastes cleanly), otherwise the formatted arguments.
993
+ function toolCallCopyText(toolCall: NonNullable<DisplayMessage['toolCalls']>[number]): string {
994
+ try {
995
+ const parsed = JSON.parse(toolCall.arguments)
996
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
997
+ for (const key of ['command', 'cmd', 'script']) {
998
+ const value = (parsed as Record<string, unknown>)[key]
999
+ if (typeof value === 'string' && value.length > 0) return value
1000
+ }
1001
+ }
1002
+ } catch {
1003
+ // fall through to formatted args
1004
+ }
1005
+ return formatToolCallArgs(toolCall.arguments)
1006
+ }