diff --git a/landing/product-docs/.vitepress/config.ts b/landing/product-docs/.vitepress/config.ts index dc246339..610afc50 100644 --- a/landing/product-docs/.vitepress/config.ts +++ b/landing/product-docs/.vitepress/config.ts @@ -93,17 +93,29 @@ const ruGuide: DefaultTheme.SidebarItem[] = [ ]; const rootNav: DefaultTheme.NavItem[] = [ - { text: "Guide", link: "/guide/quickstart" }, - { text: "Reference", link: "/reference/concepts" }, - { text: "Troubleshooting", link: "/guide/troubleshooting" }, - { text: "Download", link: downloadUrl, target: "_self" } + { text: "Guide", link: "/guide/quickstart", activeMatch: "^/guide/(?!troubleshooting(?:/|$))" }, + { text: "Reference", link: "/reference/concepts", activeMatch: "^/reference/" }, + { + text: "Troubleshooting", + link: "/guide/troubleshooting", + activeMatch: "^/guide/troubleshooting(?:/|$)" + }, + { text: "Download", link: downloadUrl, target: "_self", noIcon: true } ]; const ruNav: DefaultTheme.NavItem[] = [ - { text: "Руководство", link: "/ru/guide/quickstart" }, - { text: "Справочник", link: "/ru/reference/concepts" }, - { text: "Диагностика", link: "/ru/guide/troubleshooting" }, - { text: "Скачать", link: ruDownloadUrl, target: "_self" } + { + text: "Руководство", + link: "/ru/guide/quickstart", + activeMatch: "^/ru/guide/(?!troubleshooting(?:/|$))" + }, + { text: "Справочник", link: "/ru/reference/concepts", activeMatch: "^/ru/reference/" }, + { + text: "Диагностика", + link: "/ru/guide/troubleshooting", + activeMatch: "^/ru/guide/troubleshooting(?:/|$)" + }, + { text: "Скачать", link: ruDownloadUrl, target: "_self", noIcon: true } ]; export default defineConfig({ @@ -173,12 +185,20 @@ export default defineConfig({ } }, themeConfig: { - logo: "/logo-192.png", + logo: { + light: "/logo-192.png", + dark: "/logo-192.png", + alt: "Agent Teams" + }, siteTitle: "Agent Teams", outline: { level: [2, 3], label: "On this page" }, + externalLinkIcon: true, + darkModeSwitchLabel: "Appearance", + lightModeSwitchTitle: "Switch to light theme", + darkModeSwitchTitle: "Switch to dark theme", search: { provider: "local", options: { @@ -198,6 +218,14 @@ export default defineConfig({ } } }, + lastUpdated: { + text: "Last updated", + formatOptions: { + dateStyle: "medium", + timeStyle: "short", + forceLocale: true + } + }, nav: rootNav, sidebar: { "/ru/": ruGuide, @@ -236,6 +264,9 @@ export default defineConfig({ level: [2, 3], label: "На этой странице" }, + darkModeSwitchLabel: "Оформление", + lightModeSwitchTitle: "Переключить на светлую тему", + darkModeSwitchTitle: "Переключить на тёмную тему", search: { provider: "local", options: { @@ -255,6 +286,14 @@ export default defineConfig({ } } }, + lastUpdated: { + text: "Обновлено", + formatOptions: { + dateStyle: "medium", + timeStyle: "short", + forceLocale: true + } + }, editLink: { pattern: `https://github.com/${REPO}/edit/main/landing/product-docs/:path`, text: "Редактировать на GitHub" diff --git a/package.json b/package.json index 1a31909b..b6ee7177 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "cronstrue": "^3.13.0", "date-fns": "^3.6.0", "diff": "^8.0.3", - "dompurify": "^3.3.1", + "dompurify": "^3.4.2", "electron-updater": "^6.7.3", "fastify": "^5.8.5", "highlight.js": "^11.11.1", @@ -153,7 +153,7 @@ "isbinaryfile": "^6.0.0", "lucide-react": "^0.577.0", "mdast-util-to-hast": "^13.2.1", - "mermaid": "^11.12.3", + "mermaid": "^11.15.0", "motion": "12.38.0", "node-diff3": "^3.2.0", "node-pty": "^1.1.0", @@ -177,7 +177,7 @@ "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", "unified": "^11.0.5", - "yaml": "^2.8.2", + "yaml": "^2.9.0", "yet-another-react-lightbox": "^3.29.1", "zustand": "^4.5.0" }, @@ -330,6 +330,10 @@ }, "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", "pnpm": { + "overrides": { + "lodash-es": "^4.18.1", + "uuid": "^11.1.1" + }, "onlyBuiltDependencies": [ "electron", "node-pty", diff --git a/packages/agent-graph/src/canvas/draw-misc.ts b/packages/agent-graph/src/canvas/draw-misc.ts index 1a60831e..e2e5da31 100644 --- a/packages/agent-graph/src/canvas/draw-misc.ts +++ b/packages/agent-graph/src/canvas/draw-misc.ts @@ -30,6 +30,69 @@ export function truncateText( return lo > 0 ? text.slice(0, lo) + '...' : '...'; } +function fitTextPrefix( + ctx: CanvasRenderingContext2D, + text: string, + maxWidth: number, + font: string, +): string { + let lo = 0; + let hi = text.length; + while (lo < hi) { + const mid = (lo + hi + 1) >> 1; + if (measureTextCached(ctx, font, text.slice(0, mid)) <= maxWidth) { + lo = mid; + } else { + hi = mid - 1; + } + } + return text.slice(0, lo); +} + +/** + * Wrap text into a small fixed number of canvas lines. + * The final line is truncated with "..." when the remaining text still overflows. + */ +export function wrapTextLines( + ctx: CanvasRenderingContext2D, + text: string, + maxWidth: number, + font: string, + maxLines: number, +): string[] { + const normalized = text.trim().replace(/\s+/g, ' '); + if (!normalized || maxLines <= 0) return []; + if (maxLines === 1) return [truncateText(ctx, normalized, maxWidth, font)]; + + const lines: string[] = []; + let remaining = normalized; + + while (remaining && lines.length < maxLines) { + if (measureTextCached(ctx, font, remaining) <= maxWidth) { + lines.push(remaining); + break; + } + + if (lines.length === maxLines - 1) { + lines.push(truncateText(ctx, remaining, maxWidth, font)); + break; + } + + const prefix = fitTextPrefix(ctx, remaining, maxWidth, font).trimEnd(); + if (!prefix) { + lines.push(truncateText(ctx, remaining, maxWidth, font)); + break; + } + + const breakIndex = prefix.lastIndexOf(' '); + const line = breakIndex > 0 ? prefix.slice(0, breakIndex) : prefix; + lines.push(line); + remaining = remaining.slice(line.length).trimStart(); + } + + return lines; +} + // Pre-computed hex vertex unit offsets (avoids cos/sin per call) const HEX_COS: number[] = []; const HEX_SIN: number[] = []; diff --git a/packages/agent-graph/src/canvas/draw-tasks.ts b/packages/agent-graph/src/canvas/draw-tasks.ts index ef370902..edb0cb41 100644 --- a/packages/agent-graph/src/canvas/draw-tasks.ts +++ b/packages/agent-graph/src/canvas/draw-tasks.ts @@ -6,7 +6,7 @@ import type { GraphNode } from '../ports/types'; import { COLORS, getTaskStatusColor, getReviewStateColor } from '../constants/colors'; import { TASK_PILL, MIN_VISIBLE_OPACITY, ANIM } from '../constants/canvas-constants'; -import { truncateText } from './draw-misc'; +import { truncateText, wrapTextLines } from './draw-misc'; import { drawPillShell, drawPillStackLayer } from './draw-pill-shell'; import { hexWithAlpha } from './render-cache'; import type { KanbanZoneInfo } from '../layout/kanbanLayout'; @@ -153,7 +153,8 @@ function drawTaskPill( drawLiveTaskLogIndicator(ctx, -halfW + 8, -halfH + 8, time); } - // Subject (main title — large) + // Subject (main title - up to two lines) + let subjectLineCount = 0; if (node.sublabel) { ctx.font = `bold ${TASK_PILL.idFontSize}px sans-serif`; ctx.textAlign = 'left'; @@ -164,8 +165,13 @@ function drawTaskPill( node.reviewState !== 'approved' && (node.reviewMode === 'manual' || (node.reviewMode === 'assigned' && !!node.reviewerName)); const maxW = hasReviewChip ? w - 88 : w - 24; - const subject = truncateText(ctx, node.sublabel, maxW, ctx.font); - ctx.fillText(subject, textX, -12); + const subjectLines = wrapTextLines(ctx, node.sublabel, maxW, ctx.font, 2); + subjectLineCount = subjectLines.length; + const titleStartY = subjectLines.length > 1 ? -16 : -12; + const titleLineHeight = TASK_PILL.idFontSize + 1.5; + subjectLines.forEach((line, index) => { + ctx.fillText(line, textX, titleStartY + index * titleLineHeight); + }); } // Display ID (secondary — small) @@ -174,7 +180,7 @@ function drawTaskPill( ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillStyle = COLORS.textDim; - ctx.fillText(displayId, -halfW + 10, 12); + ctx.fillText(displayId, -halfW + 10, subjectLineCount > 1 ? 23 : 12); // Approved badge: checkmark at right side if (node.reviewState === 'approved') { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5cc86e3f..6b7e307e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,10 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + lodash-es: ^4.18.1 + uuid: ^11.1.1 + importers: .: @@ -204,8 +208,8 @@ importers: specifier: ^8.0.3 version: 8.0.3 dompurify: - specifier: ^3.3.1 - version: 3.3.1 + specifier: ^3.4.2 + version: 3.4.2 electron-updater: specifier: ^6.7.3 version: 6.7.3 @@ -228,8 +232,8 @@ importers: specifier: ^13.2.1 version: 13.2.1 mermaid: - specifier: ^11.12.3 - version: 11.12.3 + specifier: ^11.15.0 + version: 11.15.0 motion: specifier: 12.38.0 version: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -295,13 +299,13 @@ importers: version: 3.5.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) + version: 1.0.7(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0)) unified: specifier: ^11.0.5 version: 11.0.5 yaml: - specifier: ^2.8.2 - version: 2.8.2 + specifier: ^2.9.0 + version: 2.9.0 yet-another-react-lightbox: specifier: ^3.29.1 version: 3.29.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -323,7 +327,7 @@ importers: version: 5.1.1(encoding@0.1.13)(rollup@4.60.0) '@tailwindcss/typography': specifier: ^0.5.19 - version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) + version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0)) '@types/hast': specifier: ^3.0.4 version: 3.0.4 @@ -401,7 +405,7 @@ importers: version: 3.0.6(eslint@9.39.2(jiti@1.21.7)) eslint-plugin-tailwindcss: specifier: ^3.18.2 - version: 3.18.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)) + version: 3.18.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0)) globals: specifier: ^17.2.0 version: 17.2.0 @@ -428,7 +432,7 @@ importers: version: 0.7.2(prettier@3.8.1) tailwindcss: specifier: ^3.4.1 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + version: 3.4.19(tsx@4.21.0)(yaml@2.9.0) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -460,13 +464,13 @@ importers: version: 0.11.3(magicast@0.5.2)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))) '@vueuse/nuxt': specifier: ^10.11.1 - version: 10.11.1(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + version: 10.11.1(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) nuxt: specifier: ^3.20.2 - version: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + version: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) nuxt-icon: specifier: ^0.6.10 - version: 0.6.10(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + version: 0.6.10(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) pinia: specifier: ^3.0.4 version: 3.0.4(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3)) @@ -491,7 +495,7 @@ importers: version: 7.4.47 '@nuxt/eslint': specifier: ^1.12.1 - version: 1.15.2(@typescript-eslint/utils@8.57.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.15.2(@typescript-eslint/utils@8.57.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) '@shikijs/transformers': specifier: 3.22.0 version: 3.22.0 @@ -512,13 +516,13 @@ importers: version: 1.98.0 tailwindcss: specifier: ^3.4.19 - version: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + version: 3.4.19(tsx@4.21.0)(yaml@2.9.0) vite-plugin-vuetify: specifier: ^2.1.3 - version: 2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3) + version: 2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3) vitepress: specifier: 2.0.0-alpha.17 - version: 2.0.0-alpha.17(@types/node@25.0.7)(axios@1.13.6)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(oxc-minify@0.117.0)(postcss@8.5.8)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 2.0.0-alpha.17(@types/node@25.0.7)(axios@1.13.6)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(oxc-minify@0.117.0)(postcss@8.5.8)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) vitepress-codeblock-collapse: specifier: ^1.0.0 version: 1.0.0(vue@3.5.30(typescript@5.9.3)) @@ -543,7 +547,7 @@ importers: version: 22.19.15 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2) + version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -784,21 +788,9 @@ packages: '@braintree/sanitize-url@7.1.2': resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@chevrotain/cst-dts-gen@11.1.2': - resolution: {integrity: sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==} - - '@chevrotain/gast@11.1.2': - resolution: {integrity: sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==} - - '@chevrotain/regexp-to-ast@11.1.2': - resolution: {integrity: sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==} - '@chevrotain/types@11.1.2': resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - '@chevrotain/utils@11.1.2': - resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} - '@clack/core@1.1.0': resolution: {integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==} @@ -1979,8 +1971,8 @@ packages: '@mdi/js@7.4.47': resolution: {integrity: sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==} - '@mermaid-js/parser@1.0.0': - resolution: {integrity: sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==} + '@mermaid-js/parser@1.1.1': + resolution: {integrity: sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==} '@miyaneee/rollup-plugin-json5@1.2.0': resolution: {integrity: sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA==} @@ -4767,6 +4759,9 @@ packages: cpu: [x64] os: [win32] + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vercel/nft@1.5.0': resolution: {integrity: sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==} engines: {node: '>=20'} @@ -5548,14 +5543,6 @@ packages: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - - chevrotain@11.1.2: - resolution: {integrity: sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6031,8 +6018,8 @@ packages: resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} engines: {node: '>=12'} - dagre-d3-es@7.0.13: - resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -6231,8 +6218,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.4.2: + resolution: {integrity: sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -6401,6 +6388,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -7828,10 +7818,6 @@ packages: resolution: {integrity: sha512-2LOQnFKu3m0VxpE+5sb5+BRTSKrXmNxGgxVRiKwD9s5KQB1zID/FRXhtzeV7RT1L2GVpdEEAfVuclFOMGl1ikA==} engines: {node: '>= 18'} - langium@4.2.1: - resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==} - engines: {node: '>=20.10.0', npm: '>=10.2.3'} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -7908,8 +7894,8 @@ packages: resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} engines: {node: '>=20'} - lodash-es@4.17.23: - resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -8122,8 +8108,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.12.3: - resolution: {integrity: sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==} + mermaid@11.15.0: + resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -10651,8 +10637,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true vary@1.1.2: @@ -10876,23 +10862,6 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -11125,6 +11094,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -11504,23 +11478,8 @@ snapshots: '@braintree/sanitize-url@7.1.2': {} - '@chevrotain/cst-dts-gen@11.1.2': - dependencies: - '@chevrotain/gast': 11.1.2 - '@chevrotain/types': 11.1.2 - lodash-es: 4.17.23 - - '@chevrotain/gast@11.1.2': - dependencies: - '@chevrotain/types': 11.1.2 - lodash-es: 4.17.23 - - '@chevrotain/regexp-to-ast@11.1.2': {} - '@chevrotain/types@11.1.2': {} - '@chevrotain/utils@11.1.2': {} - '@clack/core@1.1.0': dependencies: sisteransi: 1.0.5 @@ -12745,9 +12704,9 @@ snapshots: '@mdi/js@7.4.47': {} - '@mermaid-js/parser@1.0.0': + '@mermaid-js/parser@1.1.1': dependencies: - langium: 4.2.1 + '@chevrotain/types': 11.1.2 '@miyaneee/rollup-plugin-json5@1.2.0(rollup@4.60.0)': dependencies: @@ -12857,20 +12816,20 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@1.7.0(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@nuxt/devtools-kit@1.7.0(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@nuxt/kit': 3.21.2(magicast@0.5.2) '@nuxt/schema': 3.21.2 execa: 7.2.0 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - magicast - '@nuxt/devtools-kit@3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@nuxt/devtools-kit@3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@nuxt/kit': 4.4.2(magicast@0.5.2) execa: 8.0.1 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - magicast @@ -12885,9 +12844,9 @@ snapshots: pkg-types: 2.3.0 semver: 7.7.4 - '@nuxt/devtools@3.2.4(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))': + '@nuxt/devtools@3.2.4(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))': dependencies: - '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.2.4 '@nuxt/kit': 4.4.2(magicast@0.5.2) '@vue/devtools-core': 8.1.0(vue@3.5.30(typescript@5.9.3)) @@ -12915,9 +12874,9 @@ snapshots: sirv: 3.0.2 structured-clone-es: 2.0.0 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-inspect: 11.3.3(@nuxt/kit@4.4.2(magicast@0.5.2))(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) - vite-plugin-vue-tracer: 1.3.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) + vite-plugin-inspect: 11.3.3(@nuxt/kit@4.4.2(magicast@0.5.2))(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) + vite-plugin-vue-tracer: 1.3.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) which: 6.0.1 ws: 8.20.0 transitivePeerDependencies: @@ -12966,10 +12925,10 @@ snapshots: - supports-color - typescript - '@nuxt/eslint@1.15.2(@typescript-eslint/utils@8.57.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))': + '@nuxt/eslint@1.15.2(@typescript-eslint/utils@8.57.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@eslint/config-inspector': 1.5.0(eslint@9.39.2(jiti@2.6.1)) - '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@nuxt/devtools-kit': 3.2.4(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) '@nuxt/eslint-config': 1.15.2(@typescript-eslint/utils@8.57.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@vue/compiler-sfc@3.5.30)(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@nuxt/eslint-plugin': 1.15.2(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@nuxt/kit': 4.4.2(magicast@0.5.2) @@ -13045,7 +13004,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@3.21.2(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3)': + '@nuxt/nitro-server@3.21.2(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 '@nuxt/kit': 3.21.2(magicast@0.5.2) @@ -13063,7 +13022,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.2(encoding@0.1.13)(idb-keyval@6.2.2) - nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 @@ -13128,12 +13087,12 @@ snapshots: rc9: 3.0.0 std-env: 3.10.0 - '@nuxt/vite-builder@3.21.2(@types/node@25.0.7)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))(yaml@2.8.2)': + '@nuxt/vite-builder@3.21.2(@types/node@25.0.7)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 3.21.2(magicast@0.5.2) '@rollup/plugin-replace': 6.0.3(rollup@4.60.0) - '@vitejs/plugin-vue': 6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) - '@vitejs/plugin-vue-jsx': 5.1.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + '@vitejs/plugin-vue': 6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': 5.1.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) autoprefixer: 10.5.0(postcss@8.5.8) consola: 3.4.2 cssnano: 7.1.3(postcss@8.5.8) @@ -13147,7 +13106,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) nypm: 0.6.5 ohash: 2.0.11 pathe: 2.0.3 @@ -13158,9 +13117,9 @@ snapshots: std-env: 4.0.0 ufo: 1.6.3 unenv: 2.0.0-rc.24 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-node: 5.3.0(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) + vite-node: 5.3.0(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) + vite-plugin-checker: 0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) vue: 3.5.30(typescript@5.9.3) vue-bundle-renderer: 2.2.0 optionalDependencies: @@ -14875,10 +14834,10 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))': + '@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0))': dependencies: postcss-selector-parser: 6.0.10 - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.9.0) '@tanstack/react-virtual@3.13.24(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: @@ -15638,6 +15597,11 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vercel/nft@1.5.0(encoding@0.1.13)(rollup@4.60.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3(encoding@0.1.13) @@ -15669,22 +15633,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))': + '@vitejs/plugin-vue-jsx@5.1.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@rolldown/pluginutils': 1.0.0-rc.11 '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0) - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.30(typescript@5.9.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.30(typescript@5.9.3) '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.7)(happy-dom@20.0.2)(sass@1.98.0)(terser@5.46.0))': @@ -15955,13 +15919,13 @@ snapshots: '@vueuse/metadata@14.3.0': {} - '@vueuse/nuxt@10.11.1(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))': + '@vueuse/nuxt@10.11.1(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))': dependencies: '@nuxt/kit': 3.21.2(magicast@0.5.2) '@vueuse/core': 10.11.1(vue@3.5.30(typescript@5.9.3)) '@vueuse/metadata': 10.11.1 local-pkg: 0.5.1 - nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2) + nuxt: 3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0) vue-demi: 0.14.10(vue@3.5.30(typescript@5.9.3)) transitivePeerDependencies: - '@vue/composition-api' @@ -16614,20 +16578,6 @@ snapshots: check-error@2.1.3: {} - chevrotain-allstar@0.3.1(chevrotain@11.1.2): - dependencies: - chevrotain: 11.1.2 - lodash-es: 4.17.23 - - chevrotain@11.1.2: - dependencies: - '@chevrotain/cst-dts-gen': 11.1.2 - '@chevrotain/gast': 11.1.2 - '@chevrotain/regexp-to-ast': 11.1.2 - '@chevrotain/types': 11.1.2 - '@chevrotain/utils': 11.1.2 - lodash-es: 4.17.23 - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -17129,10 +17079,10 @@ snapshots: d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 - dagre-d3-es@7.0.13: + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 - lodash-es: 4.17.23 + lodash-es: 4.18.1 damerau-levenshtein@1.0.8: {} @@ -17298,7 +17248,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.1: + dompurify@3.4.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -17566,6 +17516,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.46.1: {} + es6-error@4.1.1: optional: true @@ -17955,11 +17907,11 @@ snapshots: semver: 7.7.3 typescript: 5.9.3 - eslint-plugin-tailwindcss@3.18.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)): + eslint-plugin-tailwindcss@3.18.2(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0)): dependencies: fast-glob: 3.3.3 postcss: 8.5.6 - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.9.0) eslint-plugin-unicorn@63.0.0(eslint@9.39.2(jiti@2.6.1)): dependencies: @@ -19447,14 +19399,6 @@ snapshots: type-is: 2.0.1 vary: 1.1.2 - langium@4.2.1: - dependencies: - chevrotain: 11.1.2 - chevrotain-allstar: 0.3.1(chevrotain@11.1.2) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -19505,7 +19449,7 @@ snapshots: nano-spawn: 2.0.0 pidtree: 0.6.0 string-argv: 0.3.2 - yaml: 2.8.2 + yaml: 2.9.0 listhen@1.9.0: dependencies: @@ -19558,7 +19502,7 @@ snapshots: dependencies: p-locate: 6.0.0 - lodash-es@4.17.23: {} + lodash-es@4.18.1: {} lodash.defaults@4.2.0: {} @@ -19904,28 +19848,29 @@ snapshots: merge2@1.4.1: {} - mermaid@11.12.3: + mermaid@11.15.0: dependencies: '@braintree/sanitize-url': 7.1.2 '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 1.0.0 + '@mermaid-js/parser': 1.1.1 '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 cytoscape: 3.33.1 cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) cytoscape-fcose: 2.2.0(cytoscape@3.33.1) d3: 7.9.0 d3-sankey: 0.12.3 - dagre-d3-es: 7.0.13 + dagre-d3-es: 7.0.14 dayjs: 1.11.19 - dompurify: 3.3.1 + dompurify: 3.4.2 + es-toolkit: 1.46.1 katex: 0.16.33 khroma: 2.1.0 - lodash-es: 4.17.23 marked: 16.4.2 roughjs: 4.6.6 stylis: 4.3.6 ts-dedent: 2.2.0 - uuid: 11.1.0 + uuid: 11.1.1 micromark-core-commonmark@2.0.3: dependencies: @@ -20467,27 +20412,27 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt-icon@0.6.10(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)): + nuxt-icon@0.6.10(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)): dependencies: '@iconify/collections': 1.0.665 '@iconify/vue': 4.3.0(vue@3.5.30(typescript@5.9.3)) - '@nuxt/devtools-kit': 1.7.0(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + '@nuxt/devtools-kit': 1.7.0(magicast@0.5.2)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) '@nuxt/kit': 3.21.2(magicast@0.5.2) transitivePeerDependencies: - magicast - vite - vue - nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2): + nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@dxup/nuxt': 0.4.0(magicast@0.5.2)(typescript@5.9.3) '@nuxt/cli': 3.34.0(@nuxt/schema@3.21.2)(cac@6.7.14)(magicast@0.5.2) - '@nuxt/devtools': 3.2.4(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + '@nuxt/devtools': 3.2.4(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) '@nuxt/kit': 3.21.2(magicast@0.5.2) - '@nuxt/nitro-server': 3.21.2(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(typescript@5.9.3) + '@nuxt/nitro-server': 3.21.2(db0@0.3.4)(encoding@0.1.13)(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(typescript@5.9.3) '@nuxt/schema': 3.21.2 '@nuxt/telemetry': 2.7.0(@nuxt/kit@3.21.2(magicast@0.5.2)) - '@nuxt/vite-builder': 3.21.2(@types/node@25.0.7)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(yaml@2.8.2))(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))(yaml@2.8.2) + '@nuxt/vite-builder': 3.21.2(@types/node@25.0.7)(eslint@9.39.2(jiti@2.6.1))(magicast@0.5.2)(nuxt@3.21.2(@parcel/watcher@2.5.6)(@types/node@25.0.7)(@vue/compiler-sfc@3.5.30)(cac@6.7.14)(db0@0.3.4)(encoding@0.1.13)(eslint@9.39.2(jiti@2.6.1))(idb-keyval@6.2.2)(ioredis@5.10.1)(magicast@0.5.2)(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(yaml@2.9.0))(optionator@0.9.4)(rollup-plugin-visualizer@7.0.1(rollup@4.60.0))(rollup@4.60.0)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(vue@3.5.30(typescript@5.9.3))(yaml@2.9.0) '@unhead/vue': 2.1.12(vue@3.5.30(typescript@5.9.3)) '@vue/shared': 3.5.30 c12: 3.3.3(magicast@0.5.2) @@ -21111,23 +21056,23 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.6 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.6 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.9.0 - postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.2): + postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 2.6.1 postcss: 8.5.8 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.9.0 postcss-merge-longhand@7.0.5(postcss@8.5.8): dependencies: @@ -22461,11 +22406,11 @@ snapshots: tailwind-merge@3.5.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2)): + tailwindcss-animate@1.0.7(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0)): dependencies: - tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2) + tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.9.0) - tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2): + tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.9.0): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -22484,7 +22429,7 @@ snapshots: postcss: 8.5.6 postcss-import: 15.1.0(postcss@8.5.6) postcss-js: 4.1.0(postcss@8.5.6) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.8.2) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)(yaml@2.9.0) postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.11 @@ -22655,7 +22600,7 @@ snapshots: tsscmp@1.0.6: {} - tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): dependencies: bundle-require: 5.1.0(esbuild@0.27.2) cac: 6.7.14 @@ -22666,7 +22611,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.8.2) + postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(yaml@2.9.0) resolve-from: 5.0.0 rollup: 4.55.1 source-map: 0.7.6 @@ -23084,7 +23029,7 @@ snapshots: util-deprecate@1.0.2: {} - uuid@11.1.0: {} + uuid@11.1.1: {} vary@1.1.2: {} @@ -23110,15 +23055,15 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: birpc: 2.9.0 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) - vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-hot-client@2.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vite-node@3.2.4(@types/node@22.19.15)(sass@1.98.0)(terser@5.46.0): dependencies: @@ -23156,13 +23101,13 @@ snapshots: - supports-color - terser - vite-node@5.3.0(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vite-node@5.3.0(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0): dependencies: cac: 6.7.14 es-module-lexer: 2.0.0 obug: 2.1.1 pathe: 2.0.3 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -23176,7 +23121,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-checker@0.12.0(eslint@9.39.2(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@babel/code-frame': 7.29.0 chokidar: 4.0.3 @@ -23185,14 +23130,14 @@ snapshots: picomatch: 4.0.3 tiny-invariant: 1.3.3 tinyglobby: 0.2.15 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vscode-uri: 3.1.0 optionalDependencies: eslint: 9.39.2(jiti@2.6.1) optionator: 0.9.4 typescript: 5.9.3 - vite-plugin-inspect@11.3.3(@nuxt/kit@4.4.2(magicast@0.5.2))(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(@nuxt/kit@4.4.2(magicast@0.5.2))(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)): dependencies: ansis: 4.2.0 debug: 4.4.3 @@ -23202,29 +23147,29 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0)) optionalDependencies: '@nuxt/kit': 4.4.2(magicast@0.5.2) transitivePeerDependencies: - supports-color - vite-plugin-vue-tracer@1.3.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)): + vite-plugin-vue-tracer@1.3.0(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)): dependencies: estree-walker: 3.0.3 exsolve: 1.0.8 magic-string: 0.30.21 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.30(typescript@5.9.3) - vite-plugin-vuetify@2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3): + vite-plugin-vuetify@2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3): dependencies: '@vuetify/loader-shared': 2.1.2(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3) debug: 4.4.3 upath: 2.0.1 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.30(typescript@5.9.3) vuetify: 3.12.3(typescript@5.9.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.30(typescript@5.9.3)) transitivePeerDependencies: @@ -23252,7 +23197,7 @@ snapshots: sass: 1.98.0 terser: 5.46.0 - vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.3) @@ -23267,7 +23212,7 @@ snapshots: sass: 1.98.0 terser: 5.46.0 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.9.0 vitepress-codeblock-collapse@1.0.0(vue@3.5.30(typescript@5.9.3)): dependencies: @@ -23292,7 +23237,7 @@ snapshots: transitivePeerDependencies: - supports-color - vitepress@2.0.0-alpha.17(@types/node@25.0.7)(axios@1.13.6)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(oxc-minify@0.117.0)(postcss@8.5.8)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2): + vitepress@2.0.0-alpha.17(@types/node@25.0.7)(axios@1.13.6)(change-case@5.4.4)(fuse.js@7.1.0)(idb-keyval@6.2.2)(jiti@2.6.1)(oxc-minify@0.117.0)(postcss@8.5.8)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.9.0): dependencies: '@docsearch/css': 4.6.3 '@docsearch/js': 4.6.3 @@ -23302,7 +23247,7 @@ snapshots: '@shikijs/transformers': 3.22.0 '@shikijs/types': 3.23.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3)) + '@vitejs/plugin-vue': 6.0.5(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3)) '@vue/devtools-api': 8.1.1 '@vue/shared': 3.5.30 '@vueuse/core': 14.3.0(vue@3.5.30(typescript@5.9.3)) @@ -23311,7 +23256,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.2.0 shiki: 3.23.0 - vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0) vue: 3.5.30(typescript@5.9.3) optionalDependencies: oxc-minify: 0.117.0 @@ -23421,21 +23366,6 @@ snapshots: - supports-color - terser - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - vscode-uri@3.1.0: {} vue-bundle-renderer@2.2.0: @@ -23495,7 +23425,7 @@ snapshots: vue: 3.5.30(typescript@5.9.3) optionalDependencies: typescript: 5.9.3 - vite-plugin-vuetify: 2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3) + vite-plugin-vuetify: 2.1.3(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.30(typescript@5.9.3))(vuetify@3.12.3) w3c-keyname@2.2.8: {} @@ -23639,6 +23569,8 @@ snapshots: yaml@2.8.2: {} + yaml@2.9.0: {} + yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} diff --git a/runtime.lock.json b/runtime.lock.json index 7d9e8d92..5f2ca466 100644 --- a/runtime.lock.json +++ b/runtime.lock.json @@ -1,27 +1,27 @@ { - "version": "0.0.30", - "sourceRef": "v0.0.30", + "version": "0.0.31", + "sourceRef": "v0.0.31", "sourceRepository": "777genius/agent_teams_orchestrator", "releaseRepository": "777genius/agent-teams-ai", "releaseTag": "v1.2.0", "assets": { "darwin-arm64": { - "file": "agent-teams-runtime-darwin-arm64-v0.0.30.tar.gz", + "file": "agent-teams-runtime-darwin-arm64-v0.0.31.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "darwin-x64": { - "file": "agent-teams-runtime-darwin-x64-v0.0.30.tar.gz", + "file": "agent-teams-runtime-darwin-x64-v0.0.31.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "linux-x64": { - "file": "agent-teams-runtime-linux-x64-v0.0.30.tar.gz", + "file": "agent-teams-runtime-linux-x64-v0.0.31.tar.gz", "archiveKind": "tar.gz", "binaryName": "claude-multimodel" }, "win32-x64": { - "file": "agent-teams-runtime-win32-x64-v0.0.30.zip", + "file": "agent-teams-runtime-win32-x64-v0.0.31.zip", "archiveKind": "zip", "binaryName": "claude-multimodel.exe" } diff --git a/src/features/codex-account/main/composition/createCodexAccountFeature.ts b/src/features/codex-account/main/composition/createCodexAccountFeature.ts index ee29604f..e67aa484 100644 --- a/src/features/codex-account/main/composition/createCodexAccountFeature.ts +++ b/src/features/codex-account/main/composition/createCodexAccountFeature.ts @@ -770,8 +770,7 @@ class CodexAccountFeatureFacadeImpl implements CodexAccountFeatureFacade { } private async loadApiKeyAvailability(): Promise { - const storedKey = await this.apiKeyService.lookupPreferred('OPENAI_API_KEY'); - if (storedKey?.value.trim()) { + if (await this.apiKeyService.hasPreferred('OPENAI_API_KEY')) { return { available: true, source: 'stored', diff --git a/src/features/member-log-stream/main/adapters/output/sources/ClaudeMemberTranscriptPreviewSource.ts b/src/features/member-log-stream/main/adapters/output/sources/ClaudeMemberTranscriptPreviewSource.ts index fecff42d..4a070371 100644 --- a/src/features/member-log-stream/main/adapters/output/sources/ClaudeMemberTranscriptPreviewSource.ts +++ b/src/features/member-log-stream/main/adapters/output/sources/ClaudeMemberTranscriptPreviewSource.ts @@ -42,6 +42,7 @@ export class ClaudeMemberTranscriptPreviewSource implements MemberLogPreviewSour [input.memberName], { forceRefresh: input.forceRefresh === true, + includeTeamSubagentSessionDiscovery: false, } ); const dedupedRefs = dedupeMemberLogRefs(refs); diff --git a/src/main/index.ts b/src/main/index.ts index a95b1093..4c914fc7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -125,7 +125,6 @@ import { PluginCatalogService, PluginInstallationStateService, PluginInstallService, - RUNTIME_MANAGED_API_KEY_ENV_VARS, SkillsCatalogService, SkillsMutationService, SkillsWatcherService, @@ -1478,7 +1477,6 @@ async function initializeServices(): Promise { phase: 'settings', message: 'Loading secure settings...', }); - await apiKeyService.syncProcessEnv(RUNTIME_MANAGED_API_KEY_ENV_VARS); // warmup() and ensureInstalled() are deferred to after window creation // (did-finish-load handler) to avoid thread pool contention at startup. httpServer = new HttpServer(); diff --git a/src/main/ipc/extensions.ts b/src/main/ipc/extensions.ts index 3a3cccc0..18240f3e 100644 --- a/src/main/ipc/extensions.ts +++ b/src/main/ipc/extensions.ts @@ -28,10 +28,7 @@ import { } from '@preload/constants/ipcChannels'; import { createLogger } from '@shared/utils/logger'; -import { - type ApiKeyService, - RUNTIME_MANAGED_API_KEY_ENV_VARS, -} from '../services/extensions/apikeys/ApiKeyService'; +import { type ApiKeyService } from '../services/extensions/apikeys/ApiKeyService'; import { GitHubStarsService } from '../services/extensions/catalog/GitHubStarsService'; import type { ExtensionFacadeService } from '../services/extensions/ExtensionFacadeService'; @@ -396,12 +393,7 @@ async function handleApiKeysSave( ): Promise> { return wrapHandler('apiKeysSave', () => { if (!request) throw new Error('Request is required'); - return getApiKeyService() - .save(request) - .then(async (entry) => { - await getApiKeyService().syncProcessEnv(RUNTIME_MANAGED_API_KEY_ENV_VARS); - return entry; - }); + return getApiKeyService().save(request); }); } @@ -411,11 +403,7 @@ async function handleApiKeysDelete( ): Promise> { return wrapHandler('apiKeysDelete', () => { if (typeof id !== 'string' || !id) throw new Error('Key ID is required'); - return getApiKeyService() - .delete(id) - .then(async () => { - await getApiKeyService().syncProcessEnv(RUNTIME_MANAGED_API_KEY_ENV_VARS); - }); + return getApiKeyService().delete(id); }); } diff --git a/src/main/ipc/teams.ts b/src/main/ipc/teams.ts index 7b5487ce..b1a167d3 100644 --- a/src/main/ipc/teams.ts +++ b/src/main/ipc/teams.ts @@ -215,9 +215,9 @@ import type { TeamCreateResponse, TeamFastMode, TeamGetDataOptions, + TeamLaunchFailureDiagnosticsBundle, TeamLaunchRequest, TeamLaunchResponse, - TeamLaunchFailureDiagnosticsBundle, TeamMemberActivityMeta, TeamMessageNotificationData, TeamProviderBackendId, diff --git a/src/main/services/extensions/apikeys/ApiKeyService.ts b/src/main/services/extensions/apikeys/ApiKeyService.ts index 5c21f514..ab4e2c51 100644 --- a/src/main/services/extensions/apikeys/ApiKeyService.ts +++ b/src/main/services/extensions/apikeys/ApiKeyService.ts @@ -51,14 +51,11 @@ const PBKDF2_ITERATIONS = 100_000; const PBKDF2_KEY_BYTES = 32; const PBKDF2_SALT = 'claude-apikey-storage-v1'; -export const RUNTIME_MANAGED_API_KEY_ENV_VARS = ['GEMINI_API_KEY'] as const; - export class ApiKeyService { private readonly filePath: string; private cache: StoredApiKey[] | null = null; private aesKey: Buffer | null = null; private readonly reportedDecryptFailures = new Set(); - private readonly originalProcessEnv = new Map(); constructor(claudeDir?: string) { const baseDir = claudeDir ?? path.join(os.homedir(), '.claude'); @@ -185,6 +182,16 @@ export class ApiKeyService { }; } + async hasPreferred(envVarName: string, projectPath?: string): Promise { + const keys = await this.readStore(); + const preferred = this.pickPreferredKey( + keys.filter((key) => key.envVarName === envVarName), + projectPath + ); + + return Boolean(preferred?.encryptedValue); + } + async getStorageStatus(): Promise { const secure = this.isSecureBackend(); const backend = this.getBackendName(); @@ -204,31 +211,6 @@ export class ApiKeyService { }; } - async syncProcessEnv(envVarNames: readonly string[]): Promise { - if (!envVarNames.length) { - return; - } - - for (const envVarName of envVarNames) { - if (!this.originalProcessEnv.has(envVarName)) { - this.originalProcessEnv.set(envVarName, process.env[envVarName]); - } - - const nextValue = (await this.lookupPreferred(envVarName))?.value; - if (nextValue && nextValue.trim().length > 0) { - process.env[envVarName] = nextValue; - continue; - } - - const originalValue = this.originalProcessEnv.get(envVarName); - if (typeof originalValue === 'string' && originalValue.length > 0) { - process.env[envVarName] = originalValue; - } else { - delete process.env[envVarName]; - } - } - } - // ── Encryption ────────────────────────────────────────────────────────── /** diff --git a/src/main/services/extensions/index.ts b/src/main/services/extensions/index.ts index d2aaf042..4b862711 100644 --- a/src/main/services/extensions/index.ts +++ b/src/main/services/extensions/index.ts @@ -2,7 +2,7 @@ * Extension services barrel export. */ -export { ApiKeyService, RUNTIME_MANAGED_API_KEY_ENV_VARS } from './apikeys/ApiKeyService'; +export { ApiKeyService } from './apikeys/ApiKeyService'; export { GitHubStarsService } from './catalog/GitHubStarsService'; export { GlamaMcpEnrichmentService } from './catalog/GlamaMcpEnrichmentService'; export { McpCatalogAggregator } from './catalog/McpCatalogAggregator'; diff --git a/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts b/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts index 46521eeb..4abc7546 100644 --- a/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +++ b/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts @@ -18,6 +18,7 @@ async function buildManagementCliEnvForBinary(binaryPath: string): Promise { + const binaryPath = resolveAppManagedOpenCodeRuntimeBinaryPath(); + if (!binaryPath) { + return null; + } + try { + await execCli(binaryPath, ['--version'], { + timeout: VERSION_TIMEOUT_MS, + windowsHide: true, + }); + return binaryPath; + } catch { + return null; + } +} + function getExecutableName(): string { return process.platform === 'win32' ? 'opencode.exe' : 'opencode'; } @@ -150,10 +166,12 @@ function isLinuxMuslRuntime(): boolean { return !header?.glibcVersionRuntime; } -function getPlatformCandidates(): PlatformCandidate[] { - const arch = process.arch; - const musl = isLinuxMuslRuntime(); - if (process.platform === 'darwin') { +export function getOpenCodeRuntimePlatformCandidates( + platform: NodeJS.Platform = process.platform, + arch: string = process.arch, + musl: boolean = isLinuxMuslRuntime() +): PlatformCandidate[] { + if (platform === 'darwin') { if (arch === 'arm64') return [{ packageName: 'opencode-darwin-arm64', reason: 'macOS arm64' }]; if (arch === 'x64') { return [ @@ -162,7 +180,7 @@ function getPlatformCandidates(): PlatformCandidate[] { ]; } } - if (process.platform === 'linux') { + if (platform === 'linux') { if (arch === 'arm64') { return musl ? [ @@ -191,7 +209,7 @@ function getPlatformCandidates(): PlatformCandidate[] { ]; } } - if (process.platform === 'win32') { + if (platform === 'win32') { if (arch === 'arm64') return [{ packageName: 'opencode-windows-arm64', reason: 'Windows arm64' }]; if (arch === 'x64') { @@ -201,7 +219,7 @@ function getPlatformCandidates(): PlatformCandidate[] { ]; } } - throw new Error(`OpenCode app install is not supported on ${process.platform}/${arch}`); + throw new Error(`OpenCode app install is not supported on ${platform}/${arch}`); } async function fetchText(url: string): Promise { @@ -231,7 +249,7 @@ async function fetchPackageMetadata( return parsed; } -function verifyIntegrity(buffer: Buffer, integrity: string): void { +export function verifyOpenCodeRuntimePackageIntegrity(buffer: Buffer, integrity: string): void { const match = /^sha512-([A-Za-z0-9+/=]+)$/.exec(integrity.trim()); if (!match) { throw new Error('OpenCode package integrity is missing sha512 metadata'); @@ -320,7 +338,7 @@ function assertSafeTarPath(name: string): void { } } -function extractBinaryFromTarball(tarball: Buffer): Buffer { +export function extractOpenCodeRuntimeBinaryFromTarball(tarball: Buffer): Buffer { const tar = gunzipSync(tarball, { maxOutputLength: MAX_BINARY_BYTES + 1024 * 1024 }); const targetName = `package/bin/${getExecutableName()}`; let offset = 0; @@ -479,7 +497,7 @@ export class OpenCodeRuntimeInstallerService { try { this.publishProgress({ phase: 'checking', detail: 'Resolving latest OpenCode package...' }); const rootMetadata = await fetchPackageMetadata(ROOT_PACKAGE_NAME); - const candidates = getPlatformCandidates(); + const candidates = getOpenCodeRuntimePlatformCandidates(); const optionalDependencies = rootMetadata.optionalDependencies ?? {}; const selected = candidates.find((candidate) => optionalDependencies[candidate.packageName]); if (!selected) { @@ -498,10 +516,10 @@ export class OpenCodeRuntimeInstallerService { const tarball = await downloadTarball(platformMetadata.dist!.tarball!, (progress) => { this.publishProgress(progress); }); - verifyIntegrity(tarball, platformMetadata.dist!.integrity!); + verifyOpenCodeRuntimePackageIntegrity(tarball, platformMetadata.dist!.integrity!); this.publishProgress({ phase: 'installing', detail: 'Extracting OpenCode binary...' }); - const binary = extractBinaryFromTarball(tarball); + const binary = extractOpenCodeRuntimeBinaryFromTarball(tarball); const runtimeRoot = getRuntimeRootPath(); const tempDir = path.join(runtimeRoot, `installing-${process.pid}-${randomUUID()}`); const versionDir = path.join( diff --git a/src/main/services/infrastructure/PtyTerminalService.ts b/src/main/services/infrastructure/PtyTerminalService.ts index 64b43bdc..b2c45eb0 100644 --- a/src/main/services/infrastructure/PtyTerminalService.ts +++ b/src/main/services/infrastructure/PtyTerminalService.ts @@ -58,6 +58,7 @@ export class PtyTerminalService { const { env } = await buildProviderAwareCliEnv({ env: options?.env, connectionMode: 'augment', + allowStoredApiKeyDecryption: false, }); const shell = options?.command ?? diff --git a/src/main/services/runtime/ClaudeMultimodelBridgeService.ts b/src/main/services/runtime/ClaudeMultimodelBridgeService.ts index a94dd019..d484e20f 100644 --- a/src/main/services/runtime/ClaudeMultimodelBridgeService.ts +++ b/src/main/services/runtime/ClaudeMultimodelBridgeService.ts @@ -24,6 +24,8 @@ const logger = createLogger('ClaudeMultimodelBridgeService'); const PROVIDER_STATUS_TIMEOUT_MS = 10_000; const PROVIDER_MODELS_TIMEOUT_MS = 10_000; +const PROVIDER_STATUS_MAX_BUFFER_BYTES = 8 * 1024 * 1024; +const PROVIDER_MODELS_MAX_BUFFER_BYTES = 8 * 1024 * 1024; interface RuntimeExtensionCapabilityResponse { status?: 'supported' | 'read-only' | 'unsupported'; @@ -82,6 +84,7 @@ interface RuntimeProviderModelCatalogItemResponse { source?: 'anthropic-models-api' | 'app-server' | 'static-fallback'; badgeLabel?: string | null; statusMessage?: string | null; + metadata?: Record | null; } interface RuntimeProviderModelCatalogResponse { @@ -478,6 +481,21 @@ function collectRuntimeReasoningEfforts(values?: string[]): CliProviderReasoning ); } +function mapRuntimeProviderModelMetadata( + metadata?: Record | null +): NonNullable['models'][number]['metadata'] { + if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) { + return null; + } + const context = metadata.context; + return { + cost: metadata.cost ?? null, + context: typeof context === 'number' && Number.isFinite(context) ? context : null, + limits: metadata.limits ?? null, + free: metadata.free === true, + }; +} + function mapRuntimeProviderModelCatalog( providerId: CliProviderId, modelCatalog?: RuntimeProviderModelCatalogResponse | null @@ -540,6 +558,7 @@ function mapRuntimeProviderModelCatalog( source: itemSource, badgeLabel: model.badgeLabel ?? null, statusMessage: model.statusMessage ?? null, + metadata: mapRuntimeProviderModelMetadata(model.metadata), }, ]; }) ?? []; @@ -605,14 +624,18 @@ export class ClaudeMultimodelBridgeService { private async buildCliEnv( binaryPath: string ): Promise>> { - return buildProviderAwareCliEnv({ binaryPath }); + return buildProviderAwareCliEnv({ binaryPath, allowStoredApiKeyDecryption: false }); } private async buildProviderCliEnv( binaryPath: string, providerId: CliProviderId ): Promise>> { - return buildProviderAwareCliEnv({ binaryPath, providerId }); + return buildProviderAwareCliEnv({ + binaryPath, + providerId, + allowStoredApiKeyDecryption: false, + }); } private isUnifiedRuntimeUnsupported(error: unknown): boolean { @@ -769,6 +792,7 @@ export class ClaudeMultimodelBridgeService { ['runtime', 'status', '--json', '--provider', providerId], { timeout: PROVIDER_STATUS_TIMEOUT_MS, + maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, } ); @@ -850,6 +874,7 @@ export class ClaudeMultimodelBridgeService { ['runtime', 'verify', '--json', '--provider', 'opencode'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, + maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, } ); @@ -1048,6 +1073,7 @@ export class ClaudeMultimodelBridgeService { ['model', 'list', '--json', '--provider', 'all'], { timeout: PROVIDER_MODELS_TIMEOUT_MS, + maxBuffer: PROVIDER_MODELS_MAX_BUFFER_BYTES, env, } ); @@ -1119,6 +1145,7 @@ export class ClaudeMultimodelBridgeService { try { const { stdout } = await execCli(binaryPath, ['runtime', 'status', '--json'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, + maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }); const parsed = extractJsonObject(stdout); @@ -1145,10 +1172,12 @@ export class ClaudeMultimodelBridgeService { const [statusResult, modelsResult] = await Promise.allSettled([ execCli(binaryPath, ['auth', 'status', '--json', '--provider', 'all'], { timeout: PROVIDER_STATUS_TIMEOUT_MS, + maxBuffer: PROVIDER_STATUS_MAX_BUFFER_BYTES, env, }), execCli(binaryPath, ['model', 'list', '--json', '--provider', 'all'], { timeout: PROVIDER_MODELS_TIMEOUT_MS, + maxBuffer: PROVIDER_MODELS_MAX_BUFFER_BYTES, env, }), ]); diff --git a/src/main/services/runtime/CliProviderModelAvailabilityService.ts b/src/main/services/runtime/CliProviderModelAvailabilityService.ts index c6d60986..d4f5b9d8 100644 --- a/src/main/services/runtime/CliProviderModelAvailabilityService.ts +++ b/src/main/services/runtime/CliProviderModelAvailabilityService.ts @@ -193,6 +193,7 @@ export class CliProviderModelAvailabilityService { cliEnvPromise: buildProviderAwareCliEnv({ binaryPath: context.binaryPath, providerId: context.provider.providerId, + allowStoredApiKeyDecryption: false, }).then((result) => ({ env: result.env, providerArgs: result.providerArgs ?? [], diff --git a/src/main/services/runtime/ProviderConnectionService.ts b/src/main/services/runtime/ProviderConnectionService.ts index cc20165d..e697d5a4 100644 --- a/src/main/services/runtime/ProviderConnectionService.ts +++ b/src/main/services/runtime/ProviderConnectionService.ts @@ -34,6 +34,10 @@ type ExternalCredential = { value: string; } | null; +interface StoredApiKeyAccessOptions { + allowStoredApiKeyDecryption?: boolean; +} + const PROVIDER_CAPABILITIES: Record< CliProviderId, Pick @@ -258,7 +262,8 @@ export class ProviderConnectionService { async applyConfiguredConnectionEnv( env: NodeJS.ProcessEnv, providerId: CliProviderId, - runtimeBackendOverride?: string | null + runtimeBackendOverride?: string | null, + options?: StoredApiKeyAccessOptions ): Promise { if (providerId === 'anthropic') { const authMode = this.getConfiguredAuthMode(providerId); @@ -272,7 +277,7 @@ export class ProviderConnectionService { return env; } - const storedKey = await this.apiKeyService.lookupPreferred('ANTHROPIC_API_KEY'); + const storedKey = await this.lookupStoredApiKeyValue('ANTHROPIC_API_KEY', options); if (storedKey?.value.trim()) { env.ANTHROPIC_API_KEY = storedKey.value; delete env.ANTHROPIC_AUTH_TOKEN; @@ -288,6 +293,14 @@ export class ProviderConnectionService { return env; } + if (providerId === 'gemini') { + const storedKey = await this.lookupStoredApiKeyValue('GEMINI_API_KEY', options); + if (storedKey?.value.trim()) { + env.GEMINI_API_KEY = storedKey.value; + } + return env; + } + if (providerId !== 'codex') { return env; } @@ -310,7 +323,7 @@ export class ProviderConnectionService { return env; } - const resolvedApiKey = await this.resolveCodexApiKeyValue(env, runtimeBackendOverride); + const resolvedApiKey = await this.resolveCodexApiKeyValue(env, runtimeBackendOverride, options); if (readiness.effectiveAuthMode === 'api_key' && resolvedApiKey) { env.OPENAI_API_KEY = resolvedApiKey; env[CODEX_NATIVE_API_KEY_ENV_VAR] = resolvedApiKey; @@ -327,10 +340,13 @@ export class ProviderConnectionService { return env; } - async applyAllConfiguredConnectionEnv(env: NodeJS.ProcessEnv): Promise { + async applyAllConfiguredConnectionEnv( + env: NodeJS.ProcessEnv, + options?: StoredApiKeyAccessOptions + ): Promise { let nextEnv = env; for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode'] as const) { - nextEnv = await this.applyConfiguredConnectionEnv(nextEnv, providerId); + nextEnv = await this.applyConfiguredConnectionEnv(nextEnv, providerId, undefined, options); } return nextEnv; } @@ -338,20 +354,29 @@ export class ProviderConnectionService { async augmentConfiguredConnectionEnv( env: NodeJS.ProcessEnv, providerId: CliProviderId, - runtimeBackendOverride?: string | null + runtimeBackendOverride?: string | null, + options?: StoredApiKeyAccessOptions ): Promise { if (providerId === 'anthropic') { if (this.getConfiguredAuthMode(providerId) !== 'api_key') { return env; } - const storedKey = await this.apiKeyService.lookupPreferred('ANTHROPIC_API_KEY'); + const storedKey = await this.lookupStoredApiKeyValue('ANTHROPIC_API_KEY', options); if (storedKey?.value.trim()) { env.ANTHROPIC_API_KEY = storedKey.value; } return env; } + if (providerId === 'gemini') { + const storedKey = await this.lookupStoredApiKeyValue('GEMINI_API_KEY', options); + if (storedKey?.value.trim()) { + env.GEMINI_API_KEY = storedKey.value; + } + return env; + } + if (providerId !== 'codex') { return env; } @@ -374,7 +399,7 @@ export class ProviderConnectionService { return env; } - const resolvedApiKey = await this.resolveCodexApiKeyValue(env, runtimeBackendOverride); + const resolvedApiKey = await this.resolveCodexApiKeyValue(env, runtimeBackendOverride, options); if (readiness.effectiveAuthMode === 'api_key' && resolvedApiKey) { env.OPENAI_API_KEY = resolvedApiKey; env[CODEX_NATIVE_API_KEY_ENV_VAR] = resolvedApiKey; @@ -386,10 +411,13 @@ export class ProviderConnectionService { return env; } - async augmentAllConfiguredConnectionEnv(env: NodeJS.ProcessEnv): Promise { + async augmentAllConfiguredConnectionEnv( + env: NodeJS.ProcessEnv, + options?: StoredApiKeyAccessOptions + ): Promise { let nextEnv = env; for (const providerId of ['anthropic', 'codex', 'gemini', 'opencode'] as const) { - nextEnv = await this.augmentConfiguredConnectionEnv(nextEnv, providerId); + nextEnv = await this.augmentConfiguredConnectionEnv(nextEnv, providerId, undefined, options); } return nextEnv; } @@ -408,8 +436,7 @@ export class ProviderConnectionService { return null; } - const storedKey = await this.apiKeyService.lookupPreferred('ANTHROPIC_API_KEY'); - if (storedKey?.value.trim()) { + if (await this.hasStoredApiKey('ANTHROPIC_API_KEY')) { return null; } @@ -654,7 +681,7 @@ export class ProviderConnectionService { async getConnectionInfo(providerId: CliProviderId): Promise { const capabilities = PROVIDER_CAPABILITIES[providerId]; - const storedApiKey = await this.getStoredApiKey(providerId); + const hasStoredApiKey = await this.hasStoredProviderApiKey(providerId); const externalCredential = this.getExternalCredential(providerId); const codexSnapshot = providerId === 'codex' ? await this.getCodexAccountSnapshot() : null; const configurableAuthModes = capabilities.configurableAuthModes; @@ -665,11 +692,11 @@ export class ProviderConnectionService { const apiKeyConfigured = providerId === 'codex' ? (codexSnapshot?.apiKey.available ?? false) - : Boolean(storedApiKey?.value.trim() || externalCredential?.value.trim()); + : Boolean(hasStoredApiKey || externalCredential?.value.trim()); const apiKeySource = providerId === 'codex' ? (codexSnapshot?.apiKey.source ?? null) - : storedApiKey?.value.trim() + : hasStoredApiKey ? 'stored' : externalCredential?.value.trim() ? 'environment' @@ -677,7 +704,7 @@ export class ProviderConnectionService { const apiKeySourceLabel = providerId === 'codex' ? (codexSnapshot?.apiKey.sourceLabel ?? null) - : storedApiKey?.value.trim() + : hasStoredApiKey ? 'Stored in app' : (externalCredential?.label ?? null); @@ -709,11 +736,33 @@ export class ProviderConnectionService { }; } - private async getStoredApiKey( - providerId: CliProviderId - ): Promise<{ envVarName: string; value: string } | null> { + private async hasStoredProviderApiKey(providerId: CliProviderId): Promise { const envVarName = PROVIDER_API_KEY_ENV_VARS[providerId]; if (!envVarName) { + return false; + } + + return this.hasStoredApiKey(envVarName); + } + + private async hasStoredApiKey(envVarName: string): Promise { + const service = this.apiKeyService as ApiKeyService & { + hasPreferred?: (envVarName: string) => Promise; + }; + + if (typeof service.hasPreferred === 'function') { + return service.hasPreferred(envVarName); + } + + const storedKey = await service.lookupPreferred(envVarName); + return Boolean(storedKey?.value.trim()); + } + + private async lookupStoredApiKeyValue( + envVarName: string, + options?: StoredApiKeyAccessOptions + ): Promise<{ envVarName: string; value: string } | null> { + if (options?.allowStoredApiKeyDecryption === false) { return null; } @@ -736,17 +785,17 @@ export class ProviderConnectionService { (this.configManager.getConfig().providerConnections.codex.preferredAuthMode as | CodexAccountAuthMode | undefined) ?? 'auto'; - const storedKey = await this.apiKeyService.lookupPreferred('OPENAI_API_KEY'); + const hasStoredOpenAiKey = await this.hasStoredApiKey('OPENAI_API_KEY'); const externalCredential = this.getExternalCredential('codex'); - const apiKeyAvailable = Boolean(storedKey?.value.trim() || externalCredential?.value.trim()); + const apiKeyAvailable = Boolean(hasStoredOpenAiKey || externalCredential?.value.trim()); const apiKey = { available: apiKeyAvailable, - source: storedKey?.value.trim() + source: hasStoredOpenAiKey ? 'stored' : externalCredential?.value.trim() ? 'environment' : null, - sourceLabel: storedKey?.value.trim() ? 'Stored in app' : (externalCredential?.label ?? null), + sourceLabel: hasStoredOpenAiKey ? 'Stored in app' : (externalCredential?.label ?? null), } satisfies CodexAccountSnapshotDto['apiKey']; const readiness = evaluateCodexLaunchReadiness({ preferredAuthMode, @@ -786,10 +835,11 @@ export class ProviderConnectionService { private async resolveCodexApiKeyValue( env: NodeJS.ProcessEnv, - runtimeBackendOverride?: string | null + runtimeBackendOverride?: string | null, + options?: StoredApiKeyAccessOptions ): Promise { const codexRuntimeBackend = this.getConfiguredCodexRuntimeBackend(runtimeBackendOverride); - const storedKey = await this.apiKeyService.lookupPreferred('OPENAI_API_KEY'); + const storedKey = await this.lookupStoredApiKeyValue('OPENAI_API_KEY', options); const existingOpenAiKey = typeof env.OPENAI_API_KEY === 'string' && env.OPENAI_API_KEY.trim() ? env.OPENAI_API_KEY diff --git a/src/main/services/runtime/providerAwareCliEnv.ts b/src/main/services/runtime/providerAwareCliEnv.ts index 7e455aa4..f4861d6d 100644 --- a/src/main/services/runtime/providerAwareCliEnv.ts +++ b/src/main/services/runtime/providerAwareCliEnv.ts @@ -1,6 +1,6 @@ import { getCachedShellEnv } from '@main/utils/shellEnv'; -import { resolveAppManagedOpenCodeRuntimeBinaryPath } from '../infrastructure/OpenCodeRuntimeInstallerService'; +import { resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath } from '../infrastructure/OpenCodeRuntimeInstallerService'; import { buildRuntimeBaseEnv } from './buildRuntimeBaseEnv'; import { providerConnectionService } from './ProviderConnectionService'; @@ -16,6 +16,7 @@ export interface ProviderAwareCliEnvOptions { shellEnv?: NodeJS.ProcessEnv | null; env?: NodeJS.ProcessEnv; connectionMode?: 'strict' | 'augment'; + allowStoredApiKeyDecryption?: boolean; } export interface ProviderAwareCliEnvResult { @@ -28,6 +29,10 @@ export async function buildProviderAwareCliEnv( options: ProviderAwareCliEnvOptions = {} ): Promise { const connectionMode = options.connectionMode ?? 'strict'; + const storedApiKeyAccessArgs = + options.allowStoredApiKeyDecryption === undefined + ? [] + : [{ allowStoredApiKeyDecryption: options.allowStoredApiKeyDecryption }]; const shellEnv = options.shellEnv ?? getCachedShellEnv() ?? {}; const { env, resolvedProviderId } = buildRuntimeBaseEnv({ binaryPath: options.binaryPath, @@ -36,7 +41,7 @@ export async function buildProviderAwareCliEnv( shellEnv, env: options.env, }); - const appManagedOpenCodeBinary = resolveAppManagedOpenCodeRuntimeBinaryPath(); + const appManagedOpenCodeBinary = await resolveVerifiedAppManagedOpenCodeRuntimeBinaryPath(); if ( appManagedOpenCodeBinary && !env.CLAUDE_MULTIMODEL_OPENCODE_BIN_PATH && @@ -53,7 +58,8 @@ export async function buildProviderAwareCliEnv( await providerConnectionService.augmentConfiguredConnectionEnv( env, resolvedProviderId, - options.providerBackendId + options.providerBackendId, + ...storedApiKeyAccessArgs ); return { env, @@ -65,7 +71,8 @@ export async function buildProviderAwareCliEnv( await providerConnectionService.applyConfiguredConnectionEnv( env, resolvedProviderId, - options.providerBackendId + options.providerBackendId, + ...storedApiKeyAccessArgs ); return { @@ -87,7 +94,10 @@ export async function buildProviderAwareCliEnv( } if (connectionMode === 'augment') { - await providerConnectionService.augmentAllConfiguredConnectionEnv(env); + await providerConnectionService.augmentAllConfiguredConnectionEnv( + env, + ...storedApiKeyAccessArgs + ); return { env, connectionIssues: {}, @@ -95,7 +105,7 @@ export async function buildProviderAwareCliEnv( }; } - await providerConnectionService.applyAllConfiguredConnectionEnv(env); + await providerConnectionService.applyAllConfiguredConnectionEnv(env, ...storedApiKeyAccessArgs); return { env, connectionIssues: await providerConnectionService.getConfiguredConnectionIssues(env), diff --git a/src/main/services/team/TeamLaunchFailureArtifactPack.ts b/src/main/services/team/TeamLaunchFailureArtifactPack.ts index 40812bfd..4d01ee8d 100644 --- a/src/main/services/team/TeamLaunchFailureArtifactPack.ts +++ b/src/main/services/team/TeamLaunchFailureArtifactPack.ts @@ -58,6 +58,7 @@ export interface TeamLaunchFailureArtifactPackResult { } export type LaunchFailureArtifactClassificationCode = + | 'workspace_trust_required' | 'transport_rejected' | 'stdin_missing' | 'provider_quota' @@ -215,6 +216,13 @@ function firstEvidence(parts: readonly string[], pattern: RegExp): string[] { return evidence; } +const WORKSPACE_TRUST_FAILURE_PATTERN = + /workspace trust is not accepted|cannot start in headless process runtime because workspace trust|open that workspace once interactively and accept trust/i; + +export function isWorkspaceTrustLaunchFailureText(value: string): boolean { + return WORKSPACE_TRUST_FAILURE_PATTERN.test(value); +} + export function classifyLaunchFailureArtifact( input: TeamLaunchFailureArtifactPackInput ): LaunchFailureArtifactClassification { @@ -225,6 +233,11 @@ export function classifyLaunchFailureArtifact( confidence: number; pattern: RegExp; }[] = [ + { + code: 'workspace_trust_required', + confidence: 0.96, + pattern: WORKSPACE_TRUST_FAILURE_PATTERN, + }, { code: 'transport_rejected', confidence: 0.95, diff --git a/src/main/services/team/TeamLogSourceTracker.ts b/src/main/services/team/TeamLogSourceTracker.ts index 630e8fdd..efbd1e7b 100644 --- a/src/main/services/team/TeamLogSourceTracker.ts +++ b/src/main/services/team/TeamLogSourceTracker.ts @@ -1,5 +1,5 @@ -import { createLogger } from '@shared/utils/logger'; import { getTeamsBasePath } from '@main/utils/pathDecoder'; +import { createLogger } from '@shared/utils/logger'; import { watch } from 'chokidar'; import { createHash } from 'crypto'; import * as fs from 'fs/promises'; @@ -14,7 +14,6 @@ import { BOARD_TASK_CHANGES_DIRNAME, BOARD_TASK_LOG_FRESHNESS_DIRNAME, BOARD_TASK_LOG_FRESHNESS_FILE_SUFFIX, - TEAM_TASK_LOG_FRESHNESS_DIRNAME, classifyLogSourceWatcherEvent, getRelativeLogSourceParts, isAgentTranscriptFileName, @@ -22,6 +21,7 @@ import { MAX_PENDING_UNKNOWN_ROOT_SESSIONS, normalizeLogSourceSessionId, PENDING_UNKNOWN_ROOT_SESSION_TTL_MS, + TEAM_TASK_LOG_FRESHNESS_DIRNAME, } from './teamLogSourceWatchScope'; import type { TeamLogSourceLiveContext, TeamMemberLogsFinder } from './TeamMemberLogsFinder'; diff --git a/src/main/services/team/TeamMemberLogsFinder.ts b/src/main/services/team/TeamMemberLogsFinder.ts index a5564307..a8b6f49b 100644 --- a/src/main/services/team/TeamMemberLogsFinder.ts +++ b/src/main/services/team/TeamMemberLogsFinder.ts @@ -101,6 +101,11 @@ interface ProjectSessionDiscovery { knownMembers: Set; } +interface ProjectSessionDiscoveryOptions { + forceRefresh?: boolean; + includeTeamSubagentSessionDiscovery?: boolean; +} + interface TaskMentionIndex { exactTaskIds: Set; lowerTaskIds: Set; @@ -136,6 +141,7 @@ type FindRecentMemberLogFileRefsOptions = | { mtimeSinceMs?: number | null; forceRefresh?: boolean; + includeTeamSubagentSessionDiscovery?: boolean; }; export interface TeamLogSourceLiveContext { @@ -193,6 +199,14 @@ function collectTaskFreshnessRootDirs(candidates: readonly unknown[]): string[] return roots; } +function buildProjectSessionDiscoveryCacheKey( + teamName: string, + options?: ProjectSessionDiscoveryOptions +): string { + const subagentMode = options?.includeTeamSubagentSessionDiscovery === false ? 'known' : 'full'; + return `${teamName}\0${subagentMode}`; +} + export class TeamMemberLogsFinder { private readonly taskMentionIndexCache = new Map(); private readonly taskMentionIndexInFlight = new Map>(); @@ -200,6 +214,10 @@ export class TeamMemberLogsFinder { string, SubagentAttribution | RootSessionAttribution | null >(); + private readonly attributionInFlight = new Map< + string, + Promise + >(); private readonly discoveryCache = new Map< string, { @@ -209,7 +227,11 @@ export class TeamMemberLogsFinder { >(); private readonly discoveryInFlight = new Map< string, - { generation: number; promise: Promise } + { + generation: number; + promise: Promise; + forceRefresh: boolean; + } >(); private readonly discoveryGenerationByTeam = new Map(); @@ -979,10 +1001,16 @@ export class TeamMemberLogsFinder { ): Promise { const parsedOptions = typeof options === 'number' || options === null - ? { mtimeSinceMs: options ?? null, forceRefresh: false } + ? { + mtimeSinceMs: options ?? null, + forceRefresh: false, + includeTeamSubagentSessionDiscovery: true, + } : { mtimeSinceMs: options?.mtimeSinceMs ?? null, forceRefresh: options?.forceRefresh === true, + includeTeamSubagentSessionDiscovery: + options?.includeTeamSubagentSessionDiscovery !== false, }; const requestedMembersByKey = new Map(); for (const memberName of memberNames) { @@ -1001,6 +1029,7 @@ export class TeamMemberLogsFinder { const discovery = await this.discoverProjectSessions(teamName, { forceRefresh: parsedOptions.forceRefresh, + includeTeamSubagentSessionDiscovery: parsedOptions.includeTeamSubagentSessionDiscovery, }); if (!discovery) { return []; @@ -1147,41 +1176,50 @@ export class TeamMemberLogsFinder { private async discoverProjectSessions( teamName: string, - options?: { forceRefresh?: boolean } + options?: ProjectSessionDiscoveryOptions ): Promise { - let generation = this.discoveryGenerationByTeam.get(teamName) ?? 0; + const cacheKey = buildProjectSessionDiscoveryCacheKey(teamName, options); + let generation = this.discoveryGenerationByTeam.get(cacheKey) ?? 0; if (options?.forceRefresh) { + const inFlight = this.discoveryInFlight.get(cacheKey); + if (inFlight?.forceRefresh === true) { + return inFlight.promise; + } generation += 1; - this.discoveryGenerationByTeam.set(teamName, generation); - this.discoveryCache.delete(teamName); - this.discoveryInFlight.delete(teamName); + this.discoveryGenerationByTeam.set(cacheKey, generation); + this.discoveryCache.delete(cacheKey); } else { // Check discovery cache — avoids re-reading config/dirs within rapid successive calls - const cached = this.discoveryCache.get(teamName); + const cached = this.discoveryCache.get(cacheKey); if (cached && cached.expiresAt > Date.now()) { return cached.result; } - const inFlight = this.discoveryInFlight.get(teamName); + const inFlight = this.discoveryInFlight.get(cacheKey); if (inFlight) { return inFlight.promise; } } const promise = this.loadProjectSessionDiscovery(teamName, options, generation).finally(() => { - const current = this.discoveryInFlight.get(teamName); + const current = this.discoveryInFlight.get(cacheKey); if (current?.promise === promise) { - this.discoveryInFlight.delete(teamName); + this.discoveryInFlight.delete(cacheKey); } }); - this.discoveryInFlight.set(teamName, { generation, promise }); + this.discoveryInFlight.set(cacheKey, { + generation, + promise, + forceRefresh: options?.forceRefresh === true, + }); return promise; } private async loadProjectSessionDiscovery( teamName: string, - options: { forceRefresh?: boolean } | undefined, + options: ProjectSessionDiscoveryOptions | undefined, generation: number ): Promise { + const cacheKey = buildProjectSessionDiscoveryCacheKey(teamName, options); const context = await this.projectResolver.getContext(teamName, options); if (!context) { logger.debug(`No transcript context for team "${teamName}"`); @@ -1214,8 +1252,8 @@ export class TeamMemberLogsFinder { } const discovery = { projectDir, projectId, config, sessionIds, knownMembers }; - if ((this.discoveryGenerationByTeam.get(teamName) ?? 0) === generation) { - this.discoveryCache.set(teamName, { + if ((this.discoveryGenerationByTeam.get(cacheKey) ?? 0) === generation) { + this.discoveryCache.set(cacheKey, { result: discovery, expiresAt: Date.now() + DISCOVERY_CACHE_TTL, }); @@ -1585,7 +1623,15 @@ export class TeamMemberLogsFinder { if (this.attributionCache.has(cacheKey)) { return this.attributionCache.get(cacheKey) ?? null; } - const attribution = await this.attributeSubagent(filePath, knownMembers); + const existing = this.attributionInFlight.get(cacheKey); + if (existing) { + return (await existing) as SubagentAttribution | null; + } + const promise = this.attributeSubagent(filePath, knownMembers).finally(() => { + this.attributionInFlight.delete(cacheKey); + }); + this.attributionInFlight.set(cacheKey, promise); + const attribution = await promise; this.attributionCache.set(cacheKey, attribution); if (this.attributionCache.size > ATTRIBUTION_CACHE_MAX) { const oldestKey = this.attributionCache.keys().next().value; @@ -1604,7 +1650,15 @@ export class TeamMemberLogsFinder { if (this.attributionCache.has(cacheKey)) { return (this.attributionCache.get(cacheKey) as RootSessionAttribution | null) ?? null; } - const attribution = await this.attributeMemberSession(filePath, teamName, knownMembers); + const existing = this.attributionInFlight.get(cacheKey); + if (existing) { + return (await existing) as RootSessionAttribution | null; + } + const promise = this.attributeMemberSession(filePath, teamName, knownMembers).finally(() => { + this.attributionInFlight.delete(cacheKey); + }); + this.attributionInFlight.set(cacheKey, promise); + const attribution = await promise; this.attributionCache.set(cacheKey, attribution); if (this.attributionCache.size > ATTRIBUTION_CACHE_MAX) { const oldestKey = this.attributionCache.keys().next().value; diff --git a/src/main/services/team/TeamProvisioningService.ts b/src/main/services/team/TeamProvisioningService.ts index b2f939ef..004c8de0 100644 --- a/src/main/services/team/TeamProvisioningService.ts +++ b/src/main/services/team/TeamProvisioningService.ts @@ -298,7 +298,10 @@ import { import { TeamConfigReader } from './TeamConfigReader'; import { TeamInboxReader } from './TeamInboxReader'; import { TeamInboxWriter } from './TeamInboxWriter'; -import { writeTeamLaunchFailureArtifactPack } from './TeamLaunchFailureArtifactPack'; +import { + isWorkspaceTrustLaunchFailureText, + writeTeamLaunchFailureArtifactPack, +} from './TeamLaunchFailureArtifactPack'; import { createPersistedLaunchSnapshot, deriveTeamLaunchAggregateState, @@ -924,6 +927,12 @@ function classifyDeterministicBootstrapFailure(reason: string): { } { const normalizedReason = reason.trim(); const lower = normalizedReason.toLowerCase(); + if (isWorkspaceTrustLaunchFailureText(normalizedReason)) { + return { + title: 'Workspace trust required', + normalizedReason, + }; + } if (lower.includes('disabled by kill switch')) { return { title: 'Deterministic bootstrap disabled', @@ -1860,6 +1869,8 @@ interface ProvisioningRun { lastMemberSpawnAuditConfigReadWarningAt: number; /** Per-member warning throttle for repeated "missing from config" logs. */ lastMemberSpawnAuditMissingWarningAt: Map; + /** Prevents duplicate Team Launched notifications for the same live run. */ + teamLaunchedNotificationFired?: boolean; } const PROVISIONING_TRACE_STORAGE_LIMIT = 500; @@ -11825,12 +11836,7 @@ export class TeamProvisioningService { ); this.invalidateRuntimeSnapshotCaches(input.teamName); if (trackedUpdate.changed) { - this.teamChangeEmitter?.({ - type: 'member-spawn', - teamName: input.teamName, - runId: input.runId, - detail: input.memberName, - }); + this.emitMemberSpawnChange(trackedUpdate.run, input.memberName); } return; } @@ -24788,7 +24794,7 @@ export class TeamProvisioningService { private emitMemberSpawnChange( run: Pick, memberName: string - ) { + ): void { this.invalidateMemberSpawnStatusesCache(run.teamName); this.teamChangeEmitter?.({ type: 'member-spawn', @@ -24796,6 +24802,39 @@ export class TeamProvisioningService { runId: run.runId, detail: memberName, }); + const trackedRun = this.runs.get(run.runId); + if (trackedRun?.teamName === run.teamName) { + void this.maybeFireTeamLaunchedNotificationWhenAllMembersJoined(trackedRun); + } + } + + private async maybeFireTeamLaunchedNotificationWhenAllMembersJoined( + run: ProvisioningRun + ): Promise { + if ( + !run.isLaunch || + run.teamLaunchedNotificationFired || + run.processKilled || + run.cancelRequested || + !this.isProvisioningRunPromotedToAlive(run) || + !this.areAllExpectedLaunchMembersConfirmed(run) + ) { + return; + } + + await this.fireTeamLaunchedNotification(run); + } + + private areAllExpectedLaunchMembersConfirmed(run: ProvisioningRun): boolean { + const expectedMembers = run.expectedMembers ?? []; + if (expectedMembers.length === 0) { + return false; + } + + return expectedMembers.every((memberName) => { + const member = run.memberSpawnStatuses.get(memberName); + return member?.launchState === 'confirmed_alive' || member?.bootstrapConfirmed === true; + }); } private async publishMixedSecondaryLaneStatusChange( @@ -30499,12 +30538,21 @@ export class TeamProvisioningService { * Uses the existing addTeamNotification() pipeline. */ private async fireTeamLaunchedNotification(run: ProvisioningRun): Promise { + if (run.teamLaunchedNotificationFired) { + return; + } + run.teamLaunchedNotificationFired = true; + try { const config = ConfigManager.getInstance().getConfig(); const suppressToast = !config.notifications.notifyOnTeamLaunched; const displayName = run.request.displayName || run.teamName; + const joinedCount = run.expectedMembers?.length ?? 0; + const allJoined = joinedCount > 0 && this.areAllExpectedLaunchMembersConfirmed(run); const body = run.isLaunch - ? `Team "${displayName}" has been launched and is ready for tasks.` + ? allJoined + ? `Team "${displayName}" has been launched - all ${joinedCount} teammates joined and are ready for tasks.` + : `Team "${displayName}" has been launched and is ready for tasks.` : `Team "${displayName}" has been provisioned and is ready for tasks.`; await NotificationManager.getInstance().addTeamNotification({ @@ -30520,6 +30568,7 @@ export class TeamProvisioningService { suppressToast, }); } catch (error) { + run.teamLaunchedNotificationFired = false; logger.warn( `[${run.teamName}] Failed to fire team_launched notification: ${ error instanceof Error ? error.message : String(error) diff --git a/src/main/services/team/TeamTranscriptProjectResolver.ts b/src/main/services/team/TeamTranscriptProjectResolver.ts index e80221d4..b37bb4a1 100644 --- a/src/main/services/team/TeamTranscriptProjectResolver.ts +++ b/src/main/services/team/TeamTranscriptProjectResolver.ts @@ -22,6 +22,7 @@ const logger = createLogger('Service:TeamTranscriptProjectResolver'); const SESSION_DISCOVERY_CACHE_TTL = 30_000; const TEAM_AFFINITY_SCAN_LINES = 40; const ROOT_DISCOVERY_CONCURRENCY = 12; +const FAST_CONTEXT_ROOT_DISCOVERY_MTIME_GRACE_MS = 24 * 60 * 60_000; type ProjectEvidenceSource = | 'projectPath' @@ -51,6 +52,11 @@ interface TeamTranscriptProjectConfigReader { getConfigSnapshot?: (teamName: string) => Promise; } +interface TeamTranscriptProjectContextOptions { + forceRefresh?: boolean; + includeTeamSubagentSessionDiscovery?: boolean; +} + type ScannedSessionProjectMatch = Omit & { projectPath?: string; }; @@ -72,6 +78,45 @@ function isSessionDirectoryName(name: string): boolean { return name !== 'memory' && !name.startsWith('.'); } +function buildContextCacheKey( + teamName: string, + options?: TeamTranscriptProjectContextOptions +): string { + const subagentMode = options?.includeTeamSubagentSessionDiscovery === false ? 'known' : 'full'; + return `${teamName}\0${subagentMode}`; +} + +function parseTimestampMs(value: unknown): number | null { + if (typeof value === 'number' && Number.isFinite(value)) { + return value; + } + if (typeof value === 'string' && value.trim().length > 0) { + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : null; + } + return null; +} + +function teamLifecycleMtimeCutoffMs(config: TeamConfig): number | null { + const timestamps: number[] = []; + const createdAt = parseTimestampMs((config as { createdAt?: unknown }).createdAt); + if (createdAt !== null) { + timestamps.push(createdAt); + } + + for (const member of config.members ?? []) { + const joinedAt = parseTimestampMs((member as { joinedAt?: unknown }).joinedAt); + if (joinedAt !== null) { + timestamps.push(joinedAt); + } + } + + if (timestamps.length === 0) { + return null; + } + return Math.max(0, Math.min(...timestamps) - FAST_CONTEXT_ROOT_DISCOVERY_MTIME_GRACE_MS); +} + function normalizeProjectPathCandidate(value: unknown): string | null { if (typeof value !== 'string') { return null; @@ -207,12 +252,21 @@ export class TeamTranscriptProjectResolver { : this.configReader.getConfig(teamName); } + private deleteContextCacheForTeam(teamName: string): void { + this.contextCache.delete(teamName); + for (const key of this.contextCache.keys()) { + if (key === teamName || key.startsWith(`${teamName}\0`)) { + this.contextCache.delete(key); + } + } + } + async getLiveBaseContext( teamName: string, options?: { forceRefresh?: boolean; extraProjectPathCandidates?: readonly unknown[] } ): Promise { if (options?.forceRefresh) { - this.contextCache.delete(teamName); + this.deleteContextCacheForTeam(teamName); } const config = await this.readConfigForObservation(teamName); @@ -244,13 +298,14 @@ export class TeamTranscriptProjectResolver { async getContext( teamName: string, - options?: { forceRefresh?: boolean } + options?: TeamTranscriptProjectContextOptions ): Promise { + const cacheKey = buildContextCacheKey(teamName, options); if (options?.forceRefresh) { - this.contextCache.delete(teamName); + this.deleteContextCacheForTeam(teamName); } - const cached = this.contextCache.get(teamName); + const cached = this.contextCache.get(cacheKey); if (cached && cached.expiresAt > Date.now()) { return cached.value; } @@ -282,7 +337,8 @@ export class TeamTranscriptProjectResolver { const sessionIds = await this.discoverSessionIds( teamName, resolution.projectDir, - resolvedConfig + resolvedConfig, + options ); const value = { projectDir: resolution.projectDir, @@ -290,7 +346,7 @@ export class TeamTranscriptProjectResolver { config: resolvedConfig, sessionIds, }; - this.contextCache.set(teamName, { + this.contextCache.set(cacheKey, { value, expiresAt: Date.now() + SESSION_DISCOVERY_CACHE_TTL, }); @@ -741,12 +797,20 @@ export class TeamTranscriptProjectResolver { private async discoverSessionIds( teamName: string, projectDir: string, - config: TeamConfig + config: TeamConfig, + options?: TeamTranscriptProjectContextOptions ): Promise { const knownSessionIds = collectKnownSessionIds(config); - const [teamRootSessionIds, sessionDirIds] = await Promise.all([ - this.listTeamRootSessionIds(projectDir, teamName), - this.listSessionDirIds(projectDir), + const includeTeamSubagentSessionDiscovery = + options?.includeTeamSubagentSessionDiscovery !== false; + const rootMtimeSinceMs = includeTeamSubagentSessionDiscovery + ? null + : teamLifecycleMtimeCutoffMs(config); + const [teamRootSessionIds, teamSubagentSessionIds] = await Promise.all([ + this.listTeamRootSessionIds(projectDir, teamName, rootMtimeSinceMs), + includeTeamSubagentSessionDiscovery + ? this.listTeamSubagentSessionIds(projectDir, teamName) + : Promise.resolve([]), ]); const orderedSessionIds: string[] = []; @@ -762,7 +826,7 @@ export class TeamTranscriptProjectResolver { for (const sessionId of knownSessionIds) { push(sessionId); } - for (const sessionId of [...teamRootSessionIds, ...sessionDirIds].sort((left, right) => + for (const sessionId of [...teamRootSessionIds, ...teamSubagentSessionIds].sort((left, right) => left.localeCompare(right) )) { push(sessionId); @@ -816,21 +880,69 @@ export class TeamTranscriptProjectResolver { } } - private async listSessionDirIds(projectDir: string): Promise { + private async listTeamSubagentSessionIds( + projectDir: string, + teamName: string + ): Promise { const dirEntries = await this.readProjectDirEntries(projectDir); if (!dirEntries) { return []; } - return dirEntries - .filter((entry) => entry.isDirectory() && isSessionDirectoryName(entry.name)) - .map((entry) => entry.name); + const sessionDirEntries = dirEntries.filter( + (entry) => entry.isDirectory() && isSessionDirectoryName(entry.name) + ); + const discovered = new Set(); + let nextIndex = 0; + + const scanNextSessionDir = async (): Promise => { + while (nextIndex < sessionDirEntries.length) { + const entry = sessionDirEntries[nextIndex++]; + const subagentsDir = path.join(projectDir, entry.name, 'subagents'); + let subagentEntries: Dirent[]; + try { + subagentEntries = await fs.readdir(subagentsDir, { withFileTypes: true }); + } catch { + continue; + } + + for (const subagentEntry of subagentEntries) { + if (!subagentEntry.isFile()) { + continue; + } + if (!subagentEntry.name.endsWith('.jsonl')) { + continue; + } + if (!subagentEntry.name.startsWith('agent-')) { + continue; + } + if (subagentEntry.name.startsWith('agent-acompact')) { + continue; + } + + const filePath = path.join(subagentsDir, subagentEntry.name); + if (await this.fileBelongsToTeam(filePath, teamName)) { + discovered.add(entry.name); + break; + } + } + } + }; + + await Promise.all( + Array.from({ length: Math.min(ROOT_DISCOVERY_CONCURRENCY, sessionDirEntries.length) }, () => + scanNextSessionDir() + ) + ); + + return [...discovered]; } private async collectRootJsonlSessionIds( rootJsonlEntries: Dirent[], projectDir: string, - teamName: string + teamName: string, + mtimeSinceMs?: number | null ): Promise { const discovered = new Set(); let nextIndex = 0; @@ -839,6 +951,16 @@ export class TeamTranscriptProjectResolver { while (nextIndex < rootJsonlEntries.length) { const entry = rootJsonlEntries[nextIndex++]; const filePath = path.join(projectDir, entry.name); + if (mtimeSinceMs != null) { + try { + const stat = await fs.stat(filePath); + if (!stat.isFile() || stat.mtimeMs < mtimeSinceMs) { + continue; + } + } catch { + continue; + } + } if (!(await this.fileBelongsToTeam(filePath, teamName))) { continue; } @@ -855,7 +977,11 @@ export class TeamTranscriptProjectResolver { return [...discovered]; } - private async listTeamRootSessionIds(projectDir: string, teamName: string): Promise { + private async listTeamRootSessionIds( + projectDir: string, + teamName: string, + mtimeSinceMs?: number | null + ): Promise { const dirEntries = await this.readProjectDirEntries(projectDir); if (!dirEntries) { return []; @@ -864,7 +990,7 @@ export class TeamTranscriptProjectResolver { const rootJsonlEntries = dirEntries.filter( (entry) => entry.isFile() && entry.name.endsWith('.jsonl') ); - return this.collectRootJsonlSessionIds(rootJsonlEntries, projectDir, teamName); + return this.collectRootJsonlSessionIds(rootJsonlEntries, projectDir, teamName, mtimeSinceMs); } private async fileBelongsToTeam(filePath: string, teamName: string): Promise { diff --git a/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts b/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts index e9726ee4..7a3d4946 100644 --- a/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts +++ b/src/main/services/team/opencode/bridge/OpenCodeReadinessBridge.ts @@ -1,7 +1,8 @@ import { randomUUID } from 'crypto'; -import type { OpenCodeTeamRuntimeBridgePort } from '../../runtime/OpenCodeTeamRuntimeAdapter'; import { stableHash } from './OpenCodeBridgeCommandContract'; + +import type { OpenCodeTeamRuntimeBridgePort } from '../../runtime/OpenCodeTeamRuntimeAdapter'; import type { OpenCodeTeamLaunchReadiness, OpenCodeTeamLaunchReadinessState, @@ -14,10 +15,10 @@ import type { OpenCodeBridgeFailureKind, OpenCodeBridgeResult, OpenCodeBridgeRuntimeSnapshot, - OpenCodeCommandStatusCommandBody, - OpenCodeCommandStatusCommandData, OpenCodeCleanupHostsCommandBody, OpenCodeCleanupHostsCommandData, + OpenCodeCommandStatusCommandBody, + OpenCodeCommandStatusCommandData, OpenCodeLaunchTeamCommandBody, OpenCodeLaunchTeamCommandData, OpenCodeObserveMessageDeliveryCommandBody, @@ -79,10 +80,6 @@ const DEFAULT_CLEANUP_TIMEOUT_MS = 10_000; const DEFAULT_BACKFILL_TIMEOUT_MS = 45_000; const DEFAULT_COMMAND_STATUS_TIMEOUT_MS = 5_000; -function isCommandStatusRecoveryEnabled(): boolean { - return process.env.CLAUDE_TEAM_OPENCODE_COMMAND_STATUS_RECOVERY === '1'; -} - function buildSendPayloadHash(input: OpenCodeSendMessageCommandBody): string { const { payloadHash: _payloadHash, ...hashable } = input; return stableHash(hashable); @@ -246,7 +243,7 @@ export class OpenCodeReadinessBridge implements OpenCodeTeamRuntimeBridgePort { if (result.ok) { return result.data; } - if (result.error.kind === 'timeout' && isCommandStatusRecoveryEnabled()) { + if (result.error.kind === 'timeout') { const recovered = await this.recoverTimedOutSendMessage({ originalRequestId: commandRequestId, body, diff --git a/src/main/services/team/opencode/delivery/OpenCodeRuntimeDeliveryAdvisoryPolicy.ts b/src/main/services/team/opencode/delivery/OpenCodeRuntimeDeliveryAdvisoryPolicy.ts index dd1bd57b..b2b8ab28 100644 --- a/src/main/services/team/opencode/delivery/OpenCodeRuntimeDeliveryAdvisoryPolicy.ts +++ b/src/main/services/team/opencode/delivery/OpenCodeRuntimeDeliveryAdvisoryPolicy.ts @@ -1,8 +1,9 @@ +import { classifyRuntimeDiagnostic } from '../../runtime/RuntimeDiagnosticClassifier'; + import { isActionRequiredOpenCodeRuntimeDeliveryReason, selectOpenCodeRuntimeDeliveryReason, } from './OpenCodeRuntimeDeliveryDiagnostics'; -import { classifyRuntimeDiagnostic } from '../../runtime/RuntimeDiagnosticClassifier'; import type { OpenCodePromptDeliveryLedgerRecord } from './OpenCodePromptDeliveryLedger'; import type { diff --git a/src/renderer/components/dashboard/CliStatusBanner.tsx b/src/renderer/components/dashboard/CliStatusBanner.tsx index 6f849816..49b072be 100644 --- a/src/renderer/components/dashboard/CliStatusBanner.tsx +++ b/src/renderer/components/dashboard/CliStatusBanner.tsx @@ -621,6 +621,13 @@ function getOpenCodeInstallLabel(status: OpenCodeRuntimeStatus | null): string { return 'Install'; } +const OPENCODE_PROVIDER_FREE_BADGE_TITLE = + 'OpenCode includes free model options such as Big Pickle when available in your setup. OpenRouter through OpenCode can also expose free models, but not every OpenCode/OpenRouter model is free. Availability and limits may change.'; + +function shouldShowOpenCodeProviderFreeBadge(provider: CliProviderStatus): boolean { + return provider.providerId === 'opencode'; +} + const InstalledBanner = ({ cliStatus, sourceProviderMap, @@ -847,6 +854,14 @@ const InstalledBanner = ({ ? getProviderLabel(provider.providerId) : provider.displayName} + {shouldShowOpenCodeProviderFreeBadge(provider) ? ( + + Free models + + ) : null} )} diff --git a/src/renderer/components/extensions/ExtensionStoreView.tsx b/src/renderer/components/extensions/ExtensionStoreView.tsx index 874e6b08..1dac5996 100644 --- a/src/renderer/components/extensions/ExtensionStoreView.tsx +++ b/src/renderer/components/extensions/ExtensionStoreView.tsx @@ -270,11 +270,6 @@ export const ExtensionStoreView = (): React.JSX.Element => { void mcpFetchInstalled(projectPath ?? undefined); }, [mcpFetchInstalled, projectPath]); - // Fetch API keys on mount - useEffect(() => { - void fetchApiKeys(); - }, [fetchApiKeys]); - // Fetch Skills catalog on mount / project change useEffect(() => { void fetchSkillsCatalog(projectPath ?? undefined); @@ -287,7 +282,9 @@ export const ExtensionStoreView = (): React.JSX.Element => { bootstrapCliStatus, fetchCliStatus, }); - void fetchApiKeys(); + if (tabState.activeSubTab === 'api-keys') { + void fetchApiKeys(); + } void fetchPluginCatalog(projectPath ?? undefined, true); void mcpBrowse(); // re-fetch first page void mcpFetchInstalled(projectPath ?? undefined); @@ -302,6 +299,7 @@ export const ExtensionStoreView = (): React.JSX.Element => { mcpBrowse, mcpFetchInstalled, projectPath, + tabState.activeSubTab, ]); const isRefreshing = diff --git a/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx b/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx index 595b06b4..4d70cc9d 100644 --- a/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +++ b/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx @@ -36,6 +36,7 @@ export const ApiKeysPanel = ({ apiKeysLoading, apiKeysError, storageStatus, + fetchApiKeys, fetchStorageStatus, cliStatus, cliStatusLoading, @@ -46,6 +47,7 @@ export const ApiKeysPanel = ({ apiKeysLoading: s.apiKeysLoading, apiKeysError: s.apiKeysError, storageStatus: s.apiKeyStorageStatus, + fetchApiKeys: s.fetchApiKeys, fetchStorageStatus: s.fetchApiKeyStorageStatus, cliStatus: s.cliStatus, cliStatusLoading: s.cliStatusLoading, @@ -85,8 +87,9 @@ export const ApiKeysPanel = ({ const [editingKey, setEditingKey] = useState(null); useEffect(() => { + void fetchApiKeys(); void fetchStorageStatus(); - }, [fetchStorageStatus]); + }, [fetchApiKeys, fetchStorageStatus]); const handleAdd = () => { setEditingKey(null); diff --git a/src/renderer/components/runtime/ProviderModelBadges.tsx b/src/renderer/components/runtime/ProviderModelBadges.tsx index 3c7ddac2..93e7b174 100644 --- a/src/renderer/components/runtime/ProviderModelBadges.tsx +++ b/src/renderer/components/runtime/ProviderModelBadges.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useLayoutEffect, useRef, useState } from 'react'; import { cn } from '@renderer/lib/utils'; import { @@ -56,13 +56,43 @@ function getCatalogBadgeLabel( return catalogItem?.badgeLabel?.trim() || null; } +function normalizeBadgeText(value: string): string { + return value.trim().replace(/\s+/g, ' ').toLowerCase(); +} + +function shouldRenderCatalogBadge(modelLabel: string, catalogBadgeLabel: string | null): boolean { + if (!catalogBadgeLabel) { + return false; + } + return normalizeBadgeText(modelLabel) !== normalizeBadgeText(catalogBadgeLabel); +} + +function hasChildAfterRowLimit(container: HTMLElement, rowLimit: number): boolean { + const rowTops: number[] = []; + const children = Array.from(container.children) as HTMLElement[]; + + for (const child of children) { + const top = child.offsetTop; + let rowIndex = rowTops.findIndex((rowTop) => Math.abs(rowTop - top) <= 1); + if (rowIndex < 0) { + rowTops.push(top); + rowIndex = rowTops.length - 1; + } + if (rowIndex >= rowLimit) { + return true; + } + } + + return false; +} + export const ProviderModelBadges = ({ providerId, models, modelAvailability, providerStatus, collapseAfter, - expandedMaxHeightPx = 200, + maxCollapsedRows, }: { readonly providerId: CliProviderId; readonly models: string[]; @@ -72,16 +102,73 @@ export const ProviderModelBadges = ({ 'providerId' | 'authMethod' | 'backend' | 'modelCatalog' > | null; readonly collapseAfter?: number; - readonly expandedMaxHeightPx?: number; + readonly maxCollapsedRows?: number; }): React.JSX.Element => { const [expanded, setExpanded] = useState(false); + const [collapsedModelLimit, setCollapsedModelLimit] = useState(null); + const [measureTick, setMeasureTick] = useState(0); + const listRef = useRef(null); const visibleModels = getVisibleTeamProviderModels(providerId, models, providerStatus); const displayModelAvailability = providerId === 'opencode' ? undefined : modelAvailability; const shouldCollapse = typeof collapseAfter === 'number' && collapseAfter > 0 && visibleModels.length > collapseAfter; + const collapsedBaseLimit = shouldCollapse ? collapseAfter : visibleModels.length; + const collapsedLimit = + shouldCollapse && !expanded + ? Math.max(0, Math.min(collapsedModelLimit ?? collapsedBaseLimit, collapsedBaseLimit)) + : visibleModels.length; const displayedModels = - shouldCollapse && !expanded ? visibleModels.slice(0, collapseAfter) : visibleModels; - const hiddenCount = shouldCollapse ? visibleModels.length - collapseAfter : 0; + shouldCollapse && !expanded ? visibleModels.slice(0, collapsedLimit) : visibleModels; + const hiddenCount = shouldCollapse ? visibleModels.length - displayedModels.length : 0; + + useLayoutEffect(() => { + setCollapsedModelLimit(null); + }, [collapseAfter, maxCollapsedRows, models, providerStatus]); + + useLayoutEffect(() => { + if (!shouldCollapse || expanded || !maxCollapsedRows || maxCollapsedRows < 1) { + return; + } + + const container = listRef.current; + if (!container) { + return; + } + + if (!hasChildAfterRowLimit(container, maxCollapsedRows)) { + return; + } + + const nextLimit = Math.max(0, collapsedLimit - 1); + if (nextLimit !== collapsedLimit) { + setCollapsedModelLimit(nextLimit); + } + }, [collapsedLimit, expanded, maxCollapsedRows, measureTick, shouldCollapse]); + + useLayoutEffect(() => { + if (!shouldCollapse || expanded || !maxCollapsedRows || typeof ResizeObserver === 'undefined') { + return; + } + + const container = listRef.current; + if (!container) { + return; + } + + let lastWidth = container.clientWidth; + const observer = new ResizeObserver((entries) => { + const width = Math.round(entries[0]?.contentRect.width ?? container.clientWidth); + if (width === lastWidth) { + return; + } + lastWidth = width; + setCollapsedModelLimit(null); + setMeasureTick((value) => value + 1); + }); + + observer.observe(container); + return () => observer.disconnect(); + }, [expanded, maxCollapsedRows, shouldCollapse]); const badgeClassName = 'inline-flex items-center gap-1 rounded-md border px-1.5 py-px font-mono text-[10px] leading-4'; @@ -92,20 +179,18 @@ export const ProviderModelBadges = ({ }; const buttonClassName = 'inline-flex items-center gap-1 rounded-full border border-[rgba(59,130,246,0.35)] bg-[rgba(59,130,246,0.12)] px-2 py-px text-[10px] font-medium leading-4 text-[rgb(147,197,253)] transition-colors hover:border-[rgba(59,130,246,0.55)] hover:bg-[rgba(59,130,246,0.18)] hover:text-[rgb(191,219,254)]'; - const listClassName = cn('flex flex-wrap gap-1.5', expanded && shouldCollapse ? 'pr-1' : null); - const listStyle = - expanded && shouldCollapse - ? ({ maxHeight: expandedMaxHeightPx, overflowY: 'auto' } as const) - : undefined; + const listClassName = cn('flex flex-wrap gap-1.5'); const renderModelBadge = (model: string, index: number): React.JSX.Element => { const availabilityStatus = getAvailabilityStatus(model, displayModelAvailability); const availabilityReason = getAvailabilityReason(model, displayModelAvailability); const availabilityChip = getAvailabilityChip(availabilityStatus); + const modelLabel = formatModelBadgeLabel(providerId, model); const catalogBadgeLabel = getCatalogBadgeLabel(model, providerStatus); + const showCatalogBadge = shouldRenderCatalogBadge(modelLabel, catalogBadgeLabel); const title = [ availabilityReason ?? availabilityChip, - catalogBadgeLabel === 'Free' + showCatalogBadge && catalogBadgeLabel === 'Free' ? 'Reported by OpenCode metadata. Availability and limits may change.' : null, ] @@ -119,8 +204,8 @@ export const ProviderModelBadges = ({ style={badgeStyle} title={title || undefined} > - {formatModelBadgeLabel(providerId, model)} - {catalogBadgeLabel ? ( + {modelLabel} + {showCatalogBadge ? ( {catalogBadgeLabel} @@ -149,7 +234,7 @@ export const ProviderModelBadges = ({ return (
-
+
{displayedModels.map(renderModelBadge)} {shouldCollapse && !expanded ? (