@zerwiz/ymir 0.1.34 → 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 (929) 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/package.json +15 -7
  857. package/scripts/electron.sh +6 -2
  858. package/.agents/bus/messages.json +0 -1
  859. package/.agents/config/agents.machine.example.yaml +0 -16
  860. package/.pi/mcp.json +0 -16
  861. package/assets/reference/state/.branch-eligible-owner +0 -4
  862. package/assets/reference/state/.branch-mirror-cursor +0 -1
  863. package/assets/reference/state/.branch-outcomes-cursor +0 -1
  864. package/assets/reference/state/.branch-session +0 -1
  865. package/assets/reference/state/.count-default_wF_p5 +0 -1
  866. package/assets/reference/state/.count-default_wF_p6 +0 -1
  867. package/assets/reference/state/.count-default_wR_p2 +0 -1
  868. package/assets/reference/state/.count-default_wS_p2 +0 -1
  869. package/assets/reference/state/.count-firstmate_fm-server-knowledge +0 -1
  870. package/assets/reference/state/.hash-default_wF_p5 +0 -1
  871. package/assets/reference/state/.hash-default_wF_p6 +0 -1
  872. package/assets/reference/state/.hash-default_wR_p2 +0 -1
  873. package/assets/reference/state/.hash-default_wS_p2 +0 -1
  874. package/assets/reference/state/.hash-firstmate_fm-server-knowledge +0 -1
  875. package/assets/reference/state/.heartbeat-streak +0 -1
  876. package/assets/reference/state/.inactive-outcome-reconcile +0 -2
  877. package/assets/reference/state/.lock +0 -1
  878. package/assets/reference/state/.pi-branch-extension-loaded +0 -1
  879. package/assets/reference/state/.pi-turnend-extension-loaded +0 -2
  880. package/assets/reference/state/.pi-watch-extension-loaded +0 -2
  881. package/assets/reference/state/.seen-server-knowledge_turn-ended +0 -1
  882. package/assets/reference/state/.session-start-agents-baseline +0 -2
  883. package/assets/reference/state/.session-start-complete +0 -1
  884. package/assets/reference/state/.stale-default_wF_p5 +0 -1
  885. package/assets/reference/state/.stale-default_wR_p2 +0 -1
  886. package/assets/reference/state/.stale-firstmate_fm-server-knowledge +0 -1
  887. package/assets/reference/state/.stale-since-default_wF_p5 +0 -1
  888. package/assets/reference/state/.stale-since-default_wR_p2 +0 -1
  889. package/assets/reference/state/.stale-since-firstmate_fm-server-knowledge +0 -1
  890. package/assets/reference/state/.startup-network.delivered +0 -1
  891. package/assets/reference/state/.startup-network.report +0 -1
  892. package/assets/reference/state/.startup-network.status +0 -10
  893. package/assets/reference/state/.startup-network.timings +0 -6
  894. package/assets/reference/state/.trace-context-effective +0 -1
  895. package/assets/reference/state/.wake-queue +0 -0
  896. package/assets/reference/state/.wake-queue.seq +0 -1
  897. package/assets/reference/state/.wake-rows.consume.BcqXSq +0 -0
  898. package/assets/reference/state/.wake-rows.consume.NDZZei +0 -0
  899. package/assets/reference/state/.wake-rows.consume.VGkhYm +0 -0
  900. package/assets/reference/state/.wake-rows.consume.tR2rTy +0 -0
  901. package/assets/reference/state/.watch-arm-output.3581p6 +0 -0
  902. package/assets/reference/state/.watch-arm-output.3ciDNP +0 -0
  903. package/assets/reference/state/.watch-arm-output.SksR6k +0 -0
  904. package/assets/reference/state/.watch-cycle-exits.log +0 -22
  905. package/assets/reference/state/.watch-deliveries.log +0 -19
  906. package/assets/reference/state/.watch-triage.log +0 -38
  907. package/assets/reference/state/.watch.lock/fm-home +0 -1
  908. package/assets/reference/state/.watch.lock/pid +0 -1
  909. package/assets/reference/state/.watch.lock/pid-identity +0 -1
  910. package/assets/reference/state/.watch.lock/watcher-path +0 -1
  911. package/assets/reference/state/.watch.lock.owner.qqbxmS/fm-home +0 -1
  912. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid +0 -1
  913. package/assets/reference/state/.watch.lock.owner.qqbxmS/pid-identity +0 -1
  914. package/assets/reference/state/.watch.lock.owner.qqbxmS/watcher-path +0 -1
  915. package/assets/reference/state/.watcher-down +0 -1
  916. package/assets/reference/state/branch-outcomes.jsonl +0 -1
  917. package/assets/reference/state/branch-session/2026-09-04T16-49-38-671Z_01a06d53-6b6f-7619-930d-e6dfd9a4a29e.jsonl +0 -211
  918. package/assets/reference/state/home-summary.json +0 -42
  919. package/assets/reference/state/server-knowledge.busy-gen +0 -1
  920. package/assets/reference/state/server-knowledge.busy-state +0 -1
  921. package/assets/reference/state/server-knowledge.herdr-presentation +0 -12
  922. package/assets/reference/state/server-knowledge.meta +0 -18
  923. package/assets/reference/state/server-knowledge.pi-ext.ts +0 -26
  924. package/assets/reference/state/server-knowledge.turn-ended +0 -0
  925. /package/{assets/reference/state/.last-check → apps/hlidskjalf/src/app/.gitkeep} +0 -0
  926. /package/{assets/reference/state/.last-heartbeat → apps/hlidskjalf/src/components/.gitkeep} +0 -0
  927. /package/{assets/reference/state/.last-watcher-beat → apps/hlidskjalf/src/services/.gitkeep} +0 -0
  928. /package/{assets/reference/state/.main-eligible-rows.tmp.p2HMKo → apps/smidja/smidja_modules/__init__.py} +0 -0
  929. /package/{assets/reference/state/.main-eligible-rows.tmp.Exxm82 → apps/smidja-factory/templates/smidja/smidja_modules/__init__.py} +0 -0
