diff --git a/.gitignore b/.gitignore index ff2c585..29b8fb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ node_modules/ -*.tsbuildinfo +dist/ +dist-electron/ + +sidecar/**/bin/ +sidecar/**/obj/ + +.DS_Store +Thumbs.db diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..ae98d17 --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,215 @@ +# Kopaya Implementation Plan + +This file mirrors the working plan in the Copilot session workspace so the repository also carries the current implementation direction. + +## Problem statement + +Build `kopaya` into an Electron desktop application for orchestrating AI agents across multiple local projects. The desktop app should use TypeScript on the frontend, use the .NET version of Microsoft Agent Framework (MAF) with Copilot SDK for orchestration/runtime work, support all MAF orchestration modes, and also support a simple Copilot-CLI-style 1-on-1 human/agent chat mode. + +Users should be able to: + +- open and manage multiple project folders +- define reusable orchestration patterns +- create sessions inside a selected project by choosing a pattern +- view and continue active/past sessions from a chat-first UI + +The target UX is a left-side tree navigator for projects/sessions/patterns and a right-side main chat pane. + +## Current repository state + +- The repository is currently only a minimal Bun + TypeScript scaffold. +- `src/index.ts` is effectively empty. +- There is no Electron app structure yet. +- There is no renderer framework, no desktop state model, and no project/session UI. +- There is no .NET solution, no MAF integration, and no Copilot SDK integration yet. +- Current validation is minimal: `bun run test` maps to `bun run typecheck`, but the local TypeScript toolchain is not installed yet in this checkout, so the baseline command currently fails because `tsc` is unavailable. + +## Product scope captured so far + +- Desktop application built with Electron +- Standalone self-contained desktop app that bundles its local .NET backend +- Frontend implemented with React + Tailwind CSS in TypeScript +- Backend orchestration engine implemented with .NET + Microsoft Agent Framework +- Only Copilot SDK-backed agents are in scope for v1 +- Users authenticate with their Copilot account +- Support for all MAF orchestration modes currently documented for workflows: + - sequential + - concurrent + - handoff + - group chat +- - magentic (represented as unavailable in the current .NET implementation because Microsoft documents it as unsupported in C# today) +- Additional first-class single-agent chat mode for direct human-agent conversation +- Multiple projects/folders open in the app at the same time +- Reusable user-defined orchestration patterns stored in a global app-wide pattern library +- Session creation flow where the user selects both project and pattern + +## Recommended architecture + +### 1. Hybrid desktop structure + +- **Electron main process** + - application lifecycle + - native dialogs and folder selection + - spawning and supervising the .NET agent host + - IPC boundary to the renderer + - secure access to persisted local configuration +- **Electron renderer** + - chat-first application shell + - tree navigation for projects, sessions, and patterns + - pattern management screens + - session creation and session transcript views +- **.NET agent host** + - wraps Microsoft Agent Framework orchestration capabilities + - hosts Copilot SDK integrations + - creates/runs sessions + - validates and executes user-defined orchestration patterns + - streams structured events back to Electron + +### 2. Domain model + +- **Workspace**: top-level local app state +- **Project**: a user-selected folder with metadata and project-specific sessions +- **Pattern**: reusable orchestration definition with mode, participating agents, instructions, and execution options +- **Agent definition**: provider/model/instructions/tools metadata used inside a pattern +- **Session**: a concrete run bound to one project and one pattern +- **Turn/Event**: chat messages, orchestration state changes, tool events, handoffs, completion states + +### 3. Runtime boundary + +- Confirmed v1 approach: Electron main launches a bundled local .NET sidecar process as part of a standalone self-contained app. +- Use a versioned structured contract for commands/events between Electron and the .NET host. +- The contract must support: + - session creation + - send message / user input + - streaming assistant and orchestration events + - session pause/abort/resume + - pattern CRUD and validation + - project/workspace metadata sync + +### 4. UX slices + +- **Left tree navigator** + - workspace + - projects + - sessions under each project + - shared global pattern library +- **Right main pane** + - chat transcript + - composer + - session header/status + - selected pattern summary + - orchestration timeline or event rail for multi-agent runs +- **Create session flow** + - choose project + - choose pattern + - optionally override models/instructions + - launch and stream activity into the chat pane + +### 5. Persistence + +- Persist workspace metadata, known projects, patterns, sessions, transcripts, and resumable orchestration metadata in app data. +- Keep project source code in place; only store references to folders, not copies. +- Separate durable user configuration from transient runtime state. +- Store secrets and credentials in the OS keychain only. + +### 6. Testing strategy + +- TypeScript tests for desktop domain logic and renderer state +- .NET tests for pattern validation and orchestration execution +- Contract/integration tests for Electron-to-.NET messaging +- End-to-end smoke coverage for launching a project, starting a session, and streaming chat output + +## Proposed implementation phases + +### Phase 1 - Bootstrap the hybrid repository + +- Add Electron application structure +- Add renderer application structure in TypeScript +- Add a .NET solution for the agent host +- Add unified local development scripts +- Establish repository layout for TS app and .NET backend + +### Phase 2 - Define contracts and persistence + +- Define workspace/project/pattern/session models +- Define command/event contracts between Electron and .NET +- Implement local persistence for workspace metadata and transcripts +- Implement durable session state/checkpoint persistence for resume after restart +- Integrate secure credential access through the OS keychain +- Add validation rules for pattern definitions + +### Phase 3 - Implement the .NET agent host + +- Integrate Microsoft Agent Framework and Copilot SDK-backed agents +- Build single-agent chat mode +- Build orchestration execution adapters for each supported MAF mode +- Implement streaming lifecycle events and error propagation + +### Phase 4 - Build the desktop shell + +- Build the chat-first application shell +- Implement the left-side tree navigator +- Implement session list/detail routing and selection state +- Implement project add/remove flows + +### Phase 5 - Pattern authoring and session launch + +- Build pattern list/editor UX +- Support agent configuration inside a pattern +- Allow session creation from a selected project and pattern +- Surface validation errors before launch + +### Phase 6 - Reliability and polish + +- Resume/reload session state after app restart +- Improve long-running orchestration visibility +- Add packaging, logging, and diagnostics +- Expand test coverage and development ergonomics + +## Initial todo inventory + +1. `bootstrap-electron-hybrid-repo` + Create the Electron + TypeScript renderer scaffold and add the .NET solution layout for the MAF host. + +2. `define-domain-and-contracts` + Define workspace, project, pattern, agent, session, and event models plus the Electron/.NET command-event contract. + +3. `implement-persistence-layer` + Persist workspace metadata, project references, patterns, session summaries, transcripts, and resumable state. + +4. `build-dotnet-agent-host` + Create the .NET host process and integrate Microsoft Agent Framework plus Copilot SDK-backed agent support. + +5. `implement-single-agent-chat-mode` + Deliver simple Copilot-CLI-style 1-on-1 chat as a first-class session type. + +6. `wire-maf-orchestration-modes` + Add sequential, concurrent, handoff, group chat, and magentic pattern execution support. + +7. `build-electron-shell-and-navigation` + Implement the left tree navigator and the chat-first main pane. + +8. `build-pattern-management-ui` + Add CRUD workflows for reusable orchestration patterns and agent definitions. + +9. `build-session-launch-and-streaming-ui` + Allow users to choose a project and pattern, start a session, and watch streaming events in chat. + +10. `add-tests-and-packaging` + Add TypeScript/.NET/contract coverage and prepare desktop packaging and local developer workflows. + +## Confirmed product decisions + +- The app is packaged as a standalone self-contained Electron product with a bundled local .NET sidecar. +- The renderer uses React + Tailwind CSS. +- User-defined patterns are editable, reusable, and managed as a global library shared across projects. +- Sessions must resume after app restart and recover orchestration/chat state. +- Only Copilot SDK-backed agents are in scope for v1, using the user's Copilot account. +- Secrets and provider credentials are stored through the OS keychain. +- The current .NET implementation supports sequential, concurrent, handoff, and group chat; Magentic is surfaced as unavailable until C# support exists upstream. + +## Remaining assumptions + +- One desktop window is sufficient for v1. +- The right pane is primarily a chat experience, not a details-first inspector. +- Multi-agent orchestration should surface its event stream in a chat-adjacent way rather than in a separate heavy workflow designer first. diff --git a/bun.lock b/bun.lock index 343c54b..d2dd391 100644 --- a/bun.lock +++ b/bun.lock @@ -4,20 +4,548 @@ "workspaces": { "": { "name": "kopaya", + "dependencies": { + "keytar": "^7.9.0", + "react": "^19.2.4", + "react-dom": "^19.2.4", + }, "devDependencies": { + "@tailwindcss/vite": "^4.2.2", "@types/node": "^25.5.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "5.1.0", + "bun-types": "^1.3.11", + "electron": "^41.0.3", + "electron-vite": "^5.0.0", + "tailwindcss": "^4.2.2", "typescript": "^5.9.3", "typescript-language-server": "^5.1.3", + "vite": "7.1.10", }, }, }, "packages": { + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.43", "", {}, "sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.0", "", { "os": "android", "cpu": "arm" }, "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.0", "", { "os": "android", "cpu": "arm64" }, "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.0", "", { "os": "linux", "cpu": "none" }, "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.0", "", { "os": "linux", "cpu": "x64" }, "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.0", "", { "os": "none", "cpu": "arm64" }, "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.0", "", { "os": "win32", "cpu": "x64" }, "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w=="], + + "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], + + "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="], + + "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.43", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-4LuWrg7EKWgQaMJfnN+wcmbAW+VSsCmqGohftWjuct47bv8uE4n/nPpq4XjJPsxgq00GGG5J8dvBczp8uxScew=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], + + "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001780", "", {}, "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ=="], + + "chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + + "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + + "electron": ["electron@41.0.3", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-IDjx8liW1q+r7+MOip5W1Eo1eMwJzVObmYrd9yz2dPCkS7XlgLq3qPVMR80TpiROFp73iY30kTzMdpA6fEVs3A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.321", "", {}, "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ=="], + + "electron-vite": ["electron-vite@5.0.0", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-arrow-functions": "^7.27.1", "cac": "^6.7.14", "esbuild": "^0.25.11", "magic-string": "^0.30.19", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], + + "node-addon-api": ["node-addon-api@4.3.0", "", {}, "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ=="], + + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], + + "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], + + "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], + + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + + "rollup": ["rollup@4.60.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.0", "@rollup/rollup-android-arm64": "4.60.0", "@rollup/rollup-darwin-arm64": "4.60.0", "@rollup/rollup-darwin-x64": "4.60.0", "@rollup/rollup-freebsd-arm64": "4.60.0", "@rollup/rollup-freebsd-x64": "4.60.0", "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", "@rollup/rollup-linux-arm-musleabihf": "4.60.0", "@rollup/rollup-linux-arm64-gnu": "4.60.0", "@rollup/rollup-linux-arm64-musl": "4.60.0", "@rollup/rollup-linux-loong64-gnu": "4.60.0", "@rollup/rollup-linux-loong64-musl": "4.60.0", "@rollup/rollup-linux-ppc64-gnu": "4.60.0", "@rollup/rollup-linux-ppc64-musl": "4.60.0", "@rollup/rollup-linux-riscv64-gnu": "4.60.0", "@rollup/rollup-linux-riscv64-musl": "4.60.0", "@rollup/rollup-linux-s390x-gnu": "4.60.0", "@rollup/rollup-linux-x64-gnu": "4.60.0", "@rollup/rollup-linux-x64-musl": "4.60.0", "@rollup/rollup-openbsd-x64": "4.60.0", "@rollup/rollup-openharmony-arm64": "4.60.0", "@rollup/rollup-win32-arm64-msvc": "4.60.0", "@rollup/rollup-win32-ia32-msvc": "4.60.0", "@rollup/rollup-win32-x64-gnu": "4.60.0", "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], + + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-language-server": ["typescript-language-server@5.1.3", "", { "bin": { "typescript-language-server": "lib/cli.mjs" } }, "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg=="], "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vite": ["vite@7.1.10", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], + + "electron/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], + + "global-agent/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "electron/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], } } diff --git a/electron.vite.config.ts b/electron.vite.config.ts new file mode 100644 index 0000000..040abd3 --- /dev/null +++ b/electron.vite.config.ts @@ -0,0 +1,44 @@ +import { resolve } from 'node:path'; + +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; + +export default defineConfig({ + main: { + build: { + outDir: 'dist-electron/main', + }, + plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@main': resolve(__dirname, 'src/main'), + '@shared': resolve(__dirname, 'src/shared'), + }, + }, + }, + preload: { + build: { + outDir: 'dist-electron/preload', + }, + plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@shared': resolve(__dirname, 'src/shared'), + }, + }, + }, + renderer: { + root: 'src/renderer', + build: { + outDir: '../../dist/renderer', + }, + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@renderer': resolve(__dirname, 'src/renderer'), + '@shared': resolve(__dirname, 'src/shared'), + }, + }, + }, +}); diff --git a/package.json b/package.json index 4d4ec3e..c6eb19b 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,19 @@ { "name": "kopaya", "version": "1.0.0", - "description": "Terminal UI wrapper for coordinating multiple GitHub Copilot CLI sessions across projects.", + "description": "Electron orchestrator for Copilot-powered agent workflows across multiple projects.", "private": true, + "main": "dist-electron/main/index.js", "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build && bun run sidecar:build", + "preview": "electron-vite preview", "lsp:typescript": "typescript-language-server --stdio", - "typecheck": "tsc --noEmit", - "test": "bun run typecheck" + "typecheck": "tsc --noEmit -p tsconfig.json", + "test": "bun run typecheck && bun test", + "sidecar:restore": "powershell -NoProfile -Command \"dotnet restore 'sidecar\\\\Kopaya.AgentHost.slnx'\"", + "sidecar:build": "powershell -NoProfile -Command \"dotnet build 'sidecar\\\\Kopaya.AgentHost.slnx'\"", + "sidecar:test": "powershell -NoProfile -Command \"dotnet test 'sidecar\\\\Kopaya.AgentHost.slnx'\"" }, "repository": { "type": "git", @@ -21,8 +28,22 @@ "homepage": "https://github.com/davidkaya/kopaya#readme", "packageManager": "bun@1.3.6", "devDependencies": { + "@tailwindcss/vite": "^4.2.2", "@types/node": "^25.5.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "5.1.0", + "bun-types": "^1.3.11", + "electron": "^41.0.3", + "electron-vite": "^5.0.0", + "tailwindcss": "^4.2.2", "typescript": "^5.9.3", - "typescript-language-server": "^5.1.3" + "typescript-language-server": "^5.1.3", + "vite": "7.1.10" + }, + "dependencies": { + "keytar": "^7.9.0", + "react": "^19.2.4", + "react-dom": "^19.2.4" } } diff --git a/sidecar/Kopaya.AgentHost.slnx b/sidecar/Kopaya.AgentHost.slnx new file mode 100644 index 0000000..9e79146 --- /dev/null +++ b/sidecar/Kopaya.AgentHost.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/sidecar/src/Kopaya.AgentHost/Contracts/ProtocolModels.cs b/sidecar/src/Kopaya.AgentHost/Contracts/ProtocolModels.cs new file mode 100644 index 0000000..5b7c1f3 --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Contracts/ProtocolModels.cs @@ -0,0 +1,113 @@ +using System.Text.Json.Serialization; + +namespace Kopaya.AgentHost.Contracts; + +public sealed class PatternAgentDefinitionDto +{ + public string Id { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string Instructions { get; init; } = string.Empty; + public string Model { get; init; } = string.Empty; + public string? ReasoningEffort { get; init; } +} + +public sealed class PatternDefinitionDto +{ + public string Id { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; + public string Description { get; init; } = string.Empty; + public string Mode { get; init; } = string.Empty; + public string Availability { get; init; } = "available"; + public string? UnavailabilityReason { get; init; } + public int MaxIterations { get; init; } + public IReadOnlyList Agents { get; init; } = []; + public string CreatedAt { get; init; } = string.Empty; + public string UpdatedAt { get; init; } = string.Empty; +} + +public sealed class ChatMessageDto +{ + public string Id { get; init; } = string.Empty; + public string Role { get; init; } = string.Empty; + public string AuthorName { get; init; } = string.Empty; + public string Content { get; init; } = string.Empty; + public string CreatedAt { get; init; } = string.Empty; +} + +public sealed class PatternValidationIssueDto +{ + public string Level { get; init; } = "error"; + public string? Field { get; init; } + public string Message { get; init; } = string.Empty; +} + +public sealed class SidecarModeCapabilityDto +{ + public bool Available { get; init; } + public string? Reason { get; init; } +} + +public sealed class SidecarCapabilitiesDto +{ + public string Runtime { get; init; } = "dotnet-maf"; + public Dictionary Modes { get; init; } = new(StringComparer.OrdinalIgnoreCase); +} + +public class SidecarCommandEnvelope +{ + public string Type { get; init; } = string.Empty; + public string RequestId { get; init; } = string.Empty; +} + +public sealed class DescribeCapabilitiesCommandDto : SidecarCommandEnvelope; + +public sealed class ValidatePatternCommandDto : SidecarCommandEnvelope +{ + public PatternDefinitionDto Pattern { get; init; } = new(); +} + +public sealed class RunTurnCommandDto : SidecarCommandEnvelope +{ + public string SessionId { get; init; } = string.Empty; + public string ProjectPath { get; init; } = string.Empty; + public PatternDefinitionDto Pattern { get; init; } = new(); + public IReadOnlyList Messages { get; init; } = []; +} + +public abstract class SidecarEventDto +{ + public string Type { get; init; } = string.Empty; + public string RequestId { get; init; } = string.Empty; +} + +public sealed class CapabilitiesEventDto : SidecarEventDto +{ + public SidecarCapabilitiesDto Capabilities { get; init; } = new(); +} + +public sealed class PatternValidationEventDto : SidecarEventDto +{ + public IReadOnlyList Issues { get; init; } = []; +} + +public sealed class TurnDeltaEventDto : SidecarEventDto +{ + public string SessionId { get; init; } = string.Empty; + public string MessageId { get; init; } = string.Empty; + public string AuthorName { get; init; } = string.Empty; + public string ContentDelta { get; init; } = string.Empty; +} + +public sealed class TurnCompleteEventDto : SidecarEventDto +{ + public string SessionId { get; init; } = string.Empty; + public IReadOnlyList Messages { get; init; } = []; +} + +public sealed class CommandErrorEventDto : SidecarEventDto +{ + public string Message { get; init; } = string.Empty; +} + +public sealed class CommandCompleteEventDto : SidecarEventDto; diff --git a/sidecar/src/Kopaya.AgentHost/Kopaya.AgentHost.csproj b/sidecar/src/Kopaya.AgentHost/Kopaya.AgentHost.csproj new file mode 100644 index 0000000..b95853d --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Kopaya.AgentHost.csproj @@ -0,0 +1,16 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + diff --git a/sidecar/src/Kopaya.AgentHost/Program.cs b/sidecar/src/Kopaya.AgentHost/Program.cs new file mode 100644 index 0000000..5177a47 --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Program.cs @@ -0,0 +1,10 @@ +using Kopaya.AgentHost.Services; + +if (!args.Contains("--stdio", StringComparer.Ordinal)) +{ + Console.Error.WriteLine("Kopaya.AgentHost expects the --stdio flag."); + return; +} + +SidecarProtocolHost host = new(); +await host.RunAsync(Console.In, Console.Out, CancellationToken.None); diff --git a/sidecar/src/Kopaya.AgentHost/Services/CopilotWorkflowRunner.cs b/sidecar/src/Kopaya.AgentHost/Services/CopilotWorkflowRunner.cs new file mode 100644 index 0000000..8977470 --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Services/CopilotWorkflowRunner.cs @@ -0,0 +1,269 @@ +using System.Text; +using GitHub.Copilot.SDK; +using Kopaya.AgentHost.Contracts; +using Microsoft.Agents.AI; +using Microsoft.Agents.AI.GitHub.Copilot; +using Microsoft.Agents.AI.Workflows; +using Microsoft.Agents.AI.Workflows.Specialized; +using Microsoft.Extensions.AI; + +namespace Kopaya.AgentHost.Services; + +public sealed class CopilotWorkflowRunner +{ + private readonly PatternValidator _patternValidator; + + public CopilotWorkflowRunner(PatternValidator patternValidator) + { + _patternValidator = patternValidator; + } + + public async Task> RunTurnAsync( + RunTurnCommandDto command, + Func onDelta, + CancellationToken cancellationToken) + { + PatternValidationIssueDto? validationError = _patternValidator.Validate(command.Pattern).FirstOrDefault(); + if (validationError is not null) + { + throw new InvalidOperationException(validationError.Message); + } + + await using AgentBundle bundle = await AgentBundle.CreateAsync(command.Pattern, command.ProjectPath, cancellationToken); + Workflow workflow = bundle.BuildWorkflow(command.Pattern); + List inputMessages = command.Messages.Select(ToChatMessage).ToList(); + + List segments = []; + int fallbackMessageIndex = 0; + List completedMessages = []; + + await using StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, inputMessages).ConfigureAwait(false); + await run.TrySendMessageAsync(new TurnToken(emitEvents: true)).ConfigureAwait(false); + + await foreach (WorkflowEvent evt in run.WatchStreamAsync(cancellationToken).ConfigureAwait(false)) + { + if (evt is AgentResponseUpdateEvent update && !string.IsNullOrEmpty(update.Update.Text)) + { + string messageId = update.Update.MessageId ?? $"{command.RequestId}-delta-{fallbackMessageIndex++}"; + StreamingSegment segment = GetOrCreateSegment(segments, messageId, update.ExecutorId); + segment.Content.Append(update.Update.Text); + + await onDelta(new TurnDeltaEventDto + { + Type = "turn-delta", + RequestId = command.RequestId, + SessionId = command.SessionId, + MessageId = messageId, + AuthorName = update.ExecutorId, + ContentDelta = update.Update.Text, + }).ConfigureAwait(false); + } + else if (evt is WorkflowOutputEvent outputEvent) + { + List allMessages = outputEvent.As>() ?? []; + List newMessages = allMessages.Skip(inputMessages.Count).ToList(); + completedMessages = ConvertOutputMessages(command, newMessages, segments); + } + } + + return completedMessages; + } + + private static StreamingSegment GetOrCreateSegment(List segments, string messageId, string authorName) + { + StreamingSegment? existing = segments.LastOrDefault(segment => segment.MessageId == messageId); + if (existing is not null) + { + return existing; + } + + StreamingSegment created = new(messageId, authorName); + segments.Add(created); + return created; + } + + private static List ConvertOutputMessages( + RunTurnCommandDto command, + IReadOnlyList newMessages, + IReadOnlyList segments) + { + List mapped = []; + int segmentIndex = 0; + + foreach (ChatMessage message in newMessages.Where(message => message.Role != ChatRole.User)) + { + StreamingSegment? segment = segmentIndex < segments.Count ? segments[segmentIndex] : null; + segmentIndex++; + + mapped.Add(new ChatMessageDto + { + Id = segment?.MessageId ?? $"{command.RequestId}-final-{segmentIndex}", + Role = message.Role == ChatRole.System ? "system" : "assistant", + AuthorName = message.AuthorName ?? segment?.AuthorName ?? "assistant", + Content = message.Text ?? segment?.Content.ToString() ?? string.Empty, + CreatedAt = DateTimeOffset.UtcNow.ToString("O"), + }); + } + + if (mapped.Count == 0 && segments.Count > 0) + { + mapped.AddRange(segments.Select(segment => new ChatMessageDto + { + Id = segment.MessageId, + Role = "assistant", + AuthorName = segment.AuthorName, + Content = segment.Content.ToString(), + CreatedAt = DateTimeOffset.UtcNow.ToString("O"), + })); + } + + return mapped; + } + + private static ChatMessage ToChatMessage(ChatMessageDto message) + { + ChatMessage mapped = new(message.Role switch + { + "user" => ChatRole.User, + "system" => ChatRole.System, + _ => ChatRole.Assistant, + }, message.Content); + + if (!string.IsNullOrWhiteSpace(message.AuthorName)) + { + mapped.AuthorName = message.AuthorName; + } + + return mapped; + } + + private sealed class StreamingSegment + { + public StreamingSegment(string messageId, string authorName) + { + MessageId = messageId; + AuthorName = authorName; + } + + public string MessageId { get; } + + public string AuthorName { get; } + + public StringBuilder Content { get; } = new(); + } + + private sealed class AgentBundle : IAsyncDisposable + { + private readonly List _disposables = []; + + private AgentBundle(IReadOnlyList agents) + { + Agents = agents; + } + + public IReadOnlyList Agents { get; } + + public static async Task CreateAsync( + PatternDefinitionDto pattern, + string projectPath, + CancellationToken cancellationToken) + { + List disposables = []; + List agents = []; + + foreach (PatternAgentDefinitionDto definition in pattern.Agents) + { + CopilotClient client = new(); + await client.StartAsync(cancellationToken).ConfigureAwait(false); + + SessionConfig sessionConfig = new() + { + Model = definition.Model, + ReasoningEffort = definition.ReasoningEffort, + SystemMessage = new SystemMessageConfig + { + Content = definition.Instructions, + }, + WorkingDirectory = projectPath, + OnPermissionRequest = ApprovePermissionAsync, + Streaming = true, + }; + + GitHubCopilotAgent agent = new( + client, + sessionConfig, + ownsClient: true, + id: definition.Id, + name: definition.Name, + description: definition.Description); + + agents.Add(agent); + disposables.Add(agent); + } + + AgentBundle bundle = new(agents); + bundle._disposables.AddRange(disposables); + return bundle; + } + + public Workflow BuildWorkflow(PatternDefinitionDto pattern) + { + return pattern.Mode switch + { + "single" => AgentWorkflowBuilder.BuildSequential(pattern.Name, Agents), + "sequential" => AgentWorkflowBuilder.BuildSequential(pattern.Name, Agents), + "concurrent" => AgentWorkflowBuilder.BuildConcurrent(pattern.Name, Agents), + "handoff" => BuildHandoffWorkflow(pattern), + "group-chat" => BuildGroupChatWorkflow(pattern), + "magentic" => throw new NotSupportedException( + pattern.UnavailabilityReason + ?? "Magentic orchestration is not yet supported in the .NET Agent Framework."), + _ => throw new NotSupportedException($"Unsupported orchestration mode '{pattern.Mode}'."), + }; + } + + public async ValueTask DisposeAsync() + { + foreach (IAsyncDisposable disposable in _disposables) + { + await disposable.DisposeAsync().ConfigureAwait(false); + } + } + + private Workflow BuildHandoffWorkflow(PatternDefinitionDto pattern) + { + AIAgent firstAgent = Agents[0]; + IReadOnlyList specialists = Agents.Skip(1).ToList(); + + HandoffsWorkflowBuilder builder = AgentWorkflowBuilder.CreateHandoffBuilderWith(firstAgent) + .WithHandoffs(firstAgent, specialists) + .WithHandoffs(specialists, firstAgent); + + return builder.Build(); + } + + private Workflow BuildGroupChatWorkflow(PatternDefinitionDto pattern) + { + int maximumIterations = pattern.MaxIterations <= 0 ? 5 : pattern.MaxIterations; + + return AgentWorkflowBuilder + .CreateGroupChatBuilderWith(agents => + new RoundRobinGroupChatManager(agents) + { + MaximumIterationCount = maximumIterations, + }) + .AddParticipants(Agents.ToArray()) + .Build(); + } + + private static Task ApprovePermissionAsync( + PermissionRequest request, + PermissionInvocation invocation) + { + return Task.FromResult(new PermissionRequestResult + { + Kind = "approved", + }); + } + } +} diff --git a/sidecar/src/Kopaya.AgentHost/Services/PatternValidator.cs b/sidecar/src/Kopaya.AgentHost/Services/PatternValidator.cs new file mode 100644 index 0000000..478d51f --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Services/PatternValidator.cs @@ -0,0 +1,98 @@ +using Kopaya.AgentHost.Contracts; + +namespace Kopaya.AgentHost.Services; + +public sealed class PatternValidator +{ + public IReadOnlyList Validate(PatternDefinitionDto pattern) + { + List issues = []; + + if (string.IsNullOrWhiteSpace(pattern.Name)) + { + issues.Add(new PatternValidationIssueDto + { + Field = "name", + Message = "Pattern name is required.", + }); + } + + if (string.Equals(pattern.Availability, "unavailable", StringComparison.OrdinalIgnoreCase)) + { + issues.Add(new PatternValidationIssueDto + { + Field = "availability", + Message = pattern.UnavailabilityReason ?? "This orchestration mode is currently unavailable.", + }); + } + + if (pattern.Agents.Count == 0) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents", + Message = "At least one agent is required.", + }); + } + + if (string.Equals(pattern.Mode, "single", StringComparison.OrdinalIgnoreCase) && pattern.Agents.Count != 1) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents", + Message = "Single-agent chat requires exactly one agent.", + }); + } + + if (string.Equals(pattern.Mode, "handoff", StringComparison.OrdinalIgnoreCase) && pattern.Agents.Count < 2) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents", + Message = "Handoff orchestration requires at least two agents.", + }); + } + + if (string.Equals(pattern.Mode, "group-chat", StringComparison.OrdinalIgnoreCase) && pattern.Agents.Count < 2) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents", + Message = "Group chat requires at least two agents.", + }); + } + + if (string.Equals(pattern.Mode, "magentic", StringComparison.OrdinalIgnoreCase)) + { + issues.Add(new PatternValidationIssueDto + { + Field = "mode", + Message = pattern.UnavailabilityReason + ?? "Magentic orchestration is currently documented as unsupported in the .NET Agent Framework.", + }); + } + + foreach (PatternAgentDefinitionDto agent in pattern.Agents) + { + if (string.IsNullOrWhiteSpace(agent.Name)) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents.name", + Message = "Every agent needs a name.", + }); + } + + if (string.IsNullOrWhiteSpace(agent.Model)) + { + issues.Add(new PatternValidationIssueDto + { + Field = "agents.model", + Message = $"Agent \"{agent.Name}\" requires a model identifier.", + }); + } + } + + return issues; + } +} diff --git a/sidecar/src/Kopaya.AgentHost/Services/SidecarProtocolHost.cs b/sidecar/src/Kopaya.AgentHost/Services/SidecarProtocolHost.cs new file mode 100644 index 0000000..192b524 --- /dev/null +++ b/sidecar/src/Kopaya.AgentHost/Services/SidecarProtocolHost.cs @@ -0,0 +1,171 @@ +using System.Collections.Concurrent; +using System.Text.Json; +using System.Text.Json.Serialization; +using Kopaya.AgentHost.Contracts; + +namespace Kopaya.AgentHost.Services; + +public sealed class SidecarProtocolHost +{ + private readonly PatternValidator _patternValidator; + private readonly CopilotWorkflowRunner _workflowRunner; + private readonly JsonSerializerOptions _jsonOptions; + private readonly SemaphoreSlim _writeLock = new(1, 1); + private readonly ConcurrentDictionary _inFlight = new(StringComparer.Ordinal); + + public SidecarProtocolHost() + { + _patternValidator = new PatternValidator(); + _workflowRunner = new CopilotWorkflowRunner(_patternValidator); + _jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNameCaseInsensitive = true, + }; + } + + public async Task RunAsync(TextReader input, TextWriter output, CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + string? line = await input.ReadLineAsync(cancellationToken).ConfigureAwait(false); + if (line is null) + { + break; + } + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + SidecarCommandEnvelope envelope = DeserializeEnvelope(line); + Task task = HandleCommandAsync(line, envelope, output, cancellationToken); + _inFlight[envelope.RequestId] = task; + _ = task.ContinueWith( + _ => + { + _inFlight.TryRemove(envelope.RequestId, out Task? removedTask); + return removedTask is not null; + }, + CancellationToken.None, + TaskContinuationOptions.None, + TaskScheduler.Default); + } + + await Task.WhenAll(_inFlight.Values).ConfigureAwait(false); + } + + private SidecarCommandEnvelope DeserializeEnvelope(string line) + { + return JsonSerializer.Deserialize(line, _jsonOptions) + ?? throw new InvalidOperationException("Could not deserialize sidecar command envelope."); + } + + private async Task HandleCommandAsync( + string rawCommand, + SidecarCommandEnvelope envelope, + TextWriter output, + CancellationToken cancellationToken) + { + try + { + switch (envelope.Type) + { + case "describe-capabilities": + await WriteAsync(output, new CapabilitiesEventDto + { + Type = "capabilities", + RequestId = envelope.RequestId, + Capabilities = BuildCapabilities(), + }, cancellationToken).ConfigureAwait(false); + break; + + case "validate-pattern": + ValidatePatternCommandDto validateCommand = + JsonSerializer.Deserialize(rawCommand, _jsonOptions) + ?? throw new InvalidOperationException("Could not deserialize validate-pattern command."); + + await WriteAsync(output, new PatternValidationEventDto + { + Type = "pattern-validation", + RequestId = envelope.RequestId, + Issues = _patternValidator.Validate(validateCommand.Pattern), + }, cancellationToken).ConfigureAwait(false); + break; + + case "run-turn": + RunTurnCommandDto runTurnCommand = + JsonSerializer.Deserialize(rawCommand, _jsonOptions) + ?? throw new InvalidOperationException("Could not deserialize run-turn command."); + + IReadOnlyList messages = await _workflowRunner.RunTurnAsync( + runTurnCommand, + delta => WriteAsync(output, delta, cancellationToken), + cancellationToken).ConfigureAwait(false); + + await WriteAsync(output, new TurnCompleteEventDto + { + Type = "turn-complete", + RequestId = envelope.RequestId, + SessionId = runTurnCommand.SessionId, + Messages = messages, + }, cancellationToken).ConfigureAwait(false); + break; + + default: + throw new NotSupportedException($"Unknown sidecar command type '{envelope.Type}'."); + } + + await WriteAsync(output, new CommandCompleteEventDto + { + Type = "command-complete", + RequestId = envelope.RequestId, + }, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + await WriteAsync(output, new CommandErrorEventDto + { + Type = "command-error", + RequestId = envelope.RequestId, + Message = ex.Message, + }, cancellationToken).ConfigureAwait(false); + } + } + + private async Task WriteAsync(TextWriter output, object payload, CancellationToken cancellationToken) + { + string json = JsonSerializer.Serialize(payload, _jsonOptions); + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await output.WriteLineAsync(json).ConfigureAwait(false); + await output.FlushAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + _writeLock.Release(); + } + } + + private static SidecarCapabilitiesDto BuildCapabilities() + { + return new SidecarCapabilitiesDto + { + Modes = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + ["single"] = new() { Available = true }, + ["sequential"] = new() { Available = true }, + ["concurrent"] = new() { Available = true }, + ["handoff"] = new() { Available = true }, + ["group-chat"] = new() { Available = true }, + ["magentic"] = new() + { + Available = false, + Reason = "Microsoft Agent Framework currently documents Magentic orchestration as unsupported in C#.", + }, + }, + }; + } +} diff --git a/sidecar/tests/Kopaya.AgentHost.Tests/Kopaya.AgentHost.Tests.csproj b/sidecar/tests/Kopaya.AgentHost.Tests/Kopaya.AgentHost.Tests.csproj new file mode 100644 index 0000000..fefd130 --- /dev/null +++ b/sidecar/tests/Kopaya.AgentHost.Tests/Kopaya.AgentHost.Tests.csproj @@ -0,0 +1,25 @@ + + + + net9.0 + enable + enable + false + + + + + + + + + + + + + + + + + + diff --git a/sidecar/tests/Kopaya.AgentHost.Tests/UnitTest1.cs b/sidecar/tests/Kopaya.AgentHost.Tests/UnitTest1.cs new file mode 100644 index 0000000..376008b --- /dev/null +++ b/sidecar/tests/Kopaya.AgentHost.Tests/UnitTest1.cs @@ -0,0 +1,69 @@ +using Kopaya.AgentHost.Contracts; +using Kopaya.AgentHost.Services; + +namespace Kopaya.AgentHost.Tests; + +public sealed class PatternValidatorTests +{ + private readonly PatternValidator _validator = new(); + + [Fact] + public void SingleAgentPattern_WithExactlyOneAgent_IsValid() + { + PatternDefinitionDto pattern = new() + { + Id = "single", + Name = "Single", + Mode = "single", + Availability = "available", + Agents = + [ + new PatternAgentDefinitionDto + { + Id = "agent-1", + Name = "Primary", + Model = "gpt-5.4", + Instructions = "Help with the user's request.", + }, + ], + }; + + IReadOnlyList issues = _validator.Validate(pattern); + + Assert.Empty(issues); + } + + [Fact] + public void MagenticPattern_IsReportedAsUnavailable() + { + PatternDefinitionDto pattern = new() + { + Id = "magentic", + Name = "Magentic", + Mode = "magentic", + Availability = "unavailable", + UnavailabilityReason = "Unsupported in C#.", + Agents = + [ + new PatternAgentDefinitionDto + { + Id = "agent-1", + Name = "Planner", + Model = "gpt-5.4", + Instructions = "Plan the task.", + }, + new PatternAgentDefinitionDto + { + Id = "agent-2", + Name = "Specialist", + Model = "claude-opus-4.5", + Instructions = "Complete the task.", + }, + ], + }; + + IReadOnlyList issues = _validator.Validate(pattern); + + Assert.Contains(issues, issue => issue.Message.Contains("Unsupported", StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/src/main/KopayaAppService.ts b/src/main/KopayaAppService.ts new file mode 100644 index 0000000..b78867d --- /dev/null +++ b/src/main/KopayaAppService.ts @@ -0,0 +1,353 @@ +import { EventEmitter } from 'node:events'; +import { basename } from 'node:path'; + +import { dialog } from 'electron'; + +import type { TurnDeltaEvent } from '@shared/contracts/sidecar'; +import { buildSessionTitle, validatePatternDefinition, type PatternDefinition } from '@shared/domain/pattern'; +import type { ProjectRecord } from '@shared/domain/project'; +import type { SessionEventRecord } from '@shared/domain/event'; +import type { ChatMessageRecord, SessionRecord } from '@shared/domain/session'; +import type { WorkspaceState } from '@shared/domain/workspace'; +import { createId, nowIso } from '@shared/utils/ids'; + +import { WorkspaceRepository } from '@main/persistence/workspaceRepository'; +import { SecretStore } from '@main/secrets/secretStore'; +import { SidecarClient } from '@main/sidecar/sidecarProcess'; + +type AppServiceEvents = { + 'workspace-updated': [WorkspaceState]; + 'session-event': [SessionEventRecord]; +}; + +function isBuiltinPattern(patternId: string): boolean { + return patternId.startsWith('pattern-'); +} + +export class KopayaAppService extends EventEmitter { + private readonly workspaceRepository = new WorkspaceRepository(); + private readonly sidecar = new SidecarClient(); + private readonly secretStore = new SecretStore(); + private workspace?: WorkspaceState; + + async loadWorkspace(): Promise { + if (!this.workspace) { + this.workspace = await this.workspaceRepository.load(); + } + + return this.workspace; + } + + async dispose(): Promise { + await this.sidecar.dispose(); + void this.secretStore; + } + + async addProject(): Promise { + const workspace = await this.loadWorkspace(); + const result = await dialog.showOpenDialog({ + title: 'Open project folder', + properties: ['openDirectory'], + }); + + if (result.canceled || result.filePaths.length === 0) { + return workspace; + } + + const folderPath = result.filePaths[0]; + const existing = workspace.projects.find((project) => project.path === folderPath); + if (existing) { + workspace.selectedProjectId = existing.id; + return this.persistAndBroadcast(workspace); + } + + const project: ProjectRecord = { + id: createId('project'), + name: basename(folderPath), + path: folderPath, + addedAt: nowIso(), + }; + + workspace.projects.push(project); + workspace.selectedProjectId = project.id; + return this.persistAndBroadcast(workspace); + } + + async removeProject(projectId: string): Promise { + const workspace = await this.loadWorkspace(); + workspace.projects = workspace.projects.filter((project) => project.id !== projectId); + workspace.sessions = workspace.sessions.filter((session) => session.projectId !== projectId); + + if (workspace.selectedProjectId === projectId) { + workspace.selectedProjectId = workspace.projects[0]?.id; + } + + if ( + workspace.selectedSessionId && + !workspace.sessions.some((session) => session.id === workspace.selectedSessionId) + ) { + workspace.selectedSessionId = undefined; + } + + return this.persistAndBroadcast(workspace); + } + + async savePattern(pattern: PatternDefinition): Promise { + const workspace = await this.loadWorkspace(); + const issues = validatePatternDefinition(pattern).filter((issue) => issue.level === 'error'); + if (issues.length > 0) { + throw new Error(issues[0].message); + } + + const existingIndex = workspace.patterns.findIndex((current) => current.id === pattern.id); + const candidate: PatternDefinition = { + ...pattern, + createdAt: existingIndex >= 0 ? workspace.patterns[existingIndex].createdAt : nowIso(), + updatedAt: nowIso(), + }; + + if (existingIndex >= 0) { + workspace.patterns[existingIndex] = candidate; + } else { + workspace.patterns.push(candidate); + } + + workspace.selectedPatternId = candidate.id; + return this.persistAndBroadcast(workspace); + } + + async deletePattern(patternId: string): Promise { + if (isBuiltinPattern(patternId)) { + throw new Error('Built-in patterns cannot be deleted.'); + } + + const workspace = await this.loadWorkspace(); + workspace.patterns = workspace.patterns.filter((pattern) => pattern.id !== patternId); + + if (workspace.selectedPatternId === patternId) { + workspace.selectedPatternId = workspace.patterns[0]?.id; + } + + return this.persistAndBroadcast(workspace); + } + + async createSession(projectId: string, patternId: string): Promise { + const workspace = await this.loadWorkspace(); + const project = this.requireProject(workspace, projectId); + const pattern = this.requirePattern(workspace, patternId); + + const session: SessionRecord = { + id: createId('session'), + projectId: project.id, + patternId: pattern.id, + title: pattern.name, + createdAt: nowIso(), + updatedAt: nowIso(), + status: 'idle', + messages: [], + }; + + workspace.sessions.unshift(session); + workspace.selectedProjectId = project.id; + workspace.selectedPatternId = pattern.id; + workspace.selectedSessionId = session.id; + return this.persistAndBroadcast(workspace); + } + + async sendSessionMessage(sessionId: string, content: string): Promise { + const workspace = await this.loadWorkspace(); + const session = this.requireSession(workspace, sessionId); + const project = this.requireProject(workspace, session.projectId); + const pattern = this.requirePattern(workspace, session.patternId); + + const trimmed = content.trim(); + if (!trimmed) { + return; + } + + session.messages.push({ + id: createId('msg'), + role: 'user', + authorName: 'You', + content: trimmed, + createdAt: nowIso(), + }); + session.title = buildSessionTitle(pattern, session.messages); + session.status = 'running'; + session.lastError = undefined; + session.updatedAt = nowIso(); + + await this.persistAndBroadcast(workspace); + this.emitSessionEvent({ + sessionId: session.id, + kind: 'status', + status: 'running', + occurredAt: nowIso(), + }); + + const requestId = createId('turn'); + try { + const responseMessages = await this.sidecar.runTurn( + { + type: 'run-turn', + requestId, + sessionId: session.id, + projectPath: project.path, + pattern, + messages: session.messages, + }, + async (event) => { + await this.applyTurnDelta(workspace, session.id, event); + }, + ); + + this.finalizeTurn(workspace, session.id, responseMessages); + await this.persistAndBroadcast(workspace); + } catch (error) { + session.status = 'error'; + session.lastError = error instanceof Error ? error.message : String(error); + session.updatedAt = nowIso(); + + this.emitSessionEvent({ + sessionId: session.id, + kind: 'error', + occurredAt: nowIso(), + error: session.lastError, + }); + + await this.persistAndBroadcast(workspace); + } + } + + async selectProject(projectId?: string): Promise { + const workspace = await this.loadWorkspace(); + workspace.selectedProjectId = projectId; + workspace.selectedSessionId = workspace.selectedSessionId; + return this.persistAndBroadcast(workspace); + } + + async selectPattern(patternId?: string): Promise { + const workspace = await this.loadWorkspace(); + workspace.selectedPatternId = patternId; + workspace.selectedSessionId = workspace.selectedSessionId; + return this.persistAndBroadcast(workspace); + } + + async selectSession(sessionId?: string): Promise { + const workspace = await this.loadWorkspace(); + workspace.selectedSessionId = sessionId; + return this.persistAndBroadcast(workspace); + } + + private requireProject(workspace: WorkspaceState, projectId: string): ProjectRecord { + const project = workspace.projects.find((current) => current.id === projectId); + if (!project) { + throw new Error(`Project "${projectId}" was not found.`); + } + + return project; + } + + private requirePattern(workspace: WorkspaceState, patternId: string): PatternDefinition { + const pattern = workspace.patterns.find((current) => current.id === patternId); + if (!pattern) { + throw new Error(`Pattern "${patternId}" was not found.`); + } + + return pattern; + } + + private requireSession(workspace: WorkspaceState, sessionId: string): SessionRecord { + const session = workspace.sessions.find((current) => current.id === sessionId); + if (!session) { + throw new Error(`Session "${sessionId}" was not found.`); + } + + return session; + } + + private async applyTurnDelta( + workspace: WorkspaceState, + sessionId: string, + event: TurnDeltaEvent, + ): Promise { + const session = this.requireSession(workspace, sessionId); + const existing = session.messages.find((message) => message.id === event.messageId); + + if (existing) { + existing.content += event.contentDelta; + existing.pending = true; + } else { + session.messages.push({ + id: event.messageId, + role: 'assistant', + authorName: event.authorName, + content: event.contentDelta, + createdAt: nowIso(), + pending: true, + }); + } + + session.updatedAt = nowIso(); + await this.workspaceRepository.save(workspace); + + this.emitSessionEvent({ + sessionId, + kind: 'message-delta', + occurredAt: nowIso(), + messageId: event.messageId, + authorName: event.authorName, + contentDelta: event.contentDelta, + }); + } + + private finalizeTurn(workspace: WorkspaceState, sessionId: string, messages: ChatMessageRecord[]): void { + const session = this.requireSession(workspace, sessionId); + const incomingIds = new Set(messages.map((message) => message.id)); + + for (const message of messages) { + const existing = session.messages.find((current) => current.id === message.id); + if (existing) { + existing.authorName = message.authorName; + existing.content = message.content; + existing.pending = false; + } else { + session.messages.push({ ...message, pending: false }); + } + + this.emitSessionEvent({ + sessionId, + kind: 'message-complete', + occurredAt: nowIso(), + messageId: message.id, + authorName: message.authorName, + }); + } + + for (const message of session.messages) { + if (message.pending && incomingIds.has(message.id)) { + message.pending = false; + } + } + + session.status = 'idle'; + session.lastError = undefined; + session.updatedAt = nowIso(); + this.emitSessionEvent({ + sessionId, + kind: 'status', + occurredAt: nowIso(), + status: 'idle', + }); + } + + private async persistAndBroadcast(workspace: WorkspaceState): Promise { + await this.workspaceRepository.save(workspace); + this.emit('workspace-updated', workspace); + return workspace; + } + + private emitSessionEvent(event: SessionEventRecord): void { + this.emit('session-event', event); + } +} diff --git a/src/main/index.ts b/src/main/index.ts new file mode 100644 index 0000000..e4e2295 --- /dev/null +++ b/src/main/index.ts @@ -0,0 +1,37 @@ +import { app, BrowserWindow } from 'electron'; + +import { registerIpcHandlers } from '@main/ipc/registerIpcHandlers'; +import { KopayaAppService } from '@main/KopayaAppService'; +import { createMainWindow } from '@main/windows/createMainWindow'; + +let mainWindow: BrowserWindow | undefined; +let appService: KopayaAppService | undefined; + +async function bootstrap(): Promise { + appService = new KopayaAppService(); + + mainWindow = createMainWindow(); + registerIpcHandlers(mainWindow, appService); + + if (!app.isPackaged) { + mainWindow.webContents.openDevTools({ mode: 'detach' }); + } +} + +app.whenReady().then(bootstrap); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', async () => { + if (BrowserWindow.getAllWindows().length === 0) { + await bootstrap(); + } +}); + +app.on('before-quit', async () => { + await appService?.dispose(); +}); diff --git a/src/main/ipc/registerIpcHandlers.ts b/src/main/ipc/registerIpcHandlers.ts new file mode 100644 index 0000000..e74d66e --- /dev/null +++ b/src/main/ipc/registerIpcHandlers.ts @@ -0,0 +1,31 @@ +import { BrowserWindow, ipcMain } from 'electron'; + +import { ipcChannels } from '@shared/contracts/channels'; +import type { CreateSessionInput, SavePatternInput, SendSessionMessageInput } from '@shared/contracts/ipc'; + +import { KopayaAppService } from '@main/KopayaAppService'; + +export function registerIpcHandlers(window: BrowserWindow, service: KopayaAppService): void { + ipcMain.handle(ipcChannels.loadWorkspace, () => service.loadWorkspace()); + ipcMain.handle(ipcChannels.addProject, () => service.addProject()); + ipcMain.handle(ipcChannels.removeProject, (_event, projectId: string) => service.removeProject(projectId)); + ipcMain.handle(ipcChannels.savePattern, (_event, input: SavePatternInput) => service.savePattern(input.pattern)); + ipcMain.handle(ipcChannels.deletePattern, (_event, patternId: string) => service.deletePattern(patternId)); + ipcMain.handle(ipcChannels.createSession, (_event, input: CreateSessionInput) => + service.createSession(input.projectId, input.patternId), + ); + ipcMain.handle(ipcChannels.sendSessionMessage, (_event, input: SendSessionMessageInput) => + service.sendSessionMessage(input.sessionId, input.content), + ); + ipcMain.handle(ipcChannels.selectProject, (_event, projectId?: string) => service.selectProject(projectId)); + ipcMain.handle(ipcChannels.selectPattern, (_event, patternId?: string) => service.selectPattern(patternId)); + ipcMain.handle(ipcChannels.selectSession, (_event, sessionId?: string) => service.selectSession(sessionId)); + + service.on('workspace-updated', (workspace) => { + window.webContents.send(ipcChannels.workspaceUpdated, workspace); + }); + + service.on('session-event', (event) => { + window.webContents.send(ipcChannels.sessionEvent, event); + }); +} diff --git a/src/main/persistence/appPaths.ts b/src/main/persistence/appPaths.ts new file mode 100644 index 0000000..2928966 --- /dev/null +++ b/src/main/persistence/appPaths.ts @@ -0,0 +1,6 @@ +import { app } from 'electron'; +import { join } from 'node:path'; + +export function getWorkspaceFilePath(): string { + return join(app.getPath('userData'), 'workspace.json'); +} diff --git a/src/main/persistence/jsonStore.ts b/src/main/persistence/jsonStore.ts new file mode 100644 index 0000000..9e1bd39 --- /dev/null +++ b/src/main/persistence/jsonStore.ts @@ -0,0 +1,20 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + +export async function readJsonFile(filePath: string): Promise { + try { + const contents = await readFile(filePath, 'utf8'); + return JSON.parse(contents) as T; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return undefined; + } + + throw error; + } +} + +export async function writeJsonFile(filePath: string, value: T): Promise { + await mkdir(dirname(filePath), { recursive: true }); + await writeFile(filePath, JSON.stringify(value, null, 2), 'utf8'); +} diff --git a/src/main/persistence/workspaceRepository.ts b/src/main/persistence/workspaceRepository.ts new file mode 100644 index 0000000..2ff320c --- /dev/null +++ b/src/main/persistence/workspaceRepository.ts @@ -0,0 +1,62 @@ +import { createBuiltinPatterns } from '@shared/domain/pattern'; +import type { PatternDefinition } from '@shared/domain/pattern'; +import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace'; +import { nowIso } from '@shared/utils/ids'; + +import { getWorkspaceFilePath } from '@main/persistence/appPaths'; +import { readJsonFile, writeJsonFile } from '@main/persistence/jsonStore'; + +function mergePatterns(existingPatterns: PatternDefinition[]): PatternDefinition[] { + const builtinTimestamp = nowIso(); + const builtinPatterns = createBuiltinPatterns(builtinTimestamp); + const builtinIds = new Set(builtinPatterns.map((pattern) => pattern.id)); + const existingMap = new Map(existingPatterns.map((pattern) => [pattern.id, pattern])); + + const mergedBuiltins = builtinPatterns.map((builtin) => { + const existing = existingMap.get(builtin.id); + if (!existing) { + return builtin; + } + + return { + ...existing, + availability: builtin.availability, + unavailabilityReason: builtin.unavailabilityReason, + mode: builtin.mode, + }; + }); + + const customPatterns = existingPatterns.filter((pattern) => !builtinIds.has(pattern.id)); + return [...mergedBuiltins, ...customPatterns]; +} + +export class WorkspaceRepository { + readonly filePath = getWorkspaceFilePath(); + + async load(): Promise { + const stored = await readJsonFile(this.filePath); + if (!stored) { + const seeded = createWorkspaceSeed(); + await this.save(seeded); + return seeded; + } + + const workspace: WorkspaceState = { + ...stored, + patterns: mergePatterns(stored.patterns ?? []), + projects: stored.projects ?? [], + sessions: stored.sessions ?? [], + lastUpdatedAt: stored.lastUpdatedAt ?? nowIso(), + }; + + await this.save(workspace); + return workspace; + } + + async save(workspace: WorkspaceState): Promise { + await writeJsonFile(this.filePath, { + ...workspace, + lastUpdatedAt: nowIso(), + }); + } +} diff --git a/src/main/secrets/secretStore.ts b/src/main/secrets/secretStore.ts new file mode 100644 index 0000000..d7a06cc --- /dev/null +++ b/src/main/secrets/secretStore.ts @@ -0,0 +1,17 @@ +import keytar from 'keytar'; + +const serviceName = 'kopaya'; + +export class SecretStore { + async get(account: string): Promise { + return keytar.getPassword(serviceName, account); + } + + async set(account: string, secret: string): Promise { + await keytar.setPassword(serviceName, account, secret); + } + + async delete(account: string): Promise { + return keytar.deletePassword(serviceName, account); + } +} diff --git a/src/main/sidecar/sidecarProcess.ts b/src/main/sidecar/sidecarProcess.ts new file mode 100644 index 0000000..8e725de --- /dev/null +++ b/src/main/sidecar/sidecarProcess.ts @@ -0,0 +1,216 @@ +import { app } from 'electron'; +import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'; +import { join } from 'node:path'; + +import type { + SidecarCapabilities, + SidecarCommand, + SidecarEvent, + TurnDeltaEvent, + ValidatePatternCommand, + RunTurnCommand, +} from '@shared/contracts/sidecar'; +import type { ChatMessageRecord } from '@shared/domain/session'; + +type PendingCommand = + | { + kind: 'capabilities'; + resolve: (capabilities: SidecarCapabilities) => void; + reject: (error: Error) => void; + } + | { + kind: 'validate-pattern'; + resolve: (issues: ValidatePatternCommand['pattern'] extends never ? never : unknown) => void; + reject: (error: Error) => void; + } + | { + kind: 'run-turn'; + resolve: (messages: ChatMessageRecord[]) => void; + reject: (error: Error) => void; + onDelta: (event: TurnDeltaEvent) => void; + }; + +function getProjectRoot(): string { + return app.getAppPath(); +} + +function resolveSidecarProcess(): { command: string; args: string[] } { + if (app.isPackaged) { + return { + command: join(process.resourcesPath, 'sidecar', 'Kopaya.AgentHost.exe'), + args: ['--stdio'], + }; + } + + return { + command: 'dotnet', + args: [ + 'run', + '--project', + join(getProjectRoot(), 'sidecar', 'src', 'Kopaya.AgentHost', 'Kopaya.AgentHost.csproj'), + '--', + '--stdio', + ], + }; +} + +export class SidecarClient { + private process?: ChildProcessWithoutNullStreams; + private stdoutBuffer = ''; + private readonly pending = new Map(); + + async describeCapabilities(): Promise { + const command = await this.dispatch({ + type: 'describe-capabilities', + requestId: `cap-${Date.now()}`, + }); + + return command; + } + + async validatePattern(pattern: ValidatePatternCommand['pattern']): Promise { + return this.dispatch({ + type: 'validate-pattern', + requestId: `validate-${Date.now()}`, + pattern, + }); + } + + async runTurn(command: RunTurnCommand, onDelta: (event: TurnDeltaEvent) => void): Promise { + return this.dispatch(command, onDelta); + } + + async dispose(): Promise { + if (!this.process) { + return; + } + + this.process.kill(); + this.process = undefined; + } + + private async ensureProcess(): Promise { + if (this.process && !this.process.killed) { + return this.process; + } + + const sidecar = resolveSidecarProcess(); + this.process = spawn(sidecar.command, sidecar.args, { + cwd: getProjectRoot(), + stdio: 'pipe', + windowsHide: true, + }); + + this.process.stdout.setEncoding('utf8'); + this.process.stdout.on('data', (chunk: string) => { + this.stdoutBuffer += chunk; + this.flushStdoutBuffer(); + }); + + this.process.stderr.setEncoding('utf8'); + this.process.stderr.on('data', (chunk: string) => { + console.error('[kopaya sidecar]', chunk.trim()); + }); + + this.process.on('exit', (code) => { + const error = new Error(`The .NET sidecar exited unexpectedly with code ${code ?? 'unknown'}.`); + for (const pending of this.pending.values()) { + pending.reject(error); + } + this.pending.clear(); + this.process = undefined; + this.stdoutBuffer = ''; + }); + + return this.process; + } + + private async dispatch( + command: SidecarCommand, + onDelta?: (event: TurnDeltaEvent) => void, + ): Promise { + const process = await this.ensureProcess(); + + return new Promise((resolve, reject) => { + if (command.type === 'run-turn') { + this.pending.set(command.requestId, { + kind: 'run-turn', + resolve: resolve as (messages: ChatMessageRecord[]) => void, + reject, + onDelta: onDelta ?? (() => undefined), + }); + } else if (command.type === 'validate-pattern') { + this.pending.set(command.requestId, { + kind: 'validate-pattern', + resolve: resolve as (issues: unknown) => void, + reject, + }); + } else { + this.pending.set(command.requestId, { + kind: 'capabilities', + resolve: resolve as (capabilities: SidecarCapabilities) => void, + reject, + }); + } + + process.stdin.write(`${JSON.stringify(command)}\n`); + }); + } + + private flushStdoutBuffer(): void { + let newlineIndex = this.stdoutBuffer.indexOf('\n'); + + while (newlineIndex >= 0) { + const rawLine = this.stdoutBuffer.slice(0, newlineIndex).trim(); + this.stdoutBuffer = this.stdoutBuffer.slice(newlineIndex + 1); + + if (rawLine) { + this.handleEvent(JSON.parse(rawLine) as SidecarEvent); + } + + newlineIndex = this.stdoutBuffer.indexOf('\n'); + } + } + + private handleEvent(event: SidecarEvent): void { + const pending = this.pending.get(event.requestId); + if (!pending) { + return; + } + + switch (event.type) { + case 'capabilities': + if (pending.kind === 'capabilities') { + pending.resolve(event.capabilities); + this.pending.delete(event.requestId); + } + return; + case 'pattern-validation': + if (pending.kind === 'validate-pattern') { + pending.resolve(event.issues); + this.pending.delete(event.requestId); + } + return; + case 'turn-delta': + if (pending.kind === 'run-turn') { + pending.onDelta(event); + } + return; + case 'turn-complete': + if (pending.kind === 'run-turn') { + pending.resolve(event.messages); + this.pending.delete(event.requestId); + } + return; + case 'command-error': + pending.reject(new Error(event.message)); + this.pending.delete(event.requestId); + return; + case 'command-complete': + if (pending.kind !== 'run-turn') { + this.pending.delete(event.requestId); + } + return; + } + } +} diff --git a/src/main/windows/createMainWindow.ts b/src/main/windows/createMainWindow.ts new file mode 100644 index 0000000..95677a7 --- /dev/null +++ b/src/main/windows/createMainWindow.ts @@ -0,0 +1,33 @@ +import { BrowserWindow, shell } from 'electron'; +import { join } from 'node:path'; + +export function createMainWindow(): BrowserWindow { + const window = new BrowserWindow({ + width: 1440, + height: 960, + minWidth: 1120, + minHeight: 720, + title: 'kopaya', + backgroundColor: '#0f172a', + webPreferences: { + preload: join(__dirname, '../preload/index.js'), + contextIsolation: true, + nodeIntegration: false, + }, + }); + + const rendererUrl = process.env.ELECTRON_RENDERER_URL; + + if (rendererUrl) { + void window.loadURL(rendererUrl); + } else { + void window.loadFile(join(__dirname, '../../dist/renderer/index.html')); + } + + window.webContents.setWindowOpenHandler(({ url }) => { + void shell.openExternal(url); + return { action: 'deny' }; + }); + + return window; +} diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..5966440 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,33 @@ +import { contextBridge, ipcRenderer } from 'electron'; + +import { ipcChannels } from '@shared/contracts/channels'; +import type { ElectronApi } from '@shared/contracts/ipc'; + +const api: ElectronApi = { + loadWorkspace: () => ipcRenderer.invoke(ipcChannels.loadWorkspace), + addProject: () => ipcRenderer.invoke(ipcChannels.addProject), + removeProject: (projectId) => ipcRenderer.invoke(ipcChannels.removeProject, projectId), + savePattern: (input) => ipcRenderer.invoke(ipcChannels.savePattern, input), + deletePattern: (patternId) => ipcRenderer.invoke(ipcChannels.deletePattern, patternId), + createSession: (input) => ipcRenderer.invoke(ipcChannels.createSession, input), + sendSessionMessage: (input) => ipcRenderer.invoke(ipcChannels.sendSessionMessage, input), + selectProject: (projectId) => ipcRenderer.invoke(ipcChannels.selectProject, projectId), + selectPattern: (patternId) => ipcRenderer.invoke(ipcChannels.selectPattern, patternId), + selectSession: (sessionId) => ipcRenderer.invoke(ipcChannels.selectSession, sessionId), + onWorkspaceUpdated: (listener) => { + const handler = (_event: Electron.IpcRendererEvent, workspace: Awaited>) => + listener(workspace); + + ipcRenderer.on(ipcChannels.workspaceUpdated, handler); + return () => ipcRenderer.off(ipcChannels.workspaceUpdated, handler); + }, + onSessionEvent: (listener) => { + const handler = (_event: Electron.IpcRendererEvent, sessionEvent: Parameters[0]) => + listener(sessionEvent); + + ipcRenderer.on(ipcChannels.sessionEvent, handler); + return () => ipcRenderer.off(ipcChannels.sessionEvent, handler); + }, +}; + +contextBridge.exposeInMainWorld('kopayaApi', api); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx new file mode 100644 index 0000000..62c1b48 --- /dev/null +++ b/src/renderer/App.tsx @@ -0,0 +1,226 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { AppShell } from '@renderer/components/AppShell'; +import { ChatPane } from '@renderer/components/ChatPane'; +import { PatternEditor } from '@renderer/components/PatternEditor'; +import { Sidebar } from '@renderer/components/Sidebar'; +import { getElectronApi } from '@renderer/lib/electronApi'; +import type { PatternDefinition } from '@shared/domain/pattern'; +import { createBuiltinPatterns } from '@shared/domain/pattern'; +import type { WorkspaceState } from '@shared/domain/workspace'; +import { createId, nowIso } from '@shared/utils/ids'; + +function clonePattern(pattern: PatternDefinition): PatternDefinition { + return structuredClone(pattern); +} + +function createDraftPattern(): PatternDefinition { + const timestamp = nowIso(); + return { + id: createId('pattern'), + name: 'New Pattern', + description: 'Reusable orchestration pattern.', + mode: 'single', + availability: 'available', + maxIterations: 1, + agents: [ + { + id: createId('agent'), + name: 'Primary Agent', + description: 'General-purpose project assistant.', + instructions: 'You are a helpful coding assistant working inside the selected project.', + model: 'gpt-5.4', + reasoningEffort: 'high', + }, + ], + createdAt: timestamp, + updatedAt: timestamp, + }; +} + +function EmptyDetail() { + const patterns = createBuiltinPatterns(nowIso()); + return ( +
+
+
Workspace Overview
+

