@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.
- package/CHANGELOG.md +56 -0
- package/apps/README.md +4 -0
- package/apps/hlidskjalf/README.md +68 -0
- package/apps/hlidskjalf/android/app/build.gradle +73 -0
- package/apps/hlidskjalf/android/app/capacitor.build.gradle +19 -0
- package/apps/hlidskjalf/android/app/proguard-rules.pro +21 -0
- package/apps/hlidskjalf/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
- package/apps/hlidskjalf/android/app/src/main/AndroidManifest.xml +41 -0
- package/apps/hlidskjalf/android/app/src/main/java/org/zerwiz/ymir/MainActivity.java +5 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
- package/apps/hlidskjalf/android/app/src/main/res/layout/activity_main.xml +12 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/apps/hlidskjalf/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
- package/apps/hlidskjalf/android/app/src/main/res/values/strings.xml +7 -0
- package/apps/hlidskjalf/android/app/src/main/res/values/styles.xml +22 -0
- package/apps/hlidskjalf/android/app/src/main/res/xml/file_paths.xml +5 -0
- package/apps/hlidskjalf/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
- package/apps/hlidskjalf/android/build.gradle +29 -0
- package/apps/hlidskjalf/android/capacitor.settings.gradle +3 -0
- package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/apps/hlidskjalf/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/apps/hlidskjalf/android/gradle.properties +22 -0
- package/apps/hlidskjalf/android/gradlew +248 -0
- package/apps/hlidskjalf/android/gradlew.bat +92 -0
- package/apps/hlidskjalf/android/settings.gradle +5 -0
- package/apps/hlidskjalf/android/variables.gradle +16 -0
- package/apps/hlidskjalf/capacitor.config.ts +26 -0
- package/apps/hlidskjalf/dist/apple-touch-icon.png +0 -0
- package/apps/hlidskjalf/dist/assets/index-BWmNIyaG.css +1 -0
- package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js +88 -0
- package/apps/hlidskjalf/dist/assets/index-k7QEfNto.js.map +1 -0
- package/apps/hlidskjalf/dist/icon.svg +7 -0
- package/apps/hlidskjalf/dist/index.html +64 -0
- package/apps/hlidskjalf/dist/manifest.webmanifest +31 -0
- package/apps/hlidskjalf/dist/og.jpg +0 -0
- package/apps/hlidskjalf/dist/og.png +0 -0
- package/apps/hlidskjalf/dist/og.svg +64 -0
- package/apps/hlidskjalf/dist/sw.js +39 -0
- package/apps/hlidskjalf/dist/ymir-icon.png +0 -0
- package/apps/hlidskjalf/dist/ymir-mark.svg +62 -0
- package/apps/hlidskjalf/electron/icon.png +0 -0
- package/apps/hlidskjalf/electron/main.cjs +236 -0
- package/apps/hlidskjalf/electron/preload.cjs +8 -0
- package/apps/hlidskjalf/electron/smidja-icon.png +0 -0
- package/apps/hlidskjalf/electron/ymir-hlidskjalf.desktop.in +12 -0
- package/apps/hlidskjalf/electron/ymir-smidja.desktop.in +12 -0
- package/apps/hlidskjalf/index.html +63 -0
- package/apps/hlidskjalf/package-lock.json +5398 -0
- package/apps/hlidskjalf/package.json +55 -0
- package/apps/hlidskjalf/public/apple-touch-icon.png +0 -0
- package/apps/hlidskjalf/public/icon.svg +7 -0
- package/apps/hlidskjalf/public/manifest.webmanifest +31 -0
- package/apps/hlidskjalf/public/og.jpg +0 -0
- package/apps/hlidskjalf/public/og.png +0 -0
- package/apps/hlidskjalf/public/og.svg +64 -0
- package/apps/hlidskjalf/public/sw.js +39 -0
- package/apps/hlidskjalf/public/ymir-icon.png +0 -0
- package/apps/hlidskjalf/public/ymir-mark.svg +62 -0
- package/apps/hlidskjalf/server/accounts.ts +159 -0
- package/apps/hlidskjalf/server/index.ts +2045 -0
- package/apps/hlidskjalf/src/app/App.tsx +116 -0
- package/apps/hlidskjalf/src/app/BottomStream.tsx +128 -0
- package/apps/hlidskjalf/src/app/MobileNav.tsx +87 -0
- package/apps/hlidskjalf/src/app/Rail.tsx +106 -0
- package/apps/hlidskjalf/src/app/Shell.tsx +135 -0
- package/apps/hlidskjalf/src/app/Topbar.tsx +130 -0
- package/apps/hlidskjalf/src/components/AccentPicker.tsx +207 -0
- package/apps/hlidskjalf/src/components/AccountMenu.tsx +115 -0
- package/apps/hlidskjalf/src/components/AgentCard.tsx +75 -0
- package/apps/hlidskjalf/src/components/EmberBackground.tsx +36 -0
- package/apps/hlidskjalf/src/components/Emblem.tsx +48 -0
- package/apps/hlidskjalf/src/components/Halls.tsx +155 -0
- package/apps/hlidskjalf/src/components/LoginModal.tsx +329 -0
- package/apps/hlidskjalf/src/components/Markdown.tsx +10 -0
- package/apps/hlidskjalf/src/components/MetricTile.tsx +40 -0
- package/apps/hlidskjalf/src/components/ModelPicker.tsx +100 -0
- package/apps/hlidskjalf/src/components/Overlay.tsx +134 -0
- package/apps/hlidskjalf/src/components/PRCard.tsx +154 -0
- package/apps/hlidskjalf/src/components/RecallPanel.tsx +116 -0
- package/apps/hlidskjalf/src/components/RuneTag.tsx +23 -0
- package/apps/hlidskjalf/src/components/Status.tsx +35 -0
- package/apps/hlidskjalf/src/components/TaskChip.tsx +23 -0
- package/apps/hlidskjalf/src/components/TraceRow.tsx +28 -0
- package/apps/hlidskjalf/src/data/lore.ts +127 -0
- package/apps/hlidskjalf/src/data/metadata.ts +147 -0
- package/apps/hlidskjalf/src/data/mythology.ts +95 -0
- package/apps/hlidskjalf/src/data/realms.ts +167 -0
- package/apps/hlidskjalf/src/gates/Cron.tsx +77 -0
- package/apps/hlidskjalf/src/gates/Decisions.tsx +67 -0
- package/apps/hlidskjalf/src/gates/Files.tsx +138 -0
- package/apps/hlidskjalf/src/gates/Fleet.tsx +184 -0
- package/apps/hlidskjalf/src/gates/Forge.tsx +486 -0
- package/apps/hlidskjalf/src/gates/OmniChat.tsx +217 -0
- package/apps/hlidskjalf/src/gates/Processes.tsx +162 -0
- package/apps/hlidskjalf/src/gates/Profile.tsx +225 -0
- package/apps/hlidskjalf/src/gates/Reviews.tsx +33 -0
- package/apps/hlidskjalf/src/gates/Runes.tsx +116 -0
- package/apps/hlidskjalf/src/gates/Runtime.tsx +106 -0
- package/apps/hlidskjalf/src/gates/Sessions.tsx +109 -0
- package/apps/hlidskjalf/src/gates/Stats.tsx +268 -0
- package/apps/hlidskjalf/src/gates/Tasks.tsx +173 -0
- package/apps/hlidskjalf/src/gates/Trace.tsx +155 -0
- package/apps/hlidskjalf/src/gates/Well.tsx +154 -0
- package/apps/hlidskjalf/src/gates/Worktrees.tsx +48 -0
- package/apps/hlidskjalf/src/hooks/useTenantDef.ts +13 -0
- package/apps/hlidskjalf/src/main.tsx +31 -0
- package/apps/hlidskjalf/src/services/api.ts +404 -0
- package/apps/hlidskjalf/src/services/auth.ts +107 -0
- package/apps/hlidskjalf/src/services/stream.ts +35 -0
- package/apps/hlidskjalf/src/state/store.ts +648 -0
- package/apps/hlidskjalf/src/state/ui.ts +62 -0
- package/apps/hlidskjalf/src/styles/auth.css +485 -0
- package/apps/hlidskjalf/src/styles/components.css +1259 -0
- package/apps/hlidskjalf/src/styles/forge.css +115 -0
- package/apps/hlidskjalf/src/styles/global.css +167 -0
- package/apps/hlidskjalf/src/styles/menus.css +175 -0
- package/apps/hlidskjalf/src/styles/mobile.css +298 -0
- package/apps/hlidskjalf/src/styles/overlays.css +205 -0
- package/apps/hlidskjalf/src/styles/shell.css +641 -0
- package/apps/hlidskjalf/src/types.ts +251 -0
- package/apps/hlidskjalf/src/vite-env.d.ts +9 -0
- package/apps/hlidskjalf/static/landing.md +6 -0
- package/apps/hlidskjalf/tsconfig.json +25 -0
- package/apps/hlidskjalf/vite.config.ts +49 -0
- package/apps/hlidskjalf-mobile/App.tsx +93 -0
- package/apps/hlidskjalf-mobile/app.json +13 -0
- package/apps/hlidskjalf-mobile/index.ts +4 -0
- package/apps/hlidskjalf-mobile/package.json +35 -0
- package/apps/hlidskjalf-mobile/public/icon.svg +7 -0
- package/apps/hlidskjalf-mobile/tsconfig.json +4 -0
- package/apps/odrerir/.astro/content.d.ts +159 -0
- package/apps/odrerir/.astro/dev.json +24 -0
- package/apps/odrerir/.astro/settings.json +5 -0
- package/apps/odrerir/.astro/types.d.ts +2 -0
- package/apps/odrerir/astro.config.mjs +79 -0
- package/apps/odrerir/dist/apple-touch-icon.png +0 -0
- package/apps/odrerir/dist/favicon-192.png +0 -0
- package/apps/odrerir/dist/favicon-32.png +0 -0
- package/apps/odrerir/dist/favicon-512.png +0 -0
- package/apps/odrerir/dist/favicon.svg +53 -0
- package/apps/odrerir/dist/icon.svg +7 -0
- package/apps/odrerir/dist/index.html +837 -0
- package/apps/odrerir/dist/og-square.png +0 -0
- package/apps/odrerir/dist/og-story.png +0 -0
- package/apps/odrerir/dist/og.png +0 -0
- package/apps/odrerir/dist/safari-pinned-tab.svg +7 -0
- package/apps/odrerir/dist/site.webmanifest +16 -0
- package/apps/odrerir/dist/styles/cloth.css +112 -0
- package/apps/odrerir/dist/styles/livehall.css +278 -0
- package/apps/odrerir/electron/icon.png +0 -0
- package/apps/odrerir/electron/main.cjs +183 -0
- package/apps/odrerir/electron/ymir-odrerir.desktop.in +12 -0
- package/apps/odrerir/package-lock.json +4195 -0
- package/apps/odrerir/package.json +30 -0
- package/apps/odrerir/public/apple-touch-icon.png +0 -0
- package/apps/odrerir/public/favicon-192.png +0 -0
- package/apps/odrerir/public/favicon-32.png +0 -0
- package/apps/odrerir/public/favicon-512.png +0 -0
- package/apps/odrerir/public/favicon.svg +53 -0
- package/apps/odrerir/public/icon.svg +7 -0
- package/apps/odrerir/public/og-square.png +0 -0
- package/apps/odrerir/public/og-story.png +0 -0
- package/apps/odrerir/public/og.png +0 -0
- package/apps/odrerir/public/safari-pinned-tab.svg +7 -0
- package/apps/odrerir/public/site.webmanifest +16 -0
- package/apps/odrerir/scripts/extract-cloth.mjs +101 -0
- package/apps/odrerir/src/index.html +837 -0
- package/apps/odrerir/src/styles/cloth.css +112 -0
- package/apps/odrerir/src/styles/livehall.css +278 -0
- package/apps/sessrumnir/.github/ISSUE_TEMPLATE/bug_report.yml +99 -0
- package/apps/sessrumnir/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/apps/sessrumnir/.github/ISSUE_TEMPLATE/feature_request.yml +46 -0
- package/apps/sessrumnir/.github/PULL_REQUEST_TEMPLATE.md +56 -0
- package/apps/sessrumnir/.github/SUPPORT.md +18 -0
- package/apps/sessrumnir/.github/workflows/build.yml +133 -0
- package/apps/sessrumnir/AGENTS.md +540 -0
- package/apps/sessrumnir/CLA.md +63 -0
- package/apps/sessrumnir/CONTRIBUTING.md +233 -0
- package/apps/sessrumnir/LICENSE +200 -0
- package/apps/sessrumnir/NOTICE +33 -0
- package/apps/sessrumnir/README.md +329 -0
- package/apps/sessrumnir/bin/sessrumnir-web.sh +117 -0
- package/apps/sessrumnir/bin/sessrumnir.js +169 -0
- package/apps/sessrumnir/docs/CNAME +1 -0
- package/apps/sessrumnir/docs/favicon.ico +0 -0
- package/apps/sessrumnir/docs/icon-512.png +0 -0
- package/apps/sessrumnir/docs/index.html +441 -0
- package/apps/sessrumnir/docs/robots.txt +4 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_181929.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182005.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182020.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182039.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182117.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182225.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182248.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182322.png +0 -0
- package/apps/sessrumnir/docs/screenshots/Screenshot_20260824_182340.png +0 -0
- package/apps/sessrumnir/docs/sitemap.xml +9 -0
- package/apps/sessrumnir/electron.vite.config.ts +49 -0
- package/apps/sessrumnir/eslint.config.mjs +75 -0
- package/apps/sessrumnir/i18next.config.ts +33 -0
- package/apps/sessrumnir/install.sh +144 -0
- package/apps/sessrumnir/out/main/index.js +10881 -0
- package/apps/sessrumnir/out/preload/index.js +722 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-C8QS47vb.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-400-normal-D3EsxgFc.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-B7dJQtg-.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-500-normal-BLlg2W5x.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-BcIsDXzC.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-600-normal-CBymlcNF.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-Bgrpe4p1.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-400-normal-BlcaxZtM.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-CdQuyvtc.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-500-normal-pZw22qtS.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-Dr-wLyOm.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-cyrillic-ext-600-normal-dLR2-a_v.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-BGH8Vunh.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-400-normal-C3_-2Ua-.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-Dj3SQ6fR.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-500-normal-EBdSCOD3.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-B254Xisw.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-600-normal-DUW-TI76.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuQ88yz3.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-400-normal-DuXFa1Dr.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-AH9qog1s.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-500-normal-DAuUCO41.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-CD-dlDpD.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-latin-ext-600-normal-D3T6BBXG.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-C-RiYxEf.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-400-normal-DmUuA7Y2.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-DsPuwQHi.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-500-normal-tGBW_mI7.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-Dar_cCT6.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/cormorant-vietnamese-600-normal-GIs8CF2x.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-Ba-HN6uq.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-300-normal-ByB2hQUF.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-B8O3uHbr.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-300-normal-BbtBtHoB.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BPU54Tw3.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-300-normal-BRS-C4KA.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-BEtQ2wm2.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-300-normal-CQbCaAQ4.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-B6fSpYYV.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-300-normal-CpdbWO_2.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/apps/sessrumnir/out/renderer/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/index-BxqsRq2x.css +4858 -0
- package/apps/sessrumnir/out/renderer/assets/index-TaD69rFk.js +99435 -0
- package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-italic-DFS986qL.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-latin-ext-wght-normal-C-3rgBeH.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-italic-Bxi8ein9.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-latin-wght-normal-CCVVNp6i.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-italic-xAqemagA.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/newsreader-vietnamese-wght-normal-Czsa-EzN.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/openmoji-color-9qo_2DLG.woff2 +0 -0
- package/apps/sessrumnir/out/renderer/assets/pi-logo-B6SVnhuS.svg +7 -0
- package/apps/sessrumnir/out/renderer/index.html +19 -0
- package/apps/sessrumnir/out/renderer/theme-boot.js +30 -0
- package/apps/sessrumnir/package-lock.json +10636 -0
- package/apps/sessrumnir/package.json +183 -0
- package/apps/sessrumnir/public/apple-touch-icon.png +0 -0
- package/apps/sessrumnir/public/icon.svg +7 -0
- package/apps/sessrumnir/resources/icons/generate_icons.py +38 -0
- package/apps/sessrumnir/resources/icons/icon-128.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-16.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-256.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-32.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-48.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-512.png +0 -0
- package/apps/sessrumnir/resources/icons/icon-64.png +0 -0
- package/apps/sessrumnir/resources/icons/icon.icns +0 -0
- package/apps/sessrumnir/resources/icons/icon.ico +0 -0
- package/apps/sessrumnir/resources/icons/icon.png +0 -0
- package/apps/sessrumnir/resources/icons/icon.svg +7 -0
- package/apps/sessrumnir/resources/linux/after-install.sh +23 -0
- package/apps/sessrumnir/resources/linux/after-remove.sh +18 -0
- package/apps/sessrumnir/resources/locales/en/translation.json +1735 -0
- package/apps/sessrumnir/resources/permission-prompt-text.test.ts +111 -0
- package/apps/sessrumnir/resources/permission-prompt-text.ts +84 -0
- package/apps/sessrumnir/resources/permission-rules.test.ts +351 -0
- package/apps/sessrumnir/resources/permission-rules.ts +293 -0
- package/apps/sessrumnir/resources/pi-desktop-permissions.ts +62 -0
- package/apps/sessrumnir/resources/ymir-sessrumnir.desktop.in +12 -0
- package/apps/sessrumnir/scripts/check-semantic-colors.mjs +57 -0
- package/apps/sessrumnir/src/main/activity-stats.test.ts +240 -0
- package/apps/sessrumnir/src/main/activity-stats.ts +596 -0
- package/apps/sessrumnir/src/main/agent-detection.test.ts +25 -0
- package/apps/sessrumnir/src/main/agent-detection.ts +114 -0
- package/apps/sessrumnir/src/main/app-data-paths.test.ts +90 -0
- package/apps/sessrumnir/src/main/app-data-paths.ts +95 -0
- package/apps/sessrumnir/src/main/app-log.test.ts +114 -0
- package/apps/sessrumnir/src/main/app-log.ts +210 -0
- package/apps/sessrumnir/src/main/archived-sessions.test.ts +50 -0
- package/apps/sessrumnir/src/main/archived-sessions.ts +95 -0
- package/apps/sessrumnir/src/main/attachment-reader.test.ts +56 -0
- package/apps/sessrumnir/src/main/attachment-reader.ts +52 -0
- package/apps/sessrumnir/src/main/auto-tag.test.ts +38 -0
- package/apps/sessrumnir/src/main/auto-tag.ts +75 -0
- package/apps/sessrumnir/src/main/autostart-linux.test.ts +56 -0
- package/apps/sessrumnir/src/main/autostart-linux.ts +58 -0
- package/apps/sessrumnir/src/main/cmd-escape.test.ts +424 -0
- package/apps/sessrumnir/src/main/cmd-escape.ts +186 -0
- package/apps/sessrumnir/src/main/council-manager.test.ts +167 -0
- package/apps/sessrumnir/src/main/council-manager.ts +265 -0
- package/apps/sessrumnir/src/main/diagnostics-report.test.ts +140 -0
- package/apps/sessrumnir/src/main/diagnostics-report.ts +91 -0
- package/apps/sessrumnir/src/main/diagnostics.ts +111 -0
- package/apps/sessrumnir/src/main/editor-guard.test.ts +59 -0
- package/apps/sessrumnir/src/main/editor-guard.ts +50 -0
- package/apps/sessrumnir/src/main/extension-ui-ipc.test.ts +299 -0
- package/apps/sessrumnir/src/main/extension-ui-ipc.ts +117 -0
- package/apps/sessrumnir/src/main/file-service.test.ts +279 -0
- package/apps/sessrumnir/src/main/file-service.ts +652 -0
- package/apps/sessrumnir/src/main/fs-errors.ts +42 -0
- package/apps/sessrumnir/src/main/get-messages-trim.test.ts +77 -0
- package/apps/sessrumnir/src/main/get-messages-trim.ts +97 -0
- package/apps/sessrumnir/src/main/git-conveyor.test.ts +305 -0
- package/apps/sessrumnir/src/main/git-conveyor.ts +338 -0
- package/apps/sessrumnir/src/main/git-worktree.test.ts +101 -0
- package/apps/sessrumnir/src/main/git-worktree.ts +201 -0
- package/apps/sessrumnir/src/main/i18n.ts +31 -0
- package/apps/sessrumnir/src/main/index.ts +619 -0
- package/apps/sessrumnir/src/main/ipc/active-engine.ts +15 -0
- package/apps/sessrumnir/src/main/ipc/context.ts +58 -0
- package/apps/sessrumnir/src/main/ipc/council-handlers.ts +126 -0
- package/apps/sessrumnir/src/main/ipc/diagnostics-handlers.ts +8 -0
- package/apps/sessrumnir/src/main/ipc/file-handlers.ts +96 -0
- package/apps/sessrumnir/src/main/ipc/git-conveyor-handlers.ts +55 -0
- package/apps/sessrumnir/src/main/ipc/model-handlers.ts +33 -0
- package/apps/sessrumnir/src/main/ipc/models-config-handlers.ts +80 -0
- package/apps/sessrumnir/src/main/ipc/notes-handlers.ts +65 -0
- package/apps/sessrumnir/src/main/ipc/package-handlers.ts +330 -0
- package/apps/sessrumnir/src/main/ipc/permission-rules-handlers.ts +223 -0
- package/apps/sessrumnir/src/main/ipc/pi-handlers.ts +136 -0
- package/apps/sessrumnir/src/main/ipc/pi-start-options.ts +150 -0
- package/apps/sessrumnir/src/main/ipc/run-pi-cli.ts +53 -0
- package/apps/sessrumnir/src/main/ipc/session-handlers.ts +491 -0
- package/apps/sessrumnir/src/main/ipc/settings.ts +120 -0
- package/apps/sessrumnir/src/main/ipc/skills-mcp-handlers.ts +192 -0
- package/apps/sessrumnir/src/main/ipc/system-handlers.ts +131 -0
- package/apps/sessrumnir/src/main/ipc/tag-handlers.ts +69 -0
- package/apps/sessrumnir/src/main/ipc/terminal-handlers.ts +41 -0
- package/apps/sessrumnir/src/main/ipc/theme-handlers.ts +131 -0
- package/apps/sessrumnir/src/main/ipc/update-handlers.ts +61 -0
- package/apps/sessrumnir/src/main/ipc/validation.ts +45 -0
- package/apps/sessrumnir/src/main/ipc/workflow-handlers.ts +108 -0
- package/apps/sessrumnir/src/main/ipc/workspace-activity-wiring.ts +179 -0
- package/apps/sessrumnir/src/main/ipc/workspace-handlers.ts +133 -0
- package/apps/sessrumnir/src/main/ipc-handlers.ts +98 -0
- package/apps/sessrumnir/src/main/map-concurrent.test.ts +80 -0
- package/apps/sessrumnir/src/main/map-concurrent.ts +30 -0
- package/apps/sessrumnir/src/main/models-file.test.ts +114 -0
- package/apps/sessrumnir/src/main/models-file.ts +89 -0
- package/apps/sessrumnir/src/main/notes-manager.test.ts +64 -0
- package/apps/sessrumnir/src/main/notes-manager.ts +183 -0
- package/apps/sessrumnir/src/main/notify-decision.test.ts +39 -0
- package/apps/sessrumnir/src/main/notify-decision.ts +25 -0
- package/apps/sessrumnir/src/main/omp-fork-points.test.ts +119 -0
- package/apps/sessrumnir/src/main/omp-fork-points.ts +105 -0
- package/apps/sessrumnir/src/main/omp-plugin-list.test.ts +94 -0
- package/apps/sessrumnir/src/main/omp-plugin-list.ts +103 -0
- package/apps/sessrumnir/src/main/package-catalog.test.ts +157 -0
- package/apps/sessrumnir/src/main/package-catalog.ts +166 -0
- package/apps/sessrumnir/src/main/package-updates.test.ts +149 -0
- package/apps/sessrumnir/src/main/package-updates.ts +185 -0
- package/apps/sessrumnir/src/main/path-authorization.test.ts +64 -0
- package/apps/sessrumnir/src/main/path-authorization.ts +31 -0
- package/apps/sessrumnir/src/main/pi-binary-resolution.test.ts +582 -0
- package/apps/sessrumnir/src/main/pi-binary-resolution.ts +649 -0
- package/apps/sessrumnir/src/main/pi-event-router.test.ts +388 -0
- package/apps/sessrumnir/src/main/pi-event-router.ts +290 -0
- package/apps/sessrumnir/src/main/pi-paths.test.ts +134 -0
- package/apps/sessrumnir/src/main/pi-paths.ts +74 -0
- package/apps/sessrumnir/src/main/pi-rpc-manager.test.ts +467 -0
- package/apps/sessrumnir/src/main/pi-rpc-manager.ts +1319 -0
- package/apps/sessrumnir/src/main/process-tree.test.ts +94 -0
- package/apps/sessrumnir/src/main/renderer-origin.test.ts +33 -0
- package/apps/sessrumnir/src/main/renderer-origin.ts +35 -0
- package/apps/sessrumnir/src/main/session-lineage-reader.test.ts +317 -0
- package/apps/sessrumnir/src/main/session-lineage-reader.ts +111 -0
- package/apps/sessrumnir/src/main/session-metadata.test.ts +508 -0
- package/apps/sessrumnir/src/main/session-metadata.ts +526 -0
- package/apps/sessrumnir/src/main/session-name.test.ts +71 -0
- package/apps/sessrumnir/src/main/session-name.ts +69 -0
- package/apps/sessrumnir/src/main/session-paths.test.ts +110 -0
- package/apps/sessrumnir/src/main/session-paths.ts +88 -0
- package/apps/sessrumnir/src/main/session-tags.test.ts +61 -0
- package/apps/sessrumnir/src/main/session-tags.ts +253 -0
- package/apps/sessrumnir/src/main/session-trash.test.ts +66 -0
- package/apps/sessrumnir/src/main/session-trash.ts +65 -0
- package/apps/sessrumnir/src/main/settings-logic.ts +66 -0
- package/apps/sessrumnir/src/main/skills-discovery.test.ts +149 -0
- package/apps/sessrumnir/src/main/skills-discovery.ts +179 -0
- package/apps/sessrumnir/src/main/startup-launch.ts +63 -0
- package/apps/sessrumnir/src/main/terminal-service.ts +96 -0
- package/apps/sessrumnir/src/main/theme-store.test.ts +440 -0
- package/apps/sessrumnir/src/main/theme-store.ts +495 -0
- package/apps/sessrumnir/src/main/tray-decision.test.ts +46 -0
- package/apps/sessrumnir/src/main/tray-decision.ts +49 -0
- package/apps/sessrumnir/src/main/tray-manager.ts +220 -0
- package/apps/sessrumnir/src/main/tsconfig.json +20 -0
- package/apps/sessrumnir/src/main/window-background.test.ts +29 -0
- package/apps/sessrumnir/src/main/window-background.ts +16 -0
- package/apps/sessrumnir/src/main/workflow-discovery.test.ts +252 -0
- package/apps/sessrumnir/src/main/workflow-monitor.test.ts +371 -0
- package/apps/sessrumnir/src/main/workflow-monitor.ts +1020 -0
- package/apps/sessrumnir/src/main/workspace-activity.test.ts +227 -0
- package/apps/sessrumnir/src/main/workspace-activity.ts +215 -0
- package/apps/sessrumnir/src/main/workspace-manager.test.ts +680 -0
- package/apps/sessrumnir/src/main/workspace-manager.ts +1199 -0
- package/apps/sessrumnir/src/main/workspace-trust.test.ts +78 -0
- package/apps/sessrumnir/src/main/workspace-trust.ts +84 -0
- package/apps/sessrumnir/src/preload/index.ts +358 -0
- package/apps/sessrumnir/src/preload/tsconfig.json +20 -0
- package/apps/sessrumnir/src/renderer/index.html +18 -0
- package/apps/sessrumnir/src/renderer/public/theme-boot.js +30 -0
- package/apps/sessrumnir/src/renderer/src/app.tsx +194 -0
- package/apps/sessrumnir/src/renderer/src/assets/openmoji-color.woff2 +0 -0
- package/apps/sessrumnir/src/renderer/src/assets/pi-logo.svg +7 -0
- package/apps/sessrumnir/src/renderer/src/assets/ymir-mark.svg +55 -0
- package/apps/sessrumnir/src/renderer/src/assets.d.ts +7 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-code-highlight.ts +239 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-file-link.ts +140 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-input.tsx +729 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.test.ts +175 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-panel-widths.ts +100 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-panel.tsx +523 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-project-picker.tsx +193 -0
- package/apps/sessrumnir/src/renderer/src/components/chat-search.tsx +251 -0
- package/apps/sessrumnir/src/renderer/src/components/code-editor-highlight.ts +85 -0
- package/apps/sessrumnir/src/renderer/src/components/code-editor-language.test.ts +19 -0
- package/apps/sessrumnir/src/renderer/src/components/code-editor-language.ts +88 -0
- package/apps/sessrumnir/src/renderer/src/components/code-editor.tsx +128 -0
- package/apps/sessrumnir/src/renderer/src/components/command-palette.tsx +315 -0
- package/apps/sessrumnir/src/renderer/src/components/command-results.tsx +80 -0
- package/apps/sessrumnir/src/renderer/src/components/composer-permission-menu.tsx +91 -0
- package/apps/sessrumnir/src/renderer/src/components/context-menu.tsx +457 -0
- package/apps/sessrumnir/src/renderer/src/components/copy-button.tsx +36 -0
- package/apps/sessrumnir/src/renderer/src/components/council-panels.tsx +193 -0
- package/apps/sessrumnir/src/renderer/src/components/custom-models-editor.tsx +330 -0
- package/apps/sessrumnir/src/renderer/src/components/diagnostics-panel.tsx +380 -0
- package/apps/sessrumnir/src/renderer/src/components/diff-viewer.tsx +318 -0
- package/apps/sessrumnir/src/renderer/src/components/ember-background.tsx +113 -0
- package/apps/sessrumnir/src/renderer/src/components/error-boundary.tsx +38 -0
- package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.test.ts +43 -0
- package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog-helpers.ts +51 -0
- package/apps/sessrumnir/src/renderer/src/components/extension-ui-dialog.tsx +486 -0
- package/apps/sessrumnir/src/renderer/src/components/file-tree.tsx +784 -0
- package/apps/sessrumnir/src/renderer/src/components/git-conveyor-actions.tsx +246 -0
- package/apps/sessrumnir/src/renderer/src/components/home-screen.tsx +396 -0
- package/apps/sessrumnir/src/renderer/src/components/image-viewer.tsx +100 -0
- package/apps/sessrumnir/src/renderer/src/components/line-numbered-code.tsx +58 -0
- package/apps/sessrumnir/src/renderer/src/components/markdown-renderer.tsx +237 -0
- package/apps/sessrumnir/src/renderer/src/components/message-bubble.tsx +1006 -0
- package/apps/sessrumnir/src/renderer/src/components/mission-control.tsx +341 -0
- package/apps/sessrumnir/src/renderer/src/components/model-selector.tsx +219 -0
- package/apps/sessrumnir/src/renderer/src/components/note-picker.tsx +155 -0
- package/apps/sessrumnir/src/renderer/src/components/notes-panel.tsx +437 -0
- package/apps/sessrumnir/src/renderer/src/components/package-browser.tsx +604 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-mode.test.ts +22 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-mode.ts +49 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.test.ts +44 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor-helpers.ts +35 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-rules-editor.tsx +226 -0
- package/apps/sessrumnir/src/renderer/src/components/permission-selector.tsx +104 -0
- package/apps/sessrumnir/src/renderer/src/components/resize-handle.tsx +51 -0
- package/apps/sessrumnir/src/renderer/src/components/review-rail.tsx +194 -0
- package/apps/sessrumnir/src/renderer/src/components/session-menu-position.test.ts +22 -0
- package/apps/sessrumnir/src/renderer/src/components/session-menu-position.ts +34 -0
- package/apps/sessrumnir/src/renderer/src/components/session-panel.tsx +711 -0
- package/apps/sessrumnir/src/renderer/src/components/session-runtime-indicator.tsx +43 -0
- package/apps/sessrumnir/src/renderer/src/components/settings-panel.tsx +1360 -0
- package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.test.ts +47 -0
- package/apps/sessrumnir/src/renderer/src/components/sidebar-activity.ts +76 -0
- package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.test.ts +73 -0
- package/apps/sessrumnir/src/renderer/src/components/sidebar-session-labels.ts +47 -0
- package/apps/sessrumnir/src/renderer/src/components/sidebar.tsx +1045 -0
- package/apps/sessrumnir/src/renderer/src/components/skills-panel.tsx +159 -0
- package/apps/sessrumnir/src/renderer/src/components/stats-panel.tsx +368 -0
- package/apps/sessrumnir/src/renderer/src/components/status-bar.tsx +274 -0
- package/apps/sessrumnir/src/renderer/src/components/status-popover.tsx +481 -0
- package/apps/sessrumnir/src/renderer/src/components/streaming-bubble.tsx +128 -0
- package/apps/sessrumnir/src/renderer/src/components/subagent-progress.tsx +254 -0
- package/apps/sessrumnir/src/renderer/src/components/task-launcher.tsx +168 -0
- package/apps/sessrumnir/src/renderer/src/components/terminal.tsx +203 -0
- package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.test.ts +35 -0
- package/apps/sessrumnir/src/renderer/src/components/theme-editor-helpers.ts +35 -0
- package/apps/sessrumnir/src/renderer/src/components/theme-editor.tsx +440 -0
- package/apps/sessrumnir/src/renderer/src/components/theme-gallery.tsx +373 -0
- package/apps/sessrumnir/src/renderer/src/components/thinking-level-selector.tsx +85 -0
- package/apps/sessrumnir/src/renderer/src/components/timeline.tsx +289 -0
- package/apps/sessrumnir/src/renderer/src/components/tool-call-icon.ts +32 -0
- package/apps/sessrumnir/src/renderer/src/components/workflow-navigator.tsx +901 -0
- package/apps/sessrumnir/src/renderer/src/components/workspace-tabs.tsx +241 -0
- package/apps/sessrumnir/src/renderer/src/global.d.ts +8 -0
- package/apps/sessrumnir/src/renderer/src/hooks/use-folder-drop.ts +101 -0
- package/apps/sessrumnir/src/renderer/src/hooks.test.ts +88 -0
- package/apps/sessrumnir/src/renderer/src/hooks.ts +574 -0
- package/apps/sessrumnir/src/renderer/src/i18n.test.ts +54 -0
- package/apps/sessrumnir/src/renderer/src/i18n.ts +89 -0
- package/apps/sessrumnir/src/renderer/src/index.css +425 -0
- package/apps/sessrumnir/src/renderer/src/main.tsx +42 -0
- package/apps/sessrumnir/src/renderer/src/message-grouping.test.ts +422 -0
- package/apps/sessrumnir/src/renderer/src/message-grouping.ts +410 -0
- package/apps/sessrumnir/src/renderer/src/message-parsing.test.ts +86 -0
- package/apps/sessrumnir/src/renderer/src/message-parsing.ts +166 -0
- package/apps/sessrumnir/src/renderer/src/store-external-prompts.test.ts +161 -0
- package/apps/sessrumnir/src/renderer/src/store-session-transitions.test.ts +2090 -0
- package/apps/sessrumnir/src/renderer/src/store-timeline.test.ts +52 -0
- package/apps/sessrumnir/src/renderer/src/store-turn-error.test.ts +136 -0
- package/apps/sessrumnir/src/renderer/src/store.ts +3726 -0
- package/apps/sessrumnir/src/renderer/src/subagent-detection.test.ts +55 -0
- package/apps/sessrumnir/src/renderer/src/theme/engine.test.ts +29 -0
- package/apps/sessrumnir/src/renderer/src/theme/engine.ts +20 -0
- package/apps/sessrumnir/src/renderer/src/themes/breeze-claudius.json +69 -0
- package/apps/sessrumnir/src/renderer/src/themes/breeze-dark.json +69 -0
- package/apps/sessrumnir/src/renderer/src/themes/breeze-light.json +69 -0
- package/apps/sessrumnir/src/renderer/src/themes/builtin-themes.test.ts +95 -0
- package/apps/sessrumnir/src/renderer/src/themes/dark.json +46 -0
- package/apps/sessrumnir/src/renderer/src/themes/fensalir.json +82 -0
- package/apps/sessrumnir/src/renderer/src/themes/gruvbox.json +69 -0
- package/apps/sessrumnir/src/renderer/src/themes/index.ts +60 -0
- package/apps/sessrumnir/src/renderer/src/themes/light.json +46 -0
- package/apps/sessrumnir/src/renderer/src/themes/nord.json +69 -0
- package/apps/sessrumnir/src/renderer/src/themes/sessrumnir.json +82 -0
- package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.test.ts +81 -0
- package/apps/sessrumnir/src/renderer/src/utils/debounced-buffer.ts +57 -0
- package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.test.ts +62 -0
- package/apps/sessrumnir/src/renderer/src/utils/format-relative-time.ts +28 -0
- package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.test.ts +57 -0
- package/apps/sessrumnir/src/renderer/src/utils/heatmap-grid.ts +41 -0
- package/apps/sessrumnir/src/renderer/src/utils/ipc-error.test.ts +22 -0
- package/apps/sessrumnir/src/renderer/src/utils/ipc-error.ts +12 -0
- package/apps/sessrumnir/src/renderer/src/utils/model-search.test.ts +63 -0
- package/apps/sessrumnir/src/renderer/src/utils/model-search.ts +20 -0
- package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.test.ts +31 -0
- package/apps/sessrumnir/src/renderer/src/utils/planning-prompt.ts +27 -0
- package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.test.ts +15 -0
- package/apps/sessrumnir/src/renderer/src/utils/preview-load-error.ts +22 -0
- package/apps/sessrumnir/src/renderer/src/utils/process-status-label.test.ts +13 -0
- package/apps/sessrumnir/src/renderer/src/utils/process-status-label.ts +19 -0
- package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.test.ts +68 -0
- package/apps/sessrumnir/src/renderer/src/utils/quick-switcher.ts +52 -0
- package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.test.ts +46 -0
- package/apps/sessrumnir/src/renderer/src/utils/rank-file-results.ts +23 -0
- package/apps/sessrumnir/src/renderer/src/utils/relative-time.tsx +25 -0
- package/apps/sessrumnir/src/renderer/src/utils/session-title.test.ts +98 -0
- package/apps/sessrumnir/src/renderer/src/utils/session-title.ts +58 -0
- package/apps/sessrumnir/src/renderer/src/utils/stale-guard.test.ts +29 -0
- package/apps/sessrumnir/src/renderer/src/utils/stale-guard.ts +20 -0
- package/apps/sessrumnir/src/renderer/src/utils/theme.test.ts +174 -0
- package/apps/sessrumnir/src/renderer/src/utils/theme.ts +159 -0
- package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.test.ts +130 -0
- package/apps/sessrumnir/src/renderer/src/utils/workflow-runs.ts +84 -0
- package/apps/sessrumnir/src/renderer/tsconfig.json +24 -0
- package/apps/sessrumnir/src/shared/agent-engine-label.test.ts +45 -0
- package/apps/sessrumnir/src/shared/agent-engine-label.ts +53 -0
- package/apps/sessrumnir/src/shared/app-settings.test.ts +25 -0
- package/apps/sessrumnir/src/shared/app-settings.ts +23 -0
- package/apps/sessrumnir/src/shared/bridge-http.ts +341 -0
- package/apps/sessrumnir/src/shared/bridge.ts +337 -0
- package/apps/sessrumnir/src/shared/council-config.test.ts +300 -0
- package/apps/sessrumnir/src/shared/council-config.ts +356 -0
- package/apps/sessrumnir/src/shared/default-settings.ts +39 -0
- package/apps/sessrumnir/src/shared/folder-drop.test.ts +118 -0
- package/apps/sessrumnir/src/shared/folder-drop.ts +74 -0
- package/apps/sessrumnir/src/shared/fork-point.test.ts +30 -0
- package/apps/sessrumnir/src/shared/fork-point.ts +24 -0
- package/apps/sessrumnir/src/shared/i18n/i18next.d.ts +10 -0
- package/apps/sessrumnir/src/shared/i18n/index.test.ts +51 -0
- package/apps/sessrumnir/src/shared/i18n/index.ts +50 -0
- package/apps/sessrumnir/src/shared/i18n/languages.ts +12 -0
- package/apps/sessrumnir/src/shared/i18n/locale-checks.ts +98 -0
- package/apps/sessrumnir/src/shared/i18n/locales.test.ts +92 -0
- package/apps/sessrumnir/src/shared/i18n/pseudo.test.ts +39 -0
- package/apps/sessrumnir/src/shared/i18n/pseudo.ts +44 -0
- package/apps/sessrumnir/src/shared/i18n/resolve.test.ts +43 -0
- package/apps/sessrumnir/src/shared/i18n/resolve.ts +41 -0
- package/apps/sessrumnir/src/shared/i18n/resources.ts +11 -0
- package/apps/sessrumnir/src/shared/ipc-contracts.ts +1539 -0
- package/apps/sessrumnir/src/shared/models-config.test.ts +124 -0
- package/apps/sessrumnir/src/shared/models-config.ts +167 -0
- package/apps/sessrumnir/src/shared/package-filter.ts +19 -0
- package/apps/sessrumnir/src/shared/package-spec.test.ts +51 -0
- package/apps/sessrumnir/src/shared/package-spec.ts +25 -0
- package/apps/sessrumnir/src/shared/path-compare.test.ts +38 -0
- package/apps/sessrumnir/src/shared/path-compare.ts +76 -0
- package/apps/sessrumnir/src/shared/permission-mode.ts +8 -0
- package/apps/sessrumnir/src/shared/pi-command.test.ts +165 -0
- package/apps/sessrumnir/src/shared/pi-command.ts +139 -0
- package/apps/sessrumnir/src/shared/product-name.ts +7 -0
- package/apps/sessrumnir/src/shared/session-lineage.test.ts +103 -0
- package/apps/sessrumnir/src/shared/session-lineage.ts +50 -0
- package/apps/sessrumnir/src/shared/session-preview.test.ts +93 -0
- package/apps/sessrumnir/src/shared/session-preview.ts +56 -0
- package/apps/sessrumnir/src/shared/sidebar-width.test.ts +58 -0
- package/apps/sessrumnir/src/shared/sidebar-width.ts +37 -0
- package/apps/sessrumnir/src/shared/theme/builtin-ids.ts +16 -0
- package/apps/sessrumnir/src/shared/theme/resolve.test.ts +66 -0
- package/apps/sessrumnir/src/shared/theme/resolve.ts +29 -0
- package/apps/sessrumnir/src/shared/theme/syntax-defaults.ts +24 -0
- package/apps/sessrumnir/src/shared/theme/theme-file.test.ts +88 -0
- package/apps/sessrumnir/src/shared/theme/theme-file.ts +132 -0
- package/apps/sessrumnir/src/shared/theme/tokens.test.ts +35 -0
- package/apps/sessrumnir/src/shared/theme/tokens.ts +86 -0
- package/apps/sessrumnir/src/shared/tsconfig.json +17 -0
- package/apps/sessrumnir/src/shared/untrusted-data.test.ts +32 -0
- package/apps/sessrumnir/src/shared/untrusted-data.ts +22 -0
- package/apps/sessrumnir/src/shared/version-compare.test.ts +33 -0
- package/apps/sessrumnir/src/shared/version-compare.ts +50 -0
- package/apps/sessrumnir/src/shared/workflow-control.ts +33 -0
- package/apps/sessrumnir/src/web-server.ts +315 -0
- package/apps/sessrumnir/tsconfig.json +26 -0
- package/apps/smidja/smidja_build.py +45 -0
- package/apps/smidja/smidja_build_review.py +76 -0
- package/apps/smidja/smidja_build_test.py +79 -0
- package/apps/smidja/smidja_document.py +76 -0
- package/apps/smidja/smidja_modules/agent_cc.py +15 -0
- package/apps/smidja/smidja_modules/agent_opencode.py +194 -0
- package/apps/smidja/smidja_modules/agent_pi.py +392 -0
- package/apps/smidja/smidja_modules/agents.py +993 -0
- package/apps/smidja/smidja_modules/changes.py +103 -0
- package/apps/smidja/smidja_modules/console.py +131 -0
- package/apps/smidja/smidja_modules/data_types.py +447 -0
- package/apps/smidja/smidja_modules/gates.py +108 -0
- package/apps/smidja/smidja_modules/git_helper.py +120 -0
- package/apps/smidja/smidja_modules/lm_local.py +144 -0
- package/apps/smidja/smidja_modules/permissions.py +194 -0
- package/apps/smidja/smidja_modules/prompts.py +21 -0
- package/apps/smidja/smidja_modules/quality.py +240 -0
- package/apps/smidja/smidja_modules/runner.py +155 -0
- package/apps/smidja/smidja_modules/session.py +50 -0
- package/apps/smidja/smidja_modules/stall_watch.py +149 -0
- package/apps/smidja/smidja_modules/tracer.py +271 -0
- package/apps/smidja/smidja_modules/utils.py +77 -0
- package/apps/smidja/smidja_orchestrate.py +162 -0
- package/apps/smidja/smidja_plan.py +45 -0
- package/apps/smidja/smidja_plan_build.py +55 -0
- package/apps/smidja/smidja_plan_build_test.py +86 -0
- package/apps/smidja/smidja_plan_build_test_quality.py +98 -0
- package/apps/smidja/smidja_prompt.py +44 -0
- package/apps/smidja/smidja_quality.py +49 -0
- package/apps/smidja/smidja_recon_iv.py +202 -0
- package/apps/smidja/smidja_scout.py +45 -0
- package/apps/smidja/smidja_simple_sdlc.py +183 -0
- package/apps/smidja/smidja_smidja_config/smidja.config.yaml +143 -0
- package/apps/smidja-factory/AGENTS.md +165 -0
- package/apps/smidja-factory/README.md +3 -0
- package/apps/smidja-factory/SKILL.md +18 -0
- package/apps/smidja-factory/apps/visualizer/.oxlintrc.json +18 -0
- package/apps/smidja-factory/apps/visualizer/bun.lock +269 -0
- package/apps/smidja-factory/apps/visualizer/desktop/icon.png +0 -0
- package/apps/smidja-factory/apps/visualizer/desktop/main.js +140 -0
- package/apps/smidja-factory/apps/visualizer/desktop/package-lock.json +871 -0
- package/apps/smidja-factory/apps/visualizer/desktop/package.json +18 -0
- package/apps/smidja-factory/apps/visualizer/desktop/preload.js +19 -0
- package/apps/smidja-factory/apps/visualizer/index.html +28 -0
- package/apps/smidja-factory/apps/visualizer/package.json +31 -0
- package/apps/smidja-factory/apps/visualizer/public/icon.svg +7 -0
- package/apps/smidja-factory/apps/visualizer/public/models/claude.png +0 -0
- package/apps/smidja-factory/apps/visualizer/public/models/gemini.png +0 -0
- package/apps/smidja-factory/apps/visualizer/public/models/kimi.png +0 -0
- package/apps/smidja-factory/apps/visualizer/public/models/openai.png +0 -0
- package/apps/smidja-factory/apps/visualizer/public/models/zai.png +0 -0
- package/apps/smidja-factory/apps/visualizer/server/chat.ts +425 -0
- package/apps/smidja-factory/apps/visualizer/server/db.ts +1050 -0
- package/apps/smidja-factory/apps/visualizer/server/index.ts +648 -0
- package/apps/smidja-factory/apps/visualizer/server/model-catalog.ts +90 -0
- package/apps/smidja-factory/apps/visualizer/server/roster-api.ts +75 -0
- package/apps/smidja-factory/apps/visualizer/server/roster_resolve.py +170 -0
- package/apps/smidja-factory/apps/visualizer/server/settings.ts +106 -0
- package/apps/smidja-factory/apps/visualizer/shared/types.ts +630 -0
- package/apps/smidja-factory/apps/visualizer/src/App.vue +379 -0
- package/apps/smidja-factory/apps/visualizer/src/components/ChatInput.vue +155 -0
- package/apps/smidja-factory/apps/visualizer/src/components/ChatThread.vue +219 -0
- package/apps/smidja-factory/apps/visualizer/src/components/ChatView.vue +335 -0
- package/apps/smidja-factory/apps/visualizer/src/components/DecisionsView.vue +177 -0
- package/apps/smidja-factory/apps/visualizer/src/components/DetailSection.vue +76 -0
- package/apps/smidja-factory/apps/visualizer/src/components/MemoryView.vue +397 -0
- package/apps/smidja-factory/apps/visualizer/src/components/PhaseDetail.vue +1354 -0
- package/apps/smidja-factory/apps/visualizer/src/components/PhaseDots.vue +55 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionCard.vue +583 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionMiniCard.vue +58 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionPanel.vue +272 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionStartForm.vue +152 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionTrace.vue +1157 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SessionsList.vue +96 -0
- package/apps/smidja-factory/apps/visualizer/src/components/SettingsView.vue +152 -0
- package/apps/smidja-factory/apps/visualizer/src/components/StatChip.vue +98 -0
- package/apps/smidja-factory/apps/visualizer/src/components/StatsView.vue +382 -0
- package/apps/smidja-factory/apps/visualizer/src/components/StatusChip.vue +73 -0
- package/apps/smidja-factory/apps/visualizer/src/components/ToolCallCard.vue +156 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/api.ts +177 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/chat-api.ts +95 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/chat-store.ts +351 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/events.ts +131 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/format.ts +92 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/highlight.ts +54 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/markdown.ts +120 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/models.ts +42 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/router.ts +67 -0
- package/apps/smidja-factory/apps/visualizer/src/lib/types.ts +61 -0
- package/apps/smidja-factory/apps/visualizer/src/main.ts +13 -0
- package/apps/smidja-factory/apps/visualizer/src/style.css +592 -0
- package/apps/smidja-factory/apps/visualizer/tsconfig.json +30 -0
- package/apps/smidja-factory/apps/visualizer/vite.config.js +32 -0
- package/apps/smidja-factory/apps/visualizer/vite.config.ts +31 -0
- package/apps/smidja-factory/cookbooks/create_config.md +61 -0
- package/apps/smidja-factory/cookbooks/create_smidja.md +120 -0
- package/apps/smidja-factory/cookbooks/how_to_prompt_for_the_eng.md +110 -0
- package/apps/smidja-factory/cookbooks/install.md +56 -0
- package/apps/smidja-factory/cookbooks/run_smidja.md +122 -0
- package/apps/smidja-factory/cookbooks/smidja_overview.md +88 -0
- package/apps/smidja-factory/cookbooks/update_config.md +103 -0
- package/apps/smidja-factory/cookbooks/update_modules.md +100 -0
- package/apps/smidja-factory/cookbooks/update_smidja.md +90 -0
- package/apps/smidja-factory/docs/enhancement-plan.md +420 -0
- package/apps/smidja-factory/references/config.md +202 -0
- package/apps/smidja-factory/references/handoff.md +161 -0
- package/apps/smidja-factory/references/observability.md +166 -0
- package/apps/smidja-factory/scripts/install.py +113 -0
- package/apps/smidja-factory/scripts/make_config.py +35 -0
- package/apps/smidja-factory/scripts/make_smidja.py +119 -0
- package/apps/smidja-factory/skills/add-or-edit-ai-models/SKILL.md +151 -0
- package/apps/smidja-factory/skills/create-new-agent/SKILL.md +125 -0
- package/apps/smidja-factory/skills/create-new-teams/SKILL.md +151 -0
- package/apps/smidja-factory/skills/git-ops/README.md +25 -0
- package/apps/smidja-factory/skills/git-ops/SKILL.md +18 -0
- package/apps/smidja-factory/skills/git-ops/create_branch.sh +79 -0
- package/apps/smidja-factory/skills/git-ops/safe_commit.sh +82 -0
- package/apps/smidja-factory/skills/git-ops/sync_upstream.sh +94 -0
- package/apps/smidja-factory/skills/how-to-run-agent-teams/SKILL.md +117 -0
- package/apps/smidja-factory/skills/smidja-instructions/SKILL.md +168 -0
- package/apps/smidja-factory/skills/smidja-launcher/SKILL.md +155 -0
- package/apps/smidja-factory/skills/smidja-start/SKILL.md +266 -0
- package/apps/smidja-factory/skills/volundr/SKILL.md +232 -0
- package/apps/smidja-factory/skills/volundr/prompt/dispatch-prompt.md +91 -0
- package/apps/smidja-factory/skills/volundr/prompt/volundr-system.md +63 -0
- package/apps/smidja-factory/skills/volundr/scripts/volundr-observe.py +52 -0
- package/apps/smidja-factory/skills/volundr/scripts/volundr-recall.py +54 -0
- package/apps/smidja-factory/skills/volundr/scripts/volundr-status.py +51 -0
- package/apps/smidja-factory/skills/volundr/scripts/volundr-teach.py +47 -0
- package/apps/smidja-factory/templates/env.sample +28 -0
- package/apps/smidja-factory/templates/harness_engineering/subagents.ts +706 -0
- package/apps/smidja-factory/templates/harness_engineering/themeMap.ts +145 -0
- package/apps/smidja-factory/templates/justfile +89 -0
- package/apps/smidja-factory/templates/prompt_engineering/builder/system.md +13 -0
- package/apps/smidja-factory/templates/prompt_engineering/builder/user.md +34 -0
- package/apps/smidja-factory/templates/prompt_engineering/documenter/system.md +17 -0
- package/apps/smidja-factory/templates/prompt_engineering/documenter/user.md +48 -0
- package/apps/smidja-factory/templates/prompt_engineering/orchestrator/system.md +55 -0
- package/apps/smidja-factory/templates/prompt_engineering/orchestrator/user.md +39 -0
- package/apps/smidja-factory/templates/prompt_engineering/planner/system.md +21 -0
- package/apps/smidja-factory/templates/prompt_engineering/planner/user.md +45 -0
- package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/system.md +51 -0
- package/apps/smidja-factory/templates/prompt_engineering/recon_orchestrator/user.md +26 -0
- package/apps/smidja-factory/templates/prompt_engineering/reviewer/system.md +16 -0
- package/apps/smidja-factory/templates/prompt_engineering/reviewer/user.md +44 -0
- package/apps/smidja-factory/templates/prompt_engineering/scout/system.md +32 -0
- package/apps/smidja-factory/templates/prompt_engineering/scout/user.md +34 -0
- package/apps/smidja-factory/templates/smidja/smidja_build.py +45 -0
- package/apps/smidja-factory/templates/smidja/smidja_build_review.py +76 -0
- package/apps/smidja-factory/templates/smidja/smidja_build_test.py +79 -0
- package/apps/smidja-factory/templates/smidja/smidja_document.py +76 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/agent_cc.py +15 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/agent_opencode.py +194 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/agent_pi.py +392 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/agents.py +993 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/changes.py +103 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/console.py +131 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/data_types.py +447 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/gates.py +108 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/git_helper.py +120 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/lm_local.py +144 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/permissions.py +194 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/prompts.py +21 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/quality.py +240 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/runner.py +155 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/session.py +50 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/stall_watch.py +149 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/tracer.py +271 -0
- package/apps/smidja-factory/templates/smidja/smidja_modules/utils.py +77 -0
- package/apps/smidja-factory/templates/smidja/smidja_orchestrate.py +162 -0
- package/apps/smidja-factory/templates/smidja/smidja_plan.py +45 -0
- package/apps/smidja-factory/templates/smidja/smidja_plan_build.py +55 -0
- package/apps/smidja-factory/templates/smidja/smidja_plan_build_test.py +86 -0
- package/apps/smidja-factory/templates/smidja/smidja_plan_build_test_quality.py +98 -0
- package/apps/smidja-factory/templates/smidja/smidja_prompt.py +44 -0
- package/apps/smidja-factory/templates/smidja/smidja_quality.py +49 -0
- package/apps/smidja-factory/templates/smidja/smidja_recon_iv.py +202 -0
- package/apps/smidja-factory/templates/smidja/smidja_scout.py +45 -0
- package/apps/smidja-factory/templates/smidja/smidja_simple_sdlc.py +183 -0
- package/apps/smidja-factory/templates/smidja.config.yaml +143 -0
- package/bin/app-build.sh +36 -0
- package/bin/electron-lib.sh +25 -0
- package/bin/sessrumnir.sh +1 -1
- package/bin/ymir-install.sh +7 -0
- package/package.json +15 -7
- package/scripts/electron.sh +6 -2
- package/.agents/bus/messages.json +0 -1
- package/.agents/config/agents.machine.example.yaml +0 -16
- package/.pi/mcp.json +0 -16
- package/assets/reference/state/.branch-eligible-owner +0 -4
- package/assets/reference/state/.branch-mirror-cursor +0 -1
- package/assets/reference/state/.branch-outcomes-cursor +0 -1
- package/assets/reference/state/.branch-session +0 -1
- package/assets/reference/state/.count-default_wF_p5 +0 -1
- package/assets/reference/state/.count-default_wF_p6 +0 -1
- package/assets/reference/state/.count-default_wR_p2 +0 -1
- package/assets/reference/state/.count-default_wS_p2 +0 -1
- package/assets/reference/state/.count-firstmate_fm-server-knowledge +0 -1
- package/assets/reference/state/.hash-default_wF_p5 +0 -1
- package/assets/reference/state/.hash-default_wF_p6 +0 -1
- package/assets/reference/state/.hash-default_wR_p2 +0 -1
- package/assets/reference/state/.hash-default_wS_p2 +0 -1
- package/assets/reference/state/.hash-firstmate_fm-server-knowledge +0 -1
- package/assets/reference/state/.heartbeat-streak +0 -1
- package/assets/reference/state/.inactive-outcome-reconcile +0 -2
- package/assets/reference/state/.lock +0 -1
- package/assets/reference/state/.pi-branch-extension-loaded +0 -1
- package/assets/reference/state/.pi-turnend-extension-loaded +0 -2
- package/assets/reference/state/.pi-watch-extension-loaded +0 -2
- package/assets/reference/state/.seen-server-knowledge_turn-ended +0 -1
- package/assets/reference/state/.session-start-agents-baseline +0 -2
- package/assets/reference/state/.session-start-complete +0 -1
- package/assets/reference/state/.stale-default_wF_p5 +0 -1
- package/assets/reference/state/.stale-default_wR_p2 +0 -1
- package/assets/reference/state/.stale-firstmate_fm-server-knowledge +0 -1
- package/assets/reference/state/.stale-since-default_wF_p5 +0 -1
- package/assets/reference/state/.stale-since-default_wR_p2 +0 -1
- package/assets/reference/state/.stale-since-firstmate_fm-server-knowledge +0 -1
- package/assets/reference/state/.startup-network.delivered +0 -1
- package/assets/reference/state/.startup-network.report +0 -1
- package/assets/reference/state/.startup-network.status +0 -10
- package/assets/reference/state/.startup-network.timings +0 -6
- package/assets/reference/state/.trace-context-effective +0 -1
- package/assets/reference/state/.wake-queue +0 -0
- package/assets/reference/state/.wake-queue.seq +0 -1
- package/assets/reference/state/.wake-rows.consume.BcqXSq +0 -0
- package/assets/reference/state/.wake-rows.consume.NDZZei +0 -0
- package/assets/reference/state/.wake-rows.consume.VGkhYm +0 -0
- package/assets/reference/state/.wake-rows.consume.tR2rTy +0 -0
- package/assets/reference/state/.watch-arm-output.3581p6 +0 -0
- package/assets/reference/state/.watch-arm-output.3ciDNP +0 -0
- package/assets/reference/state/.watch-arm-output.SksR6k +0 -0
- package/assets/reference/state/.watch-cycle-exits.log +0 -22
- package/assets/reference/state/.watch-deliveries.log +0 -19
- package/assets/reference/state/.watch-triage.log +0 -38
- package/assets/reference/state/.watch.lock/fm-home +0 -1
- package/assets/reference/state/.watch.lock/pid +0 -1
- package/assets/reference/state/.watch.lock/pid-identity +0 -1
- package/assets/reference/state/.watch.lock/watcher-path +0 -1
- package/assets/reference/state/.watch.lock.owner.qqbxmS/fm-home +0 -1
- package/assets/reference/state/.watch.lock.owner.qqbxmS/pid +0 -1
- package/assets/reference/state/.watch.lock.owner.qqbxmS/pid-identity +0 -1
- package/assets/reference/state/.watch.lock.owner.qqbxmS/watcher-path +0 -1
- package/assets/reference/state/.watcher-down +0 -1
- package/assets/reference/state/branch-outcomes.jsonl +0 -1
- package/assets/reference/state/branch-session/2026-09-04T16-49-38-671Z_01a06d53-6b6f-7619-930d-e6dfd9a4a29e.jsonl +0 -211
- package/assets/reference/state/home-summary.json +0 -42
- package/assets/reference/state/server-knowledge.busy-gen +0 -1
- package/assets/reference/state/server-knowledge.busy-state +0 -1
- package/assets/reference/state/server-knowledge.herdr-presentation +0 -12
- package/assets/reference/state/server-knowledge.meta +0 -18
- package/assets/reference/state/server-knowledge.pi-ext.ts +0 -26
- package/assets/reference/state/server-knowledge.turn-ended +0 -0
- /package/{assets/reference/state/.last-check → apps/hlidskjalf/src/app/.gitkeep} +0 -0
- /package/{assets/reference/state/.last-heartbeat → apps/hlidskjalf/src/components/.gitkeep} +0 -0
- /package/{assets/reference/state/.last-watcher-beat → apps/hlidskjalf/src/services/.gitkeep} +0 -0
- /package/{assets/reference/state/.main-eligible-rows.tmp.p2HMKo → apps/smidja/smidja_modules/__init__.py} +0 -0
- /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
|
+
}
|