@@ -0,0 +1,1020 @@
1
+ import { createHash } from 'crypto'
2
+ import { mkdir, open, readdir, readFile, rename, stat, writeFile } from 'fs/promises'
3
+ import { homedir } from 'os'
4
+ import { basename, join, resolve } from 'path'
5
+ import { pathGroupKey } from '../shared/path-compare'
6
+ import { appLog } from './app-log'
7
+ import { pathsEqual, sanitizePath } from './session-paths'
8
+ import type {
9
+ WorkflowAgentDetail,
10
+ WorkflowAgentSummary,
11
+ WorkflowAgentStatus,
12
+ WorkflowHistoryEntry,
13
+ WorkflowRunDetail,
14
+ WorkflowRunStatus,
15
+ WorkflowRunSummary,
16
+ Workspace,
17
+ } from '../shared/ipc-contracts'
18
+
19
+ const WORKFLOW_HOME = join(homedir(), '.pi', 'workflows')
20
+ const WORKFLOW_PROJECTS_DIR = join(WORKFLOW_HOME, 'projects')
21
+ const WORKFLOW_SETTINGS_PATH = join(WORKFLOW_HOME, 'settings.json')
22
+ const MAX_HISTORY_ENTRIES = 150
23
+ const MAX_ENTRY_CHARS = 20_000
24
+ const MAX_TRANSCRIPT_CHARS = 160_000
25
+ const MAX_SCRIPT_CHARS = 120_000
26
+ const MAX_LOG_LINES = 200
27
+ const MAX_LOG_CHARS = 4_000
28
+ // Bounds for the read-only workflow project discovery (see discoverWorkflowProjects).
29
+ const MAX_DISCOVERED_PROJECTS = 100
30
+ const MAX_SESSION_DIRS_SCANNED = 200
31
+ const MAX_SESSION_FILES_INDEXED = 500
32
+ const MAX_SESSION_HEADER_BYTES = 4 * 1024
33
+ const MAX_RUNS_READ_PER_PROJECT = 3
34
+ const MAX_RUN_FILE_BYTES = 2 * 1024 * 1024
35
+ // Bounds for the per-agent transcript scan (see findPersistedSession). The scan
36
+ // parses whole session files, so it is capped like every other walk here; 200
37
+ // newest sessions of one project cover far more than one workflow run.
38
+ export const MAX_TRANSCRIPT_FILES_SCANNED = 200
39
+ // The prompt needle used to identify an agent's session. It must be short
40
+ // enough to survive the MAX_ENTRY_CHARS truncation of the session text it is
41
+ // compared against, even when the persisted user message wraps the prompt in a
42
+ // preamble; 2 KB of prompt identifies an agent many times over.
43
+ const PROMPT_MATCH_CHARS = 2_000
44
+ /** Cache-key stamp for a store root that does not exist (see dirStamp). */
45
+ const MISSING_DIR_STAMP = 'none'
46
+ const LOG_SCOPE = 'workflow-monitor'
47
+ // pi-dynamic-workflows names project dirs `<slug>-<sha256(cwd).slice(0,12)>`.
48
+ const WORKFLOW_PROJECT_KEY_RE = /^[a-z0-9._-]{1,60}-[0-9a-f]{12}$/
49
+ const RUN_STATUSES = new Set<WorkflowRunStatus>([
50
+ 'pending',
51
+ 'running',
52
+ 'paused',
53
+ 'completed',
54
+ 'failed',
55
+ 'aborted',
56
+ ])
57
+ const AGENT_STATUSES = new Set<WorkflowAgentStatus>([
58
+ 'queued',
59
+ 'running',
60
+ 'done',
61
+ 'error',
62
+ 'skipped',
63
+ ])
64
+
65
+ interface UnknownRecord {
66
+ [key: string]: unknown
67
+ }
68
+
69
+ interface CachedRun {
70
+ mtimeMs: number
71
+ size: number
72
+ ino: number
73
+ value: UnknownRecord
74
+ }
75
+
76
+ const runCache = new Map<string, CachedRun>()
77
+
78
+ interface CachedPersistedSession {
79
+ mtimeMs: number
80
+ size: number
81
+ /** Approximate retained size, used for the eviction budget below. */
82
+ bytes: number
83
+ value: PersistedSessionMatch | null
84
+ }
85
+
86
+ /** A single session file larger than this is never parsed. */
87
+ const MAX_PERSISTED_SESSION_FILE_BYTES = 4 * 1024 * 1024
88
+ /**
89
+ * Retention budget for parsed transcripts held in the main process. Entry
90
+ * counts are the wrong unit: one entry retains up to MAX_TRANSCRIPT_CHARS of
91
+ * text, so a count-based ceiling is a byte ceiling three orders of magnitude
92
+ * higher than intended. ~32 MB holds roughly a hundred full-size transcripts
93
+ * and thousands of ordinary ones.
94
+ */
95
+ const MAX_PERSISTED_SESSION_CACHE_BYTES = 32 * 1024 * 1024
96
+ /** Per-entry cost of the key and the record itself, on top of its text. */
97
+ const PERSISTED_SESSION_ENTRY_OVERHEAD_BYTES = 512
98
+ /** JS strings are UTF-16; one char retains two bytes. */
99
+ const BYTES_PER_CHAR = 2
100
+
101
+ // Insertion order is the LRU order: every hit re-inserts its key, so the front
102
+ // of the Map is always the coldest entry.
103
+ const persistedSessionCache = new Map<string, CachedPersistedSession>()
104
+ let persistedSessionCacheBytes = 0
105
+ let persistedSessionCacheBudget = MAX_PERSISTED_SESSION_CACHE_BYTES
106
+
107
+ /** Test helper: current retention of the persisted-session cache. */
108
+ export function persistedSessionCacheStats(): { entries: number; bytes: number } {
109
+ return { entries: persistedSessionCache.size, bytes: persistedSessionCacheBytes }
110
+ }
111
+
112
+ /** Test helper: drop every cached transcript. */
113
+ export function clearPersistedSessionCache(): void {
114
+ persistedSessionCache.clear()
115
+ persistedSessionCacheBytes = 0
116
+ persistedSessionCacheBudget = MAX_PERSISTED_SESSION_CACHE_BYTES
117
+ }
118
+
119
+ /**
120
+ * Test helper: shrink the retention budget (and evict down to it now) so
121
+ * eviction is observable without writing tens of megabytes of fixtures.
122
+ */
123
+ export function setPersistedSessionCacheBudget(bytes: number): void {
124
+ persistedSessionCacheBudget = bytes
125
+ evictPersistedSessions()
126
+ }
127
+
128
+ function persistedSessionBytes(filePath: string, value: PersistedSessionMatch | null): number {
129
+ let chars = filePath.length
130
+ if (value) {
131
+ chars += value.sessionName.length + value.firstUserText.length
132
+ for (const entry of value.history) chars += entry.text.length + (entry.diff?.length ?? 0)
133
+ }
134
+ return PERSISTED_SESSION_ENTRY_OVERHEAD_BYTES + chars * BYTES_PER_CHAR
135
+ }
136
+
137
+ function evictPersistedSessions(): void {
138
+ for (const [key, entry] of persistedSessionCache) {
139
+ if (persistedSessionCacheBytes <= persistedSessionCacheBudget) return
140
+ persistedSessionCache.delete(key)
141
+ persistedSessionCacheBytes -= entry.bytes
142
+ }
143
+ }
144
+
145
+ /** Cache read that also marks the entry as the most recently used. */
146
+ function takeCachedSession(filePath: string): CachedPersistedSession | undefined {
147
+ const cached = persistedSessionCache.get(filePath)
148
+ if (!cached) return undefined
149
+ persistedSessionCache.delete(filePath)
150
+ persistedSessionCache.set(filePath, cached)
151
+ return cached
152
+ }
153
+
154
+ function cachePersistedSession(
155
+ filePath: string,
156
+ file: { mtimeMs: number; size: number },
157
+ value: PersistedSessionMatch | null
158
+ ): void {
159
+ const previous = persistedSessionCache.get(filePath)
160
+ if (previous) persistedSessionCacheBytes -= previous.bytes
161
+ const bytes = persistedSessionBytes(filePath, value)
162
+ // Re-insert (never update in place) so the entry becomes the newest.
163
+ persistedSessionCache.delete(filePath)
164
+ persistedSessionCache.set(filePath, { mtimeMs: file.mtimeMs, size: file.size, bytes, value })
165
+ persistedSessionCacheBytes += bytes
166
+ evictPersistedSessions()
167
+ }
168
+
169
+ function isRecord(value: unknown): value is UnknownRecord {
170
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
171
+ }
172
+
173
+ function stringValue(value: unknown): string | undefined {
174
+ return typeof value === 'string' && value.length > 0 ? value : undefined
175
+ }
176
+
177
+ function numberValue(value: unknown): number | undefined {
178
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined
179
+ }
180
+
181
+ function boundedString(value: unknown, max = 500): string | undefined {
182
+ const text = stringValue(value)
183
+ return text ? text.slice(0, max) : undefined
184
+ }
185
+
186
+ function jsonText(value: unknown, max: number): string | undefined {
187
+ if (value === undefined) return undefined
188
+ if (typeof value === 'string') return value.slice(0, max)
189
+ try {
190
+ return JSON.stringify(value, null, 2).slice(0, max)
191
+ } catch {
192
+ return '[unserializable value]'
193
+ }
194
+ }
195
+
196
+ function sanitizeProjectName(value: string): string {
197
+ const sanitized = value
198
+ .toLowerCase()
199
+ .replace(/[^a-z0-9._-]+/g, '-')
200
+ .replace(/^-+|-+$/g, '')
201
+ .slice(0, 48)
202
+ return sanitized || 'project'
203
+ }
204
+
205
+ /** Mirrors pi-dynamic-workflows' project key without importing the extension. */
206
+ function workflowProjectKey(cwd: string): string {
207
+ const projectPath = resolve(cwd)
208
+ const projectName = sanitizeProjectName(basename(projectPath) || 'project')
209
+ const hash = createHash('sha256').update(projectPath).digest('hex').slice(0, 12)
210
+ return `${projectName}-${hash}`
211
+ }
212
+
213
+ /**
214
+ * Read-only workspace projection for workflow files of a project that is not
215
+ * registered in workspaces.json. `key` is optional: pass it for projects whose
216
+ * resolved cwd does not recompute to the wanted key (unresolved projects get a
217
+ * display-only path under the projects dir; the id still pins the real key).
218
+ */
219
+ export function workflowWorkspaceForPath(cwd: string, key?: string): Workspace {
220
+ const path = resolve(cwd)
221
+ const projectKey = key ?? workflowProjectKey(path)
222
+ const name = key ? slugFromProjectKey(key) : basename(path) || path
223
+ return {
224
+ id: `workflow-${projectKey}`,
225
+ name,
226
+ path,
227
+ createdAt: 0,
228
+ lastActiveAt: 0,
229
+ color: '#6b7280',
230
+ }
231
+ }
232
+
233
+ /** `pi-desktop-273acbe828fd` -> `pi-desktop`; passthrough when malformed. */
234
+ function slugFromProjectKey(key: string): string {
235
+ return key.length > 13 ? key.slice(0, key.length - 13) : key
236
+ }
237
+
238
+ /** The project key embedded in a `workflow-<key>` workspace id, or null. */
239
+ function workflowKeyFromId(workspaceId: string): string | null {
240
+ if (!workspaceId.startsWith('workflow-')) return null
241
+ const key = workspaceId.slice('workflow-'.length)
242
+ return WORKFLOW_PROJECT_KEY_RE.test(key) ? key : null
243
+ }
244
+
245
+ function workflowRunDirs(workspace: Workspace): string[] {
246
+ // A `workflow-<key>` projection pins its project key even when its path is
247
+ // display-only (unresolved projects); registered workspaces derive the key
248
+ // from their (possibly healed) path as before.
249
+ const key = workflowKeyFromId(workspace.id) ?? workflowProjectKey(workspace.path)
250
+ return [
251
+ join(WORKFLOW_PROJECTS_DIR, key, 'runs'),
252
+ join(resolve(workspace.path), '.pi', 'workflows', 'runs'),
253
+ ]
254
+ }
255
+
256
+ /** Root of Pi's session store; read per call because tests override the env. */
257
+ function piSessionsRoot(): string {
258
+ return join(process.env.PI_CODING_AGENT_DIR || join(homedir(), '.pi', 'agent'), 'sessions')
259
+ }
260
+
261
+ function workflowSessionsDir(cwd: string): string {
262
+ const resolvedCwd = resolve(cwd)
263
+ const safePath = `--${resolvedCwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`
264
+ return join(piSessionsRoot(), safePath)
265
+ }
266
+
267
+ // ─── Workflow project discovery ─────────────────────────────────────────────
268
+
269
+ /**
270
+ * Result of the bounded discovery walk over `~/.pi/workflows/projects`.
271
+ *
272
+ * `projects` maps every on-disk project key to its real cwd, or null when the
273
+ * cwd could not be recovered from the session store. `sessionDirCwds` maps
274
+ * sanitized session dir names to the header cwd of their newest session, so
275
+ * registered workspaces holding a lossy/phantom path can be repaired.
276
+ */
277
+ export interface WorkflowProjectDiscovery {
278
+ projects: Map<string, string | null>
279
+ sessionDirCwds: Map<string, string>
280
+ }
281
+
282
+ let projectDiscoveryCache: { cacheKey: string; value: WorkflowProjectDiscovery } | null = null
283
+
284
+ /** Test helper: drop the discovery cache between cases. */
285
+ export function clearWorkflowProjectDiscoveryCache(): void {
286
+ projectDiscoveryCache = null
287
+ }
288
+
289
+ /** `E:\Projects\AI\pi-desktop` -> `--E--Projects-AI-pi-desktop--`. */
290
+ function sessionDirKey(cwd: string): string {
291
+ return pathGroupKey(sanitizePath(cwd))
292
+ }
293
+
294
+ /**
295
+ * Bounded read of the session header's cwd: first complete line, first 4 KB.
296
+ * Never throws; null when the file is unreadable or the header lacks a cwd.
297
+ */
298
+ async function readSessionHeaderCwd(filePath: string): Promise<string | null> {
299
+ let handle
300
+ try {
301
+ handle = await open(filePath, 'r')
302
+ const buffer = Buffer.alloc(MAX_SESSION_HEADER_BYTES)
303
+ const { bytesRead } = await handle.read(buffer, 0, MAX_SESSION_HEADER_BYTES, 0)
304
+ const firstLine = buffer.subarray(0, bytesRead).toString('utf-8').split('\n', 1)[0]
305
+ const record: unknown = JSON.parse(firstLine)
306
+ return isRecord(record) && typeof record.cwd === 'string' && record.cwd.length > 0
307
+ ? record.cwd
308
+ : null
309
+ } catch {
310
+ return null
311
+ } finally {
312
+ await handle?.close()
313
+ }
314
+ }
315
+
316
+ /** sessionId (header uuid) from a run record, without parsing the whole file. */
317
+ async function sessionIdFromRunFile(filePath: string): Promise<string | null> {
318
+ try {
319
+ const file = await stat(filePath)
320
+ if (file.size <= 0 || file.size > MAX_RUN_FILE_BYTES) return null
321
+ const text = await readFile(filePath, 'utf8')
322
+ const match = /"sessionId"\s*:\s*"([^"]+)"/.exec(text)
323
+ return match?.[1] ?? null
324
+ } catch {
325
+ return null
326
+ }
327
+ }
328
+
329
+ /** All parent session files in one dir, newest first. */
330
+ async function sessionFilesNewestFirst(sessionDir: string): Promise<string[]> {
331
+ try {
332
+ const names = (await readdir(sessionDir)).filter((name) => name.endsWith('.jsonl'))
333
+ // Pi names parent sessions `<ISO-timestamp>_<uuid>.jsonl`; the fixed-width
334
+ // timestamp makes a descending sort the newest-first order with no stats.
335
+ return names.sort().reverse().map((name) => join(sessionDir, name))
336
+ } catch {
337
+ return []
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Discover every workflow project on disk and its real cwd.
343
+ *
344
+ * Never reconstructs a path from a sanitized session dir name (lossy). Instead:
345
+ * 1. `workflowProjectKey` of every known path (registered workspaces + launch
346
+ * cwd) is matched against the on-disk project keys;
347
+ * 2. one bounded pass over the session store reads the header cwd of the
348
+ * newest session per dir and verifies it recomputes to a project key;
349
+ * 3. unresolved keys fall back to their newest run JSONs' `sessionId`, which
350
+ * is matched against session filenames (`<timestamp>_<uuid>.jsonl`) and
351
+ * verified against the session header cwd.
352
+ *
353
+ * Every read is bounded (caps above), and the whole walk is cached on the
354
+ * projects + sessions directory mtimes, so the runs-list poll does not rescan
355
+ * session files (or huge run files) on every tick.
356
+ */
357
+ export async function discoverWorkflowProjects(
358
+ knownPaths: readonly string[] = []
359
+ ): Promise<WorkflowProjectDiscovery> {
360
+ const cacheKey = await discoveryCacheKey(knownPaths)
361
+ if (cacheKey === null) {
362
+ projectDiscoveryCache = null
363
+ return { projects: new Map(), sessionDirCwds: new Map() }
364
+ }
365
+ if (projectDiscoveryCache && projectDiscoveryCache.cacheKey === cacheKey) {
366
+ return projectDiscoveryCache.value
367
+ }
368
+
369
+ const projects = new Map<string, string | null>()
370
+ let projectDirs: string[]
371
+ try {
372
+ projectDirs = (await readdir(WORKFLOW_PROJECTS_DIR, { withFileTypes: true }))
373
+ .filter((entry) => entry.isDirectory() && WORKFLOW_PROJECT_KEY_RE.test(entry.name))
374
+ .slice(0, MAX_DISCOVERED_PROJECTS)
375
+ .map((entry) => entry.name)
376
+ } catch {
377
+ projectDirs = []
378
+ }
379
+
380
+ // (1) Known paths: exact key match, no reads. This is what keeps registered
381
+ // workspace ids authoritative — a registered path that resolves here is never
382
+ // shadowed by a projection.
383
+ for (const dir of projectDirs) {
384
+ projects.set(dir, null)
385
+ }
386
+ for (const known of knownPaths) {
387
+ const key = workflowProjectKey(known)
388
+ if (projects.has(key) && projects.get(key) === null) projects.set(key, resolve(known))
389
+ }
390
+
391
+ const sessionsRoot = piSessionsRoot()
392
+ const sessionDirCwds = new Map<string, string>()
393
+ const sessionUuidToFile = new Map<string, string>()
394
+ let sessionDirsScanned = 0
395
+ let filesIndexed = 0
396
+
397
+ // (2) One bounded pass over the session store.
398
+ let sessionDirNames: string[]
399
+ try {
400
+ sessionDirNames = (await readdir(sessionsRoot, { withFileTypes: true }))
401
+ .filter((entry) => entry.isDirectory())
402
+ .map((entry) => entry.name)
403
+ } catch {
404
+ sessionDirNames = []
405
+ }
406
+ for (const dirName of sessionDirNames) {
407
+ if (sessionDirsScanned >= MAX_SESSION_DIRS_SCANNED) break
408
+ sessionDirsScanned++
409
+ const sessionDir = join(sessionsRoot, dirName)
410
+ // One readdir per dir serves both the header scan and the uuid index.
411
+ const files = await sessionFilesNewestFirst(sessionDir)
412
+
413
+ const headerCwd = await readSessionHeaderCwd(files[0])
414
+ if (headerCwd) {
415
+ sessionDirCwds.set(pathGroupKey(dirName), headerCwd)
416
+ const key = workflowProjectKey(headerCwd)
417
+ if (projects.has(key) && projects.get(key) === null) projects.set(key, resolve(headerCwd))
418
+ }
419
+
420
+ // Index parent session uuids from filenames alone (no reads) for the
421
+ // run-sessionId fallback below.
422
+ for (const name of files) {
423
+ if (filesIndexed >= MAX_SESSION_FILES_INDEXED) break
424
+ const stem = basename(name, '.jsonl')
425
+ const underscore = stem.lastIndexOf('_')
426
+ if (underscore > 0 && underscore < stem.length - 1) {
427
+ sessionUuidToFile.set(stem.slice(underscore + 1), name)
428
+ filesIndexed++
429
+ }
430
+ }
431
+ }
432
+
433
+ // (3) Run-sessionId fallback for keys the header scan could not resolve.
434
+ for (const [key, cwd] of [...projects]) {
435
+ if (cwd !== null) continue
436
+ const runsDir = join(WORKFLOW_PROJECTS_DIR, key, 'runs')
437
+ let runFiles: string[]
438
+ try {
439
+ const names = (await readdir(runsDir)).filter((name) => name.endsWith('.json'))
440
+ const newest = await Promise.all(
441
+ names.map(async (name) => {
442
+ try {
443
+ return { name, mtimeMs: (await stat(join(runsDir, name))).mtimeMs }
444
+ } catch {
445
+ return null
446
+ }
447
+ })
448
+ )
449
+ runFiles = newest
450
+ .filter((entry): entry is { name: string; mtimeMs: number } => entry !== null)
451
+ .sort((a, b) => b.mtimeMs - a.mtimeMs)
452
+ .slice(0, MAX_RUNS_READ_PER_PROJECT)
453
+ .map((entry) => join(runsDir, entry.name))
454
+ } catch {
455
+ continue
456
+ }
457
+ for (const runFile of runFiles) {
458
+ const sessionId = await sessionIdFromRunFile(runFile)
459
+ if (!sessionId) continue
460
+ const sessionFile = sessionUuidToFile.get(sessionId)
461
+ if (!sessionFile) continue
462
+ const headerCwd = await readSessionHeaderCwd(sessionFile)
463
+ if (headerCwd && workflowProjectKey(headerCwd) === key) {
464
+ projects.set(key, resolve(headerCwd))
465
+ break
466
+ }
467
+ }
468
+ }
469
+
470
+ const value: WorkflowProjectDiscovery = { projects, sessionDirCwds }
471
+ projectDiscoveryCache = { cacheKey, value }
472
+ return value
473
+ }
474
+
475
+ /** mtime+size of a store root, or MISSING_DIR_STAMP when it does not exist. */
476
+ async function dirStamp(dir: string): Promise<string> {
477
+ try {
478
+ const info = await stat(dir)
479
+ return `${info.mtimeMs}:${info.size}`
480
+ } catch {
481
+ return MISSING_DIR_STAMP
482
+ }
483
+ }
484
+
485
+ /** mtime+size of both store roots; null when the projects dir is missing. */
486
+ async function discoveryCacheKey(knownPaths: readonly string[]): Promise<string | null> {
487
+ const [projects, sessions] = await Promise.all([
488
+ dirStamp(WORKFLOW_PROJECTS_DIR),
489
+ dirStamp(piSessionsRoot()),
490
+ ])
491
+ // Only the projects dir can null the key: without it there are no runs to
492
+ // project at all. A missing session store just costs the walk its cwd
493
+ // evidence (its readdir already yields an empty list), so the projections
494
+ // built from the run files must survive it.
495
+ if (projects === MISSING_DIR_STAMP) return null
496
+ // Known paths are cheap to fold in and change only with workspace state.
497
+ const known = knownPaths.map((p) => workflowProjectKey(p)).join(',')
498
+ return `${projects}:${sessions}:${known}`
499
+ }
500
+
501
+ /**
502
+ * The single workspace projection shared by workflow list, getRun and control.
503
+ *
504
+ * Registered workspaces keep their ids and (on-disk) paths. A registered path
505
+ * whose key has no project dir is checked against the session-store scan: if
506
+ * its sanitized session dir's headers point at a real project (the lossy-decode
507
+ * phantom case), the returned projection carries the healed real cwd in memory
508
+ * only — workspaces.json is never rewritten and no sidebar workspace is created.
509
+ * Unregistered projects get a read-only `workflow-<key>` projection (real cwd
510
+ * when discovered, display-only path otherwise) that never spawns a Pi manager.
511
+ */
512
+ export async function resolveWorkflowWorkspaces(
513
+ registered: readonly Workspace[]
514
+ ): Promise<Workspace[]> {
515
+ const knownPaths = [...registered.map((ws) => ws.path), process.cwd()]
516
+ const discovery = await discoverWorkflowProjects(knownPaths)
517
+ const out: Workspace[] = []
518
+ const coveredKeys = new Set<string>()
519
+
520
+ for (const ws of registered) {
521
+ const key = workflowProjectKey(ws.path)
522
+ coveredKeys.add(key)
523
+ let effective = ws
524
+ if (!discovery.projects.has(key)) {
525
+ // No runs at the registered path. If it is a phantom (lossy session-dir
526
+ // decode), the session headers reveal the real cwd of the project whose
527
+ // runs exist on disk — heal the projection in memory, keeping the id.
528
+ const headerCwd = discovery.sessionDirCwds.get(sessionDirKey(ws.path))
529
+ if (headerCwd && !pathsEqual(headerCwd, ws.path)) {
530
+ const healedKey = workflowProjectKey(headerCwd)
531
+ if (discovery.projects.has(healedKey)) {
532
+ effective = { ...ws, path: headerCwd, name: basename(headerCwd) || headerCwd }
533
+ coveredKeys.add(healedKey)
534
+ }
535
+ }
536
+ }
537
+ out.push(effective)
538
+ }
539
+
540
+ // Read-only projections for every project dir without a registered workspace.
541
+ for (const [key, cwd] of discovery.projects) {
542
+ if (coveredKeys.has(key)) continue
543
+ coveredKeys.add(key)
544
+ out.push(
545
+ cwd
546
+ ? workflowWorkspaceForPath(cwd)
547
+ : workflowWorkspaceForPath(join(WORKFLOW_PROJECTS_DIR, key), key)
548
+ )
549
+ }
550
+
551
+ // The launch cwd must stay visible even before its first run is persisted.
552
+ const launchKey = workflowProjectKey(process.cwd())
553
+ if (!coveredKeys.has(launchKey)) {
554
+ out.push(workflowWorkspaceForPath(process.cwd()))
555
+ }
556
+
557
+ return out
558
+ }
559
+
560
+ function toStatus(value: unknown): WorkflowRunStatus {
561
+ return typeof value === 'string' && RUN_STATUSES.has(value as WorkflowRunStatus)
562
+ ? (value as WorkflowRunStatus)
563
+ : 'unknown'
564
+ }
565
+
566
+ function toAgentStatus(value: unknown): WorkflowAgentStatus {
567
+ return typeof value === 'string' && AGENT_STATUSES.has(value as WorkflowAgentStatus)
568
+ ? (value as WorkflowAgentStatus)
569
+ : 'queued'
570
+ }
571
+
572
+ function toTokenUsage(value: unknown): WorkflowRunSummary['tokenUsage'] | undefined {
573
+ if (!isRecord(value)) return undefined
574
+ const input = numberValue(value.input)
575
+ const output = numberValue(value.output)
576
+ const total = numberValue(value.total)
577
+ if (input === undefined || output === undefined || total === undefined) return undefined
578
+ const cost = numberValue(value.cost)
579
+ const cacheRead = numberValue(value.cacheRead)
580
+ const cacheWrite = numberValue(value.cacheWrite)
581
+ return {
582
+ input,
583
+ output,
584
+ total,
585
+ ...(cost === undefined ? {} : { cost }),
586
+ ...(cacheRead === undefined ? {} : { cacheRead }),
587
+ ...(cacheWrite === undefined ? {} : { cacheWrite }),
588
+ }
589
+ }
590
+
591
+ function normalizeHistoryEntry(value: unknown): WorkflowHistoryEntry | null {
592
+ if (!isRecord(value)) return null
593
+ const text = boundedString(value.text, MAX_ENTRY_CHARS) ?? ''
594
+ const role = stringValue(value.role) ?? 'system'
595
+ const kind = stringValue(value.kind) ?? 'text'
596
+ const entry: WorkflowHistoryEntry = { role, kind, text }
597
+ const id = stringValue(value.id)
598
+ const timestamp = stringValue(value.timestamp)
599
+ const toolName = stringValue(value.toolName)
600
+ const path = stringValue(value.path)
601
+ const diff = boundedString(value.diff, MAX_ENTRY_CHARS)
602
+ if (id) entry.id = id
603
+ if (timestamp) entry.timestamp = timestamp
604
+ if (toolName) entry.toolName = toolName
605
+ if (path) entry.path = path
606
+ if (diff) entry.diff = diff
607
+ if (typeof value.isError === 'boolean') entry.isError = value.isError
608
+ return entry
609
+ }
610
+
611
+ function normalizeHistory(value: unknown): WorkflowHistoryEntry[] {
612
+ if (!Array.isArray(value)) return []
613
+ const result: WorkflowHistoryEntry[] = []
614
+ let chars = 0
615
+ for (const item of value.slice(-MAX_HISTORY_ENTRIES)) {
616
+ const entry = normalizeHistoryEntry(item)
617
+ if (!entry) continue
618
+ chars += entry.text.length + (entry.diff?.length ?? 0)
619
+ if (chars > MAX_TRANSCRIPT_CHARS) break
620
+ result.push(entry)
621
+ }
622
+ return result
623
+ }
624
+
625
+ function projectRun(workspace: Workspace, raw: UnknownRecord): WorkflowRunSummary | null {
626
+ const runId = stringValue(raw.runId)
627
+ const workflowName = stringValue(raw.workflowName)
628
+ const startedAt = stringValue(raw.startedAt)
629
+ const updatedAt = stringValue(raw.updatedAt)
630
+ if (!runId || !workflowName || !startedAt || !updatedAt) return null
631
+
632
+ const phases = Array.isArray(raw.phases)
633
+ ? raw.phases.filter((phase): phase is string => typeof phase === 'string')
634
+ : []
635
+ const agents = Array.isArray(raw.agents)
636
+ ? raw.agents.flatMap((value): WorkflowRunSummary['agents'] => {
637
+ if (!isRecord(value)) return []
638
+ const id = numberValue(value.id)
639
+ const label = stringValue(value.label)
640
+ if (id === undefined || !label) return []
641
+ const error = boundedString(value.error)
642
+ const resultPreview = boundedString(value.resultPreview, 4_000)
643
+ const tokens = numberValue(value.tokens)
644
+ const model = stringValue(value.model)
645
+ const started = stringValue(value.startedAt)
646
+ const ended = stringValue(value.endedAt)
647
+ const callId = stringValue(value.callId)
648
+ const errorCode = stringValue(value.errorCode)
649
+ const tokenUsage = toTokenUsage(value.tokenUsage)
650
+ return [{
651
+ id,
652
+ ...(callId ? { callId } : {}),
653
+ label,
654
+ ...(stringValue(value.phase) ? { phase: value.phase as string } : {}),
655
+ status: toAgentStatus(value.status),
656
+ ...(error ? { error } : {}),
657
+ ...(errorCode ? { errorCode } : {}),
658
+ ...(typeof value.recoverable === 'boolean' ? { recoverable: value.recoverable } : {}),
659
+ hasHistory: Array.isArray(value.history) && value.history.length > 0,
660
+ ...(resultPreview ? { resultPreview } : {}),
661
+ ...(tokens === undefined ? {} : { tokens }),
662
+ ...(model ? { model } : {}),
663
+ ...(started ? { startedAt: started } : {}),
664
+ ...(ended ? { endedAt: ended } : {}),
665
+ ...(tokenUsage ? { tokenUsage } : {}),
666
+ }]
667
+ })
668
+ : []
669
+ const tokenUsage = toTokenUsage(raw.tokenUsage)
670
+ const sessionId = stringValue(raw.sessionId)
671
+ const pauseReason = boundedString(raw.pauseReason)
672
+ const resetHint = boundedString(raw.resetHint)
673
+
674
+ return {
675
+ workspaceId: workspace.id,
676
+ workspaceName: workspace.name,
677
+ cwd: workspace.path,
678
+ runId,
679
+ workflowName,
680
+ ...(sessionId ? { sessionId } : {}),
681
+ status: toStatus(raw.status),
682
+ ...(pauseReason ? { pauseReason } : {}),
683
+ ...(resetHint ? { resetHint } : {}),
684
+ phases,
685
+ ...(stringValue(raw.currentPhase) ? { currentPhase: raw.currentPhase as string } : {}),
686
+ agents,
687
+ startedAt,
688
+ updatedAt,
689
+ ...(numberValue(raw.durationMs) === undefined ? {} : { durationMs: raw.durationMs as number }),
690
+ ...(tokenUsage ? { tokenUsage } : {}),
691
+ }
692
+ }
693
+
694
+ async function readJson(path: string): Promise<UnknownRecord | null> {
695
+ try {
696
+ const value: unknown = JSON.parse(await readFile(path, 'utf8'))
697
+ return isRecord(value) ? value : null
698
+ } catch {
699
+ return null
700
+ }
701
+ }
702
+
703
+ async function readRunFiles(dir: string): Promise<UnknownRecord[]> {
704
+ let names: string[]
705
+ try {
706
+ names = await readdir(dir)
707
+ } catch {
708
+ return []
709
+ }
710
+
711
+ const values = await Promise.all(names.filter((name) => name.endsWith('.json')).map(async (name) => {
712
+ const filePath = join(dir, name)
713
+ try {
714
+ const file = await stat(filePath)
715
+ const cached = runCache.get(filePath)
716
+ if (cached && cached.mtimeMs === file.mtimeMs && cached.size === file.size && cached.ino === file.ino) {
717
+ return cached.value
718
+ }
719
+ const value = await readJson(filePath)
720
+ if (value) runCache.set(filePath, { mtimeMs: file.mtimeMs, size: file.size, ino: file.ino, value })
721
+ else runCache.delete(filePath)
722
+ return value
723
+ } catch {
724
+ runCache.delete(filePath)
725
+ return null
726
+ }
727
+ }))
728
+ return values.filter((value): value is UnknownRecord => value !== null)
729
+ }
730
+
731
+ async function readRunRecord(workspace: Workspace, runId: string): Promise<UnknownRecord | null> {
732
+ if (!/^[a-zA-Z0-9._-]{1,200}$/.test(runId)) return null
733
+ for (const dir of workflowRunDirs(workspace)) {
734
+ const primary = await readJson(join(dir, `${runId}.json`))
735
+ if (primary) return primary
736
+ const backup = await readJson(join(dir, `${runId}.json.bak`))
737
+ if (backup) return backup
738
+ }
739
+ return null
740
+ }
741
+
742
+ function contentText(value: unknown): string {
743
+ if (typeof value === 'string') return value
744
+ if (!Array.isArray(value)) return ''
745
+ return value
746
+ .map((block) => {
747
+ if (!isRecord(block)) return ''
748
+ if (typeof block.text === 'string') return block.text
749
+ if (typeof block.thinking === 'string') return block.thinking
750
+ if (block.type === 'toolCall') return jsonText(block, MAX_ENTRY_CHARS) ?? ''
751
+ return ''
752
+ })
753
+ .filter(Boolean)
754
+ .join('\n')
755
+ }
756
+
757
+ /**
758
+ * The bounded transcript of one persisted session.
759
+ *
760
+ * `firstUserText` is kept apart from `history` on purpose: normalizeHistory
761
+ * retains only the newest MAX_HISTORY_ENTRIES entries, so on a long session the
762
+ * opening user message — the one carrying the agent prompt — is dropped, and
763
+ * with it the only evidence that identifies which agent this session belongs
764
+ * to. It is already bounded to MAX_ENTRY_CHARS by the loop below.
765
+ */
766
+ function sessionTranscript(entries: UnknownRecord[]): Omit<PersistedSessionMatch, 'sessionName'> {
767
+ const result: WorkflowHistoryEntry[] = []
768
+ for (const entry of entries) {
769
+ if (entry.type !== 'message' || !isRecord(entry.message)) continue
770
+ const message = entry.message
771
+ const role = stringValue(message.role) ?? 'system'
772
+ const timestamp = stringValue(entry.timestamp)
773
+ const content = Array.isArray(message.content) ? message.content : [message.content]
774
+ if (role === 'assistant') {
775
+ for (const block of content) {
776
+ if (!isRecord(block)) continue
777
+ const kind = block.type === 'thinking' ? 'thinking' : block.type === 'toolCall' ? 'toolCall' : 'text'
778
+ const text = block.type === 'toolCall'
779
+ ? jsonText(block, MAX_ENTRY_CHARS) ?? ''
780
+ : contentText([block])
781
+ if (text) result.push({
782
+ role,
783
+ kind,
784
+ text: text.slice(0, MAX_ENTRY_CHARS),
785
+ ...(timestamp ? { timestamp } : {}),
786
+ ...(stringValue(block.name) ? { toolName: block.name as string } : {}),
787
+ })
788
+ }
789
+ continue
790
+ }
791
+ const text = contentText(message.content).slice(0, MAX_ENTRY_CHARS)
792
+ if (!text) continue
793
+ result.push({
794
+ role,
795
+ kind: role === 'toolResult' ? 'toolResult' : 'text',
796
+ text,
797
+ ...(timestamp ? { timestamp } : {}),
798
+ ...(stringValue(message.toolName) ? { toolName: message.toolName as string } : {}),
799
+ ...(message.isError === true ? { isError: true } : {}),
800
+ })
801
+ }
802
+ return {
803
+ history: normalizeHistory(result),
804
+ firstUserText: result.find((entry) => entry.role === 'user')?.text ?? '',
805
+ }
806
+ }
807
+
808
+ interface PersistedSessionMatch {
809
+ history: WorkflowHistoryEntry[]
810
+ sessionName: string
811
+ /** Bounded text of the session's FIRST user message; '' when it has none. */
812
+ firstUserText: string
813
+ }
814
+
815
+ async function readPersistedSession(filePath: string): Promise<PersistedSessionMatch | null> {
816
+ let file
817
+ try {
818
+ file = await stat(filePath)
819
+ } catch {
820
+ return null
821
+ }
822
+ const cached = takeCachedSession(filePath)
823
+ if (cached && cached.mtimeMs === file.mtimeMs && cached.size === file.size) return cached.value
824
+ if (file.size > MAX_PERSISTED_SESSION_FILE_BYTES) {
825
+ cachePersistedSession(filePath, file, null)
826
+ return null
827
+ }
828
+
829
+ let lines: string[]
830
+ try {
831
+ lines = (await readFile(filePath, 'utf8')).split(/\r?\n/)
832
+ } catch {
833
+ return null
834
+ }
835
+ const entries: UnknownRecord[] = []
836
+ let sessionName: string | undefined
837
+ for (const line of lines) {
838
+ if (!line.trim()) continue
839
+ try {
840
+ const value: unknown = JSON.parse(line)
841
+ if (!isRecord(value)) continue
842
+ if (value.type === 'session_info') sessionName = stringValue(value.name)
843
+ entries.push(value)
844
+ } catch {
845
+ // A live session can end with a partial JSONL line; keep the readable prefix.
846
+ }
847
+ }
848
+ const value = sessionName ? { ...sessionTranscript(entries), sessionName } : null
849
+ cachePersistedSession(filePath, file, value)
850
+ return value
851
+ }
852
+
853
+ /**
854
+ * Does this session carry the agent's prompt? Both sides are bounded to the
855
+ * same scale: a PROMPT_MATCH_CHARS prefix of the prompt against text already
856
+ * truncated to MAX_ENTRY_CHARS. The opening user message is checked separately
857
+ * because the history window can no longer contain it (see sessionTranscript).
858
+ */
859
+ function sessionCarriesPrompt(match: PersistedSessionMatch, needle: string): boolean {
860
+ if (match.firstUserText.includes(needle)) return true
861
+ return match.history.some((entry) => entry.role === 'user' && entry.text.includes(needle))
862
+ }
863
+
864
+ interface SessionCandidate {
865
+ match: PersistedSessionMatch
866
+ labelMatch: boolean
867
+ promptMatch: boolean
868
+ }
869
+
870
+ /** Warned session dirs, so a per-agent scan cannot flood the log each poll. */
871
+ const truncationWarnedDirs = new Set<string>()
872
+
873
+ function warnScanTruncated(sessionDir: string, available: number): void {
874
+ if (truncationWarnedDirs.has(sessionDir)) return
875
+ // Bounded like every other set here; a reset only means the dirs get warned
876
+ // about once more.
877
+ if (truncationWarnedDirs.size >= MAX_SESSION_DIRS_SCANNED) truncationWarnedDirs.clear()
878
+ truncationWarnedDirs.add(sessionDir)
879
+ appLog.warn(
880
+ LOG_SCOPE,
881
+ `Transcript scan covered the ${MAX_TRANSCRIPT_FILES_SCANNED} newest of ${available} session files`,
882
+ { sessionDir, available, scanned: MAX_TRANSCRIPT_FILES_SCANNED }
883
+ )
884
+ }
885
+
886
+ /**
887
+ * The persisted session that belongs to ONE agent of a run, or null when the
888
+ * evidence does not single one out. `agentCount` is the number of agents in the
889
+ * run: it is what makes the last-resort tier safe, because a session that no
890
+ * label and no prompt claims belongs to one of the other agents.
891
+ */
892
+ async function findPersistedSession(
893
+ cwd: string,
894
+ runId: string,
895
+ agent: UnknownRecord,
896
+ agentCount: number
897
+ ): Promise<PersistedSessionMatch | null> {
898
+ const sessionDir = workflowSessionsDir(cwd)
899
+ // Newest first, so the sessions of a recent run are always inside the cap.
900
+ const files = await sessionFilesNewestFirst(sessionDir)
901
+ const scanned = files.slice(0, MAX_TRANSCRIPT_FILES_SCANNED)
902
+ if (files.length > scanned.length) warnScanTruncated(sessionDir, files.length)
903
+
904
+ const expected = `workflow:${runId} `
905
+ const label = stringValue(agent.label)
906
+ const promptNeedle = stringValue(agent.prompt)?.slice(0, PROMPT_MATCH_CHARS)
907
+ const candidates: SessionCandidate[] = []
908
+ for (const file of scanned) {
909
+ const match = await readPersistedSession(file)
910
+ if (!match || !match.sessionName.startsWith(expected)) continue
911
+ const suffix = match.sessionName.slice(expected.length).trim()
912
+ candidates.push({
913
+ match,
914
+ // Exact only: an agent labelled `build` must never claim the transcript
915
+ // of `build extras`.
916
+ labelMatch: !!label && suffix === label,
917
+ promptMatch: !!promptNeedle && sessionCarriesPrompt(match, promptNeedle),
918
+ })
919
+ }
920
+
921
+ // Never use readdir order to assign a transcript to an agent. A persisted
922
+ // session is safe only when its label, prompt, or unique run prefix selects it.
923
+ const labelMatches = candidates.filter((candidate) => candidate.labelMatch)
924
+ if (labelMatches.length === 1) return labelMatches[0].match
925
+ // Two agents can share a label (or one label can prefix another), which makes
926
+ // the label tier useless rather than conclusive: let the prompt decide, first
927
+ // inside the tied set and then across the whole run.
928
+ const tiedByPrompt = labelMatches.filter((candidate) => candidate.promptMatch)
929
+ if (tiedByPrompt.length === 1) return tiedByPrompt[0].match
930
+ const promptMatches = candidates.filter((candidate) => candidate.promptMatch)
931
+ if (promptMatches.length === 1) return promptMatches[0].match
932
+ // Last resort: the lone session of a single-agent run is unambiguous however
933
+ // it was named. With more agents in the run an unclaimed session belongs to
934
+ // one of the others, so it stays unassigned.
935
+ return agentCount === 1 && candidates.length === 1 ? candidates[0].match : null
936
+ }
937
+
938
+ async function projectAgentDetail(
939
+ workspace: Workspace,
940
+ runId: string,
941
+ value: UnknownRecord,
942
+ summary: WorkflowAgentSummary,
943
+ agentCount: number
944
+ ): Promise<WorkflowAgentDetail> {
945
+ const runHistory = normalizeHistory(value.history)
946
+ const persisted = await findPersistedSession(workspace.path, runId, value, agentCount)
947
+ const history = persisted?.history.length ? persisted.history : runHistory
948
+ const resultText = jsonText(value.result ?? value.resultPreview, MAX_ENTRY_CHARS)
949
+ const prompt = stringValue(value.prompt)
950
+ return {
951
+ ...summary,
952
+ ...(prompt ? { prompt: prompt.slice(0, MAX_ENTRY_CHARS) } : {}),
953
+ ...(resultText ? { resultText } : {}),
954
+ history,
955
+ transcriptSource: persisted?.history.length ? 'persisted-session' : runHistory.length ? 'run-history' : 'none',
956
+ transcriptComplete: !!persisted?.history.length,
957
+ }
958
+ }
959
+
960
+ async function readWorkspaceRuns(workspace: Workspace): Promise<WorkflowRunSummary[]> {
961
+ const byId = new Map<string, WorkflowRunSummary>()
962
+ // Primary storage wins over the legacy project-local directory if both exist.
963
+ for (const dir of workflowRunDirs(workspace)) {
964
+ for (const raw of await readRunFiles(dir)) {
965
+ const run = projectRun(workspace, raw)
966
+ if (run && !byId.has(run.runId)) byId.set(run.runId, run)
967
+ }
968
+ }
969
+ return [...byId.values()]
970
+ }
971
+
972
+ export async function listWorkflowRuns(workspaces: Workspace[]): Promise<WorkflowRunSummary[]> {
973
+ const runs = (await Promise.all(workspaces.map(readWorkspaceRuns))).flat()
974
+ return runs.sort((a, b) => {
975
+ const time = (value: string): number => Date.parse(value) || 0
976
+ return time(b.updatedAt) - time(a.updatedAt)
977
+ })
978
+ }
979
+
980
+ export async function setWorkflowPersistence(enabled: boolean): Promise<void> {
981
+ let existing: UnknownRecord = {}
982
+ try {
983
+ const value: unknown = JSON.parse(await readFile(WORKFLOW_SETTINGS_PATH, 'utf8'))
984
+ if (isRecord(value)) existing = value
985
+ } catch {
986
+ // Missing or malformed settings are replaced with the one safe preference.
987
+ }
988
+ await mkdir(WORKFLOW_HOME, { recursive: true })
989
+ const tempPath = `${WORKFLOW_SETTINGS_PATH}.tmp`
990
+ await writeFile(tempPath, `${JSON.stringify({ ...existing, persistAgentSessions: enabled }, null, 2)}\n`, 'utf8')
991
+ await rename(tempPath, WORKFLOW_SETTINGS_PATH)
992
+ }
993
+
994
+ export async function getWorkflowRun(workspace: Workspace, runId: string): Promise<WorkflowRunDetail | null> {
995
+ const raw = await readRunRecord(workspace, runId)
996
+ if (!raw) return null
997
+ const summary = projectRun(workspace, raw)
998
+ if (!summary) return null
999
+ const rawAgents = Array.isArray(raw.agents) ? raw.agents.filter(isRecord) : []
1000
+ const agents = await Promise.all(rawAgents.map(async (agent) => {
1001
+ const id = numberValue(agent.id)
1002
+ const fallback = summary.agents.find((candidate) => candidate.id === id)
1003
+ if (id === undefined || !fallback) return null
1004
+ return projectAgentDetail(workspace, runId, agent, fallback, rawAgents.length)
1005
+ }))
1006
+ const logs = Array.isArray(raw.logs)
1007
+ ? raw.logs
1008
+ .filter((line): line is string => typeof line === 'string')
1009
+ .slice(-MAX_LOG_LINES)
1010
+ .map((line) => line.slice(0, MAX_LOG_CHARS))
1011
+ : []
1012
+ return {
1013
+ ...summary,
1014
+ ...(boundedString(raw.script, MAX_SCRIPT_CHARS) ? { script: boundedString(raw.script, MAX_SCRIPT_CHARS) } : {}),
1015
+ ...(raw.args === undefined ? {} : { argsText: jsonText(raw.args, MAX_ENTRY_CHARS) }),
1016
+ ...(raw.result === undefined ? {} : { resultText: jsonText(raw.result, MAX_TRANSCRIPT_CHARS) }),
1017
+ logs,
1018
+ agents: agents.filter((agent): agent is WorkflowAgentDetail => agent !== null),
1019
+ }
1020
+ }