Chat-first orchestration across projects

+

+ Select a pattern to edit it, add one or more projects on the left, and start sessions that bind a + project folder to a reusable orchestration blueprint. +

+
+ {patterns.map((pattern) => ( +
+
+

{pattern.name}

+ + {pattern.mode} + +
+

{pattern.description}

+
+ ))} +
+
+
+ ); +} + +export default function App() { + const api = getElectronApi(); + const [workspace, setWorkspace] = useState(); + const [draftPattern, setDraftPattern] = useState(null); + const [error, setError] = useState(); + + useEffect(() => { + let disposed = false; + + void api + .loadWorkspace() + .then((nextWorkspace) => { + if (!disposed) { + setWorkspace(nextWorkspace); + } + }) + .catch((nextError) => { + if (!disposed) { + setError(nextError instanceof Error ? nextError.message : String(nextError)); + } + }); + + const offWorkspace = api.onWorkspaceUpdated((nextWorkspace) => { + setWorkspace(nextWorkspace); + setError(undefined); + }); + + return () => { + disposed = true; + offWorkspace(); + }; + }, [api]); + + useEffect(() => { + if (!workspace?.selectedPatternId) { + setDraftPattern(null); + return; + } + + const selectedPattern = workspace.patterns.find((pattern) => pattern.id === workspace.selectedPatternId); + setDraftPattern(selectedPattern ? clonePattern(selectedPattern) : null); + }, [workspace?.lastUpdatedAt, workspace?.selectedPatternId, workspace?.patterns]); + + const selectedSession = useMemo( + () => workspace?.sessions.find((session) => session.id === workspace.selectedSessionId), + [workspace?.selectedSessionId, workspace?.sessions], + ); + const selectedPattern = useMemo( + () => + draftPattern ?? + workspace?.patterns.find((pattern) => pattern.id === workspace.selectedPatternId), + [draftPattern, workspace?.patterns, workspace?.selectedPatternId], + ); + const selectedProject = useMemo( + () => workspace?.projects.find((project) => project.id === workspace.selectedProjectId), + [workspace?.projects, workspace?.selectedProjectId], + ); + + if (!workspace) { + return ( +
+ Loading workspace… +
+ ); + } + + const patternForSession = selectedSession + ? workspace.patterns.find((pattern) => pattern.id === selectedSession.patternId) + : undefined; + const projectForSession = selectedSession + ? workspace.projects.find((project) => project.id === selectedSession.projectId) + : undefined; + + return ( + +
+
Error
+

Something went wrong

+

{error}

+
+ + ) : selectedSession && patternForSession && projectForSession ? ( + api.sendSessionMessage({ sessionId: selectedSession.id, content })} + pattern={patternForSession} + project={projectForSession} + session={selectedSession} + /> + ) : selectedPattern ? ( + { + await api.deletePattern(selectedPattern.id); + } + } + onSave={async () => { + await api.savePattern({ pattern: draftPattern ?? selectedPattern }); + }} + pattern={draftPattern ?? selectedPattern} + /> + ) : ( + + ) + } + sidebar={ + { + void api.addProject(); + }} + onCreateSession={() => { + if (!workspace.selectedProjectId || !workspace.selectedPatternId) { + return; + } + + void api.createSession({ + projectId: workspace.selectedProjectId, + patternId: workspace.selectedPatternId, + }); + }} + onNewPattern={() => { + setDraftPattern(createDraftPattern()); + void api.selectPattern(undefined); + void api.selectSession(undefined); + }} + onPatternSelect={(patternId) => { + void api.selectPattern(patternId); + void api.selectSession(undefined); + }} + onProjectSelect={(projectId) => { + void api.selectProject(projectId); + void api.selectSession(undefined); + }} + onSessionSelect={(sessionId) => { + void api.selectSession(sessionId); + }} + workspace={workspace} + /> + } + /> + ); +} diff --git a/src/renderer/components/AppShell.tsx b/src/renderer/components/AppShell.tsx new file mode 100644 index 0000000..90cebac --- /dev/null +++ b/src/renderer/components/AppShell.tsx @@ -0,0 +1,15 @@ +import type { ReactNode } from 'react'; + +interface AppShellProps { + sidebar: ReactNode; + content: ReactNode; +} + +export function AppShell({ sidebar, content }: AppShellProps) { + return ( +
+ +
{content}
+
+ ); +} diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx new file mode 100644 index 0000000..61f5f1f --- /dev/null +++ b/src/renderer/components/ChatPane.tsx @@ -0,0 +1,140 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; + +import type { PatternDefinition } from '@shared/domain/pattern'; +import type { ProjectRecord } from '@shared/domain/project'; +import type { SessionRecord } from '@shared/domain/session'; + +interface ChatPaneProps { + project: ProjectRecord; + pattern: PatternDefinition; + session: SessionRecord; + onSend: (content: string) => Promise; +} + +export function ChatPane({ project, pattern, session, onSend }: ChatPaneProps) { + const [input, setInput] = useState(''); + const transcriptRef = useRef(null); + + useEffect(() => { + transcriptRef.current?.scrollTo({ + top: transcriptRef.current.scrollHeight, + behavior: 'smooth', + }); + }, [session.messages.length]); + + const isBusy = session.status === 'running'; + const sessionStats = useMemo( + () => `${session.messages.length} messages • ${pattern.agents.length} agent${pattern.agents.length === 1 ? '' : 's'}`, + [pattern.agents.length, session.messages.length], + ); + + return ( +
+
+
+
+
{project.name}
+

{session.title}

+

+ Pattern: {pattern.name} • Mode:{' '} + {pattern.mode} +

+

{sessionStats}

+
+
+ {session.status} +
+
+
+ +
+ {session.messages.length === 0 ? ( +
+ Start the conversation to launch this orchestration against {project.path}. +
+ ) : ( +
+ {session.messages.map((message) => { + const isUser = message.role === 'user'; + return ( +
+
+
+ {message.authorName} +
+
{message.content}
+ {message.pending ? ( +
Streaming…
+ ) : null} +
+
+ ); + })} +
+ )} +
+ +
+ {session.lastError ? ( +
+ {session.lastError} +
+ ) : null} + +
{ + event.preventDefault(); + if (!input.trim()) { + return; + } + + const nextInput = input; + setInput(''); + await onSend(nextInput); + }} + > +