diff --git a/.npmrc b/.npmrc index cdaa8f45..308f32ca 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -only-built-dependencies=@vscode/ripgrep esbuild +only-built-dependencies=@vscode/ripgrep esbuild node-pty diff --git "a/.trae/documents/Claude \345\255\220\344\273\243\347\220\206\347\213\254\347\253\213 JSONL \346\226\207\344\273\266\350\256\276\350\256\241\345\256\236\347\216\260.md" "b/.trae/documents/Claude \345\255\220\344\273\243\347\220\206\347\213\254\347\253\213 JSONL \346\226\207\344\273\266\350\256\276\350\256\241\345\256\236\347\216\260.md" new file mode 100644 index 00000000..d337b11e --- /dev/null +++ "b/.trae/documents/Claude \345\255\220\344\273\243\347\220\206\347\213\254\347\253\213 JSONL \346\226\207\344\273\266\350\256\276\350\256\241\345\256\236\347\216\260.md" @@ -0,0 +1,195 @@ +## 目标 + +将子代理(Task/Subagent)的对话流写到独立 JSONL 文件 `agent_.jsonl`,主会话 JSONL 只保留"调用记录 + 关联信息 + 摘要"。 + +--- + +## 一、类型定义扩展 + +### 1.1 扩展 `BladeJSONLEntry`(src/context/types.ts) + +```typescript +export interface BladeJSONLEntry { + // ... 现有字段 ... + + // === 子代理关联字段(新增) === + /** 父会话 ID(子代理 JSONL 必带,用于回链主会话) */ + parentSessionId?: string; + /** 是否为侧链/子代理会话(Claude 概念兼容) */ + isSidechain?: boolean; + + // === 主会话中的子代理引用字段(新增) === + /** 关联的子代理会话 ID */ + subagentSessionId?: string; + /** 子代理类型 */ + subagentType?: string; + /** 子代理状态 */ + subagentStatus?: 'running' | 'completed' | 'failed' | 'cancelled'; + /** 子代理结果摘要(避免重复全文) */ + subagentSummary?: string; +} +``` + +--- + +## 二、存储层改造 + +### 2.1 新增 `SubagentPersistentStore`(src/context/storage/SubagentPersistentStore.ts) + +专门处理子代理 JSONL 文件的写入,复用现有 `JSONLStore` 和 `pathUtils`。 + +**核心功能:** +- `getSubagentFilePath(projectPath, agentId)` → `~/.blade/projects/{escaped-path}/agent_.jsonl` +- `saveMessage(agentId, ...)` - 追加消息到子代理 JSONL +- `saveToolUse(agentId, ...)` - 追加工具调用 +- `saveToolResult(agentId, ...)` - 追加工具结果 +- `readAll(agentId)` - 读取子代理完整对话流 + +**关键设计:** +- 每条 entry 必带 `sessionId = agent_`、`parentSessionId`、`isSidechain = true` +- 复用 `BladeJSONLEntry` 结构,保持与主会话格式一致 + +### 2.2 扩展 `pathUtils.ts` + +新增函数: +```typescript +export function getSubagentFilePath(projectPath: string, agentId: string): string { + const storagePath = getProjectStoragePath(projectPath); + const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, '_'); + return path.join(storagePath, `${safeId}.jsonl`); +} +``` + +--- + +## 三、子代理执行器改造 + +### 3.1 修改 `SubagentExecutor`(src/agent/subagents/SubagentExecutor.ts) + +**改动点:** +1. 构造函数接收 `projectPath` 参数 +2. 创建 `SubagentPersistentStore` 实例 +3. 在 `execute()` 中: + - 生成 `agent_` 作为 sessionId + - 通过 `runAgenticLoop` 的回调或后处理,将消息写入独立 JSONL + - 返回结果时包含 `agentId` 供主会话引用 + +### 3.2 修改 `BackgroundAgentManager`(src/agent/subagents/BackgroundAgentManager.ts) + +**改动点:** +1. `executeAgent()` 中使用 `SubagentPersistentStore` 写入 JSONL +2. 保留 `AgentSessionStore` 的 JSON 存储作为元数据索引(状态、统计信息) +3. 消息历史从 JSON 迁移到 JSONL(JSON 只存 metadata,不存 messages) + +--- + +## 四、主会话引用节点 + +### 4.1 修改 Task 工具(src/tools/builtin/task/task.ts) + +在返回结果时,添加子代理关联信息到 metadata: + +```typescript +return { + success: true, + llmContent: result.message, + metadata: { + subagentSessionId: agentId, // 新增 + subagentType: subagent_type, + subagentStatus: 'completed', // 新增 + subagentSummary: result.message.slice(0, 500), // 新增 + // ... 其他字段 + }, +}; +``` + +### 4.2 修改 `PersistentStore.saveToolResult()` + +支持写入子代理关联字段: + +```typescript +async saveToolResult( + sessionId: string, + toolId: string, + toolOutput: JsonValue, + parentUuid: string | null = null, + error?: string, + subagentInfo?: { // 新增参数 + subagentSessionId: string; + subagentType: string; + subagentStatus: string; + subagentSummary?: string; + } +): Promise +``` + +--- + +## 五、文件结构变更 + +``` +src/ +├── context/ +│ ├── types.ts # 扩展 BladeJSONLEntry +│ └── storage/ +│ ├── pathUtils.ts # 新增 getSubagentFilePath +│ ├── PersistentStore.ts # 扩展 saveToolResult +│ └── SubagentPersistentStore.ts # 【新建】子代理 JSONL 存储 +├── agent/subagents/ +│ ├── SubagentExecutor.ts # 集成 SubagentPersistentStore +│ ├── BackgroundAgentManager.ts # 集成 SubagentPersistentStore +│ └── AgentSessionStore.ts # 简化:只存 metadata,不存 messages +└── tools/builtin/task/ + └── task.ts # 返回子代理关联信息 +``` + +--- + +## 六、实现步骤 + +| 步骤 | 文件 | 改动内容 | +|------|------|----------| +| 1 | `src/context/types.ts` | 扩展 `BladeJSONLEntry` 添加子代理字段 | +| 2 | `src/context/storage/pathUtils.ts` | 新增 `getSubagentFilePath` 函数 | +| 3 | `src/context/storage/SubagentPersistentStore.ts` | 【新建】子代理 JSONL 存储类 | +| 4 | `src/agent/subagents/SubagentExecutor.ts` | 集成 JSONL 写入 | +| 5 | `src/agent/subagents/BackgroundAgentManager.ts` | 集成 JSONL 写入 | +| 6 | `src/agent/subagents/AgentSessionStore.ts` | 移除 messages 字段,只保留 metadata | +| 7 | `src/context/storage/PersistentStore.ts` | 扩展 `saveToolResult` 支持子代理字段 | +| 8 | `src/tools/builtin/task/task.ts` | 返回结果时添加子代理关联信息 | + +--- + +## 七、数据流示意 + +``` +用户请求 → 主会话 JSONL + ↓ + Task Tool 调用 + ↓ + ┌─────────────────────┐ + │ SubagentExecutor │ + │ 或 BackgroundAgent │ + └─────────────────────┘ + ↓ + 子代理 JSONL (agent_.jsonl) + - sessionId: agent_ + - parentSessionId: 主会话 ID + - isSidechain: true + - 完整对话流(user/assistant/tool_use/tool_result) + ↓ + 主会话 JSONL 写入引用节点 + - type: tool_result + - subagentSessionId: agent_ + - subagentType: Explore + - subagentStatus: completed + - subagentSummary: "..." +``` + +--- + +## 八、预估改动量 + +- **新建文件**:1 个(`SubagentPersistentStore.ts`,约 150 行) +- **修改文件**:7 个 +- **总代码变更**:约 300-400 行 \ No newline at end of file diff --git a/package.json b/package.json index 15b400bb..5f8f0c35 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ ], "scripts": { "dev": "bun --watch src/blade.tsx", + "dev:serve": "bun --watch src/blade.tsx serve --port 4097", "build": "rm -rf dist && bun run scripts/build.ts", "start": "bun run dist/blade.js", "test": "node scripts/test.js", @@ -103,7 +104,9 @@ "vitest": "^3.0.0" }, "optionalDependencies": { - "@vscode/ripgrep": "^1.17.0" + "@vscode/ripgrep": "^1.17.0", + "bun-pty": "^0.4.8", + "node-pty": "1.0.0" }, "dependencies": { "@agentclientprotocol/sdk": "^0.12.0", @@ -128,6 +131,7 @@ "fast-glob": "^3.3.3", "fuse.js": "^7.1.0", "gray-matter": "^4.0.3", + "hono": "^4.7.10", "ink": "npm:@jrichman/ink@6.4.6", "ink-big-text": "^2.0.0", "ink-gradient": "^3.0.0", @@ -139,10 +143,9 @@ "lowlight": "^3.3.0", "lru-cache": "^11.2.4", "nanoid": "^5.1.6", + "open": "^10.1.2", "openai": "^6.2.0", "picomatch": "^4.0.3", - "pino": "^10.1.0", - "pino-pretty": "^13.1.3", "react": "^19.1.1", "react-dom": "^19.1.1", "semver": "^7.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 012d6626..8493baad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: gray-matter: specifier: ^4.0.3 version: 4.0.3 + hono: + specifier: ^4.7.10 + version: 4.11.1 ink: specifier: npm:@jrichman/ink@6.4.6 version: '@jrichman/ink@6.4.6(@types/react@19.2.7)(react@19.2.3)' @@ -107,18 +110,15 @@ importers: nanoid: specifier: ^5.1.6 version: 5.1.6 + open: + specifier: ^10.1.2 + version: 10.2.0 openai: specifier: ^6.2.0 version: 6.14.0(ws@8.18.3)(zod@3.25.76) picomatch: specifier: ^4.0.3 version: 4.0.3 - pino: - specifier: ^10.1.0 - version: 10.1.0 - pino-pretty: - specifier: ^13.1.3 - version: 13.1.3 react: specifier: ^19.1.1 version: 19.2.3 @@ -211,6 +211,12 @@ importers: '@vscode/ripgrep': specifier: ^1.17.0 version: 1.17.0 + bun-pty: + specifier: ^0.4.8 + version: 0.4.8 + node-pty: + specifier: 1.0.0 + version: 1.0.0 packages: @@ -812,9 +818,6 @@ packages: cpu: [x64] os: [win32] - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1130,10 +1133,6 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - auto-bind@5.0.1: resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1167,6 +1166,10 @@ packages: buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + bun-pty@0.4.8: + resolution: {integrity: sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w==} + engines: {bun: '>=1.0.0'} + bun-types@1.3.4: resolution: {integrity: sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ==} @@ -1246,9 +1249,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1296,9 +1296,6 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dayjs@1.11.19: resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} @@ -1383,9 +1380,6 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -1473,9 +1467,6 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-copy@4.0.2: - resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1483,9 +1474,6 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -1641,9 +1629,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - highlight.js@11.11.1: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} @@ -1837,10 +1822,6 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} - joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -2024,6 +2005,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nan@2.25.0: + resolution: {integrity: sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2047,6 +2031,9 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-pty@1.0.0: + resolution: {integrity: sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==} + nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} @@ -2061,10 +2048,6 @@ packages: obliterator@2.0.5: resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2141,23 +2124,6 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-abstract-transport@3.0.0: - resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} - - pino-pretty@13.1.3: - resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} - hasBin: true - - pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - - pino@10.1.0: - resolution: {integrity: sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==} - hasBin: true - pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} @@ -2166,9 +2132,6 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2179,9 +2142,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2193,9 +2153,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2225,10 +2182,6 @@ packages: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -2270,10 +2223,6 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2295,9 +2244,6 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} - secure-json-parse@4.1.0: - resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} - semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -2360,17 +2306,10 @@ packages: resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} engines: {node: '>= 18'} - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -2438,9 +2377,6 @@ packages: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -3274,8 +3210,6 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.16.2': optional: true - '@pinojs/redact@0.4.0': {} - '@pkgjs/parseargs@0.11.0': optional: true @@ -3572,8 +3506,6 @@ snapshots: asynckit@0.4.0: {} - atomic-sleep@1.0.0: {} - auto-bind@5.0.1: {} axios@1.13.2: @@ -3617,6 +3549,9 @@ snapshots: buffer-equal-constant-time@1.0.1: {} + bun-pty@0.4.8: + optional: true + bun-types@1.3.4: dependencies: '@types/node': 22.19.3 @@ -3692,8 +3627,6 @@ snapshots: color-name@1.1.4: {} - colorette@2.0.20: {} - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -3733,8 +3666,6 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - dateformat@4.6.3: {} - dayjs@1.11.19: {} debug@4.4.3: @@ -3794,10 +3725,6 @@ snapshots: encodeurl@2.0.0: {} - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - entities@6.0.1: {} environment@1.1.0: {} @@ -3915,8 +3842,6 @@ snapshots: extend@3.0.2: {} - fast-copy@4.0.2: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -3927,8 +3852,6 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} fastq@1.19.1: @@ -4105,8 +4028,6 @@ snapshots: dependencies: function-bind: 1.1.2 - help-me@5.0.0: {} - highlight.js@11.11.1: {} hono@4.11.1: {} @@ -4280,8 +4201,6 @@ snapshots: jose@6.1.3: {} - joycon@3.1.1: {} - js-cookie@3.0.5: {} js-tiktoken@1.0.21: @@ -4479,6 +4398,9 @@ snapshots: ms@2.1.3: {} + nan@2.25.0: + optional: true + nanoid@3.3.11: {} nanoid@5.1.6: {} @@ -4493,6 +4415,11 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-pty@1.0.0: + dependencies: + nan: 2.25.0 + optional: true + nwsapi@2.2.23: {} object-assign@4.1.1: {} @@ -4501,8 +4428,6 @@ snapshots: obliterator@2.0.5: {} - on-exit-leak-free@2.1.2: {} - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -4582,46 +4507,6 @@ snapshots: picomatch@4.0.3: {} - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-abstract-transport@3.0.0: - dependencies: - split2: 4.2.0 - - pino-pretty@13.1.3: - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 4.0.2 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pump: 3.0.3 - secure-json-parse: 4.1.0 - sonic-boom: 4.2.0 - strip-json-comments: 5.0.3 - - pino-std-serializers@7.0.0: {} - - pino@10.1.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 - pkce-challenge@5.0.1: {} postcss@8.5.6: @@ -4630,8 +4515,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - process-warning@5.0.0: {} - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -4645,11 +4528,6 @@ snapshots: proxy-from-env@1.1.0: {} - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - punycode@2.3.1: {} qs@6.14.0: @@ -4658,8 +4536,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-format-unescaped@4.0.4: {} - range-parser@1.2.1: {} raw-body@3.0.2: @@ -4685,8 +4561,6 @@ snapshots: react@19.2.3: {} - real-require@0.2.0: {} - require-from-string@2.0.2: {} resize-observer-polyfill@1.5.1: {} @@ -4750,8 +4624,6 @@ snapshots: safe-buffer@5.2.1: {} - safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: {} saxes@6.0.0: @@ -4769,8 +4641,6 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 - secure-json-parse@4.1.0: {} - semver@7.7.3: {} send@1.2.1: @@ -4852,14 +4722,8 @@ snapshots: smol-toml@1.6.0: {} - sonic-boom@4.2.0: - dependencies: - atomic-sleep: 1.0.0 - source-map-js@1.2.1: {} - split2@4.2.0: {} - sprintf-js@1.0.3: {} stack-utils@2.0.6: @@ -4927,10 +4791,6 @@ snapshots: glob: 10.5.0 minimatch: 9.0.5 - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - tinybench@2.9.0: {} tinycolor2@1.6.0: {} diff --git a/scripts/build.ts b/scripts/build.ts index 0306bd98..6bc488c3 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,3 +1,12 @@ +import { spawn } from "node:child_process"; +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(__dirname, ".."); + const packageJson = await Bun.file(new URL("../package.json", import.meta.url)).json(); const externals = [ @@ -6,6 +15,8 @@ const externals = [ ...Object.keys(packageJson.peerDependencies ?? {}) ]; +console.log("Building backend..."); + const result = await Bun.build({ entrypoints: ["src/blade.tsx"], outdir: "dist", @@ -20,3 +31,104 @@ if (!result.success) { } process.exit(1); } + +console.log("✓ Backend built successfully\n"); + +const webDir = join(rootDir, "web"); +if (existsSync(join(webDir, "package.json"))) { + console.log("Building web UI..."); + + const runPnpm = ( + args: string[], + label: string, + registry?: string + ): Promise => { + return new Promise((resolve, reject) => { + const child = spawn("pnpm", args, { + cwd: webDir, + stdio: "inherit", + shell: true, + env: registry + ? { + ...process.env, + PNPM_CONFIG_REGISTRY: registry, + NPM_CONFIG_REGISTRY: registry, + npm_config_registry: registry, + } + : process.env, + }); + + child.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Web ${label} failed with exit code ${code}`)); + } + }); + + child.on("error", (err) => { + reject(err); + }); + }); + }; + + const loadWebRegistry = async (): Promise => { + const npmrcPath = join(webDir, ".npmrc"); + if (!existsSync(npmrcPath)) { + return undefined; + } + const npmrc = await readFile(npmrcPath, "utf8"); + const registryLine = npmrc + .split(/\r?\n/) + .map((line) => line.trim()) + .find((line) => line.startsWith("registry=")); + if (!registryLine) { + return undefined; + } + const [, registry] = registryLine.split("=", 2); + return registry?.trim() || undefined; + }; + + const ensureWebDependencies = async (): Promise => { + const nodeModulesDir = join(webDir, "node_modules"); + const webPackageJson = await Bun.file(join(webDir, "package.json")).json(); + const requiredDeps = [ + ...Object.keys(webPackageJson.dependencies ?? {}), + ...Object.keys(webPackageJson.devDependencies ?? {}), + ]; + + const missingDeps = requiredDeps.filter((dep: string) => + !existsSync(join(nodeModulesDir, dep)) + ); + + if (missingDeps.length === 0) { + return; + } + + console.log(`Installing web UI dependencies (${missingDeps.length} missing)...`); + const registry = await loadWebRegistry(); + const lockFile = join(webDir, "pnpm-lock.yaml"); + const installArgs = existsSync(lockFile) + ? ["install", "--frozen-lockfile"] + : ["install"]; + + await runPnpm(installArgs, "install", registry); + }; + + const buildWeb = (): Promise => { + return runPnpm(["build"], "build"); + }; + + try { + await ensureWebDependencies(); + await buildWeb(); + console.log("✓ Web UI built successfully\n"); + } catch (error) { + console.error("Failed to build web UI:", error); + process.exit(1); + } +} else { + console.log("Web UI source not found, skipping web build.\n"); +} + +console.log("✓ Build completed!"); diff --git a/src/agent/Agent.ts b/src/agent/Agent.ts index c0cfe3a2..2b178fc8 100644 --- a/src/agent/Agent.ts +++ b/src/agent/Agent.ts @@ -13,10 +13,10 @@ import * as os from 'os'; import * as path from 'path'; import { - type BladeConfig, - ConfigManager, - type PermissionConfig, - PermissionMode, + type BladeConfig, + ConfigManager, + type PermissionConfig, + PermissionMode, } from '../config/index.js'; import { CompactionService } from '../context/CompactionService.js'; import { ContextManager } from '../context/ContextManager.js'; @@ -30,12 +30,12 @@ import { AttachmentCollector } from '../prompts/processors/AttachmentCollector.j import type { Attachment } from '../prompts/processors/types.js'; import { buildSpecModePrompt, createSpecModeReminder } from '../prompts/spec.js'; import { - type ChatResponse, - type ContentPart, - createChatServiceAsync, - type IChatService, - type Message, - type StreamToolCall, + type ChatResponse, + type ContentPart, + createChatServiceAsync, + type IChatService, + type Message, + type StreamToolCall, } from '../services/ChatServiceInterface.js'; import type { JsonValue } from '../store/types.js'; @@ -51,14 +51,14 @@ function toJsonValue(value: string | object): JsonValue { import { discoverSkills, injectSkillsMetadata } from '../skills/index.js'; import { SpecManager } from '../spec/SpecManager.js'; import { - appActions, - configActions, - ensureStoreInitialized, - getAllModels, - getConfig, - getCurrentModel, - getMcpServers, - getThinkingModeEnabled, + appActions, + configActions, + ensureStoreInitialized, + getAllModels, + getConfig, + getCurrentModel, + getMcpServers, + getThinkingModeEnabled, } from '../store/vanilla.js'; import { getBuiltinTools } from '../tools/builtin/index.js'; import { ExecutionPipeline } from '../tools/execution/ExecutionPipeline.js'; @@ -69,13 +69,13 @@ import { isThinkingModel } from '../utils/modelDetection.js'; import { ExecutionEngine } from './ExecutionEngine.js'; import { subagentRegistry } from './subagents/SubagentRegistry.js'; import type { - AgentOptions, - AgentResponse, - AgentTask, - ChatContext, - LoopOptions, - LoopResult, - UserMessageContent, + AgentOptions, + AgentResponse, + AgentTask, + ChatContext, + LoopOptions, + LoopResult, + UserMessageContent, } from './types.js'; // 创建 Agent 专用 Logger @@ -423,8 +423,9 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl // Plan 模式差异 1: 使用统一入口构建 Plan 模式系统提示词 const { prompt: systemPrompt } = await buildSystemPrompt({ projectPath: process.cwd(), - mode: PermissionMode.PLAN, // Plan 模式会使用 PLAN_MODE_SYSTEM_PROMPT + mode: PermissionMode.PLAN, includeEnvironment: true, + language: this.config.language, }); // Plan 模式差异 2: 在用户消息中注入 system-reminder @@ -556,6 +557,7 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl replaceDefault: replacePrompt, append: appendPrompt, includeEnvironment: false, + language: this.config.language, }); return result.prompt; @@ -645,7 +647,10 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl lastMessageUuid = await contextMgr.saveMessage( context.sessionId, 'user', - textContent + textContent, + null, + undefined, + context.subagentInfo ); } else if (textContent.trim() === '') { logger.debug('[Agent] 跳过保存空用户消息'); @@ -990,7 +995,9 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl context.sessionId, 'assistant', turnResult.content, - lastMessageUuid // 链接到上一条消息 + lastMessageUuid, + undefined, + context.subagentInfo ); } else { logger.debug('[Agent] 跳过保存空响应(任务完成时)'); @@ -1031,7 +1038,9 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl context.sessionId, 'assistant', turnResult.content, - lastMessageUuid // 链接到上一条消息 + lastMessageUuid, + undefined, + context.subagentInfo ); } else { logger.debug('[Agent] 跳过保存空响应(工具调用时)'); @@ -1115,7 +1124,8 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl context.sessionId, toolCall.function.name, params, - lastMessageUuid + lastMessageUuid, + context.subagentInfo ); } } catch (error) { @@ -1238,9 +1248,11 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl lastMessageUuid = await contextMgr.saveToolResult( context.sessionId, toolCall.id, + toolCall.function.name, result.success ? toJsonValue(result.llmContent) : null, toolUseUuid, - result.success ? undefined : result.error?.message + result.success ? undefined : result.error?.message, + context.subagentInfo ); } } catch (err) { @@ -1858,6 +1870,7 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl replaceDefault: replacePrompt, append: appendPrompt, includeEnvironment: false, + language: this.config.language, }); if (result.prompt) { diff --git a/src/agent/subagents/BackgroundAgentManager.ts b/src/agent/subagents/BackgroundAgentManager.ts index f22425d6..864d630b 100644 --- a/src/agent/subagents/BackgroundAgentManager.ts +++ b/src/agent/subagents/BackgroundAgentManager.ts @@ -194,44 +194,43 @@ export class BackgroundAgentManager { const startTime = Date.now(); try { - // 检查是否已取消 if (signal.aborted) { throw new Error('Agent execution was cancelled'); } - // 1. 创建 Agent const systemPrompt = config.systemPrompt || ''; const agent = await Agent.create({ systemPrompt, toolWhitelist: config.tools, }); - // 2. 执行 agentic loop - // 创建 context 对象以便获取更新后的消息历史 const context = { messages: existingMessages || [], userId: 'subagent', - sessionId: parentSessionId || `subagent_${agentId}`, + sessionId: agentId, workspaceRoot: process.cwd(), permissionMode, + subagentInfo: { + parentSessionId: parentSessionId || '', + subagentType: config.name, + isSidechain: true, + }, }; const loopResult = await agent.runAgenticLoop(prompt, context, { - signal, // 传递 AbortSignal 以支持 killAgent + signal, }); - // 3. 保存更新后的消息历史(用于 resume) - // runAgenticLoop 会修改 context.messages(添加 LLM 响应、工具调用等) this.sessionStore.updateSession(agentId, { messages: context.messages, }); - // 4. 构建结果 const duration = Date.now() - startTime; const result: SubagentResult = loopResult.success ? { success: true, message: loopResult.finalMessage || '', + agentId, stats: { tokens: loopResult.metadata?.tokensUsed || 0, toolCalls: loopResult.metadata?.toolCallsCount || 0, @@ -241,11 +240,11 @@ export class BackgroundAgentManager { : { success: false, message: '', + agentId, error: loopResult.error?.message || 'Unknown error', stats: { duration }, }; - // 5. 更新会话状态 this.sessionStore.markCompleted( agentId, { @@ -262,7 +261,6 @@ export class BackgroundAgentManager { const duration = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : String(error); - // 更新会话状态 this.sessionStore.markCompleted( agentId, { @@ -278,6 +276,7 @@ export class BackgroundAgentManager { return { success: false, message: '', + agentId, error: errorMessage, stats: { duration }, }; diff --git a/src/agent/subagents/SubagentExecutor.ts b/src/agent/subagents/SubagentExecutor.ts index b49e1935..3819b359 100644 --- a/src/agent/subagents/SubagentExecutor.ts +++ b/src/agent/subagents/SubagentExecutor.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'crypto'; import { Agent } from '../Agent.js'; import type { SubagentConfig, SubagentContext, SubagentResult } from './types.js'; @@ -8,6 +9,7 @@ import type { SubagentConfig, SubagentContext, SubagentResult } from './types.js * - 创建子 Agent 实例 * - 配置工具白名单 * - 执行任务并返回结果 + * - 将子代理对话流写入独立 JSONL 文件 */ export class SubagentExecutor { constructor(private config: SubagentConfig) {} @@ -15,21 +17,19 @@ export class SubagentExecutor { /** * 执行 subagent 任务 * 无状态设计:systemPrompt 通过 ChatContext 传入 + * 子代理对话流写入独立 JSONL 文件 (agent_.jsonl) */ async execute(context: SubagentContext): Promise { const startTime = Date.now(); + const agentId = `agent_${randomUUID()}`; try { - // 1. 构建系统提示 const systemPrompt = this.buildSystemPrompt(context); - // 2. 创建子 Agent(无状态设计:不再传递 systemPrompt 到 AgentOptions) const agent = await Agent.create({ - toolWhitelist: this.config.tools, // 应用工具白名单 + toolWhitelist: this.config.tools, }); - // 3. 执行对话循环(让 Agent 自主完成任务) - // 无状态设计:systemPrompt 通过 ChatContext 传入 let finalMessage = ''; let toolCallCount = 0; let tokensUsed = 0; @@ -39,10 +39,15 @@ export class SubagentExecutor { { messages: [], userId: 'subagent', - sessionId: context.parentSessionId || `subagent_${Date.now()}`, + sessionId: agentId, workspaceRoot: process.cwd(), - permissionMode: context.permissionMode, // 继承父 Agent 的权限模式 - systemPrompt, // 🆕 无状态设计:通过 context 传入 systemPrompt + permissionMode: context.permissionMode, + systemPrompt, + subagentInfo: { + parentSessionId: context.parentSessionId || '', + subagentType: this.config.name, + isSidechain: true, + }, }, { onToolStart: context.onToolStart @@ -63,12 +68,12 @@ export class SubagentExecutor { throw new Error(loopResult.error?.message || 'Subagent execution failed'); } - // 4. 返回结果 const duration = Date.now() - startTime; return { success: true, message: finalMessage, + agentId, stats: { tokens: tokensUsed, toolCalls: toolCallCount, @@ -80,6 +85,7 @@ export class SubagentExecutor { return { success: false, message: '', + agentId, error: error instanceof Error ? error.message : String(error), stats: { duration, @@ -88,9 +94,6 @@ export class SubagentExecutor { } } - /** - * 构建系统提示 - */ private buildSystemPrompt(_context: SubagentContext): string { return this.config.systemPrompt || ''; } diff --git a/src/agent/subagents/types.ts b/src/agent/subagents/types.ts index 0c84d059..8d5983df 100644 --- a/src/agent/subagents/types.ts +++ b/src/agent/subagents/types.ts @@ -138,6 +138,9 @@ export interface SubagentResult { /** 错误信息(如果失败) */ error?: string; + /** 子代理会话 ID(用于关联独立 JSONL 文件) */ + agentId?: string; + /** 执行统计 */ stats?: { /** Token 使用量 */ diff --git a/src/agent/types.ts b/src/agent/types.ts index 29d4bdd3..b6bd9b19 100644 --- a/src/agent/types.ts +++ b/src/agent/types.ts @@ -16,6 +16,15 @@ import type { ToolResult } from '../tools/types/ToolTypes.js'; */ export type UserMessageContent = string | ContentPart[]; +/** + * 子代理信息(用于 JSONL 写入) + */ +export interface SubagentInfoForContext { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; +} + /** * 聊天上下文接口 * @@ -34,6 +43,7 @@ export interface ChatContext { confirmationHandler?: ConfirmationHandler; // 会话级别的确认处理器 permissionMode?: PermissionMode; // 当前权限模式(用于 Plan 模式判断) systemPrompt?: string; // 动态传入的系统提示词(无状态设计) + subagentInfo?: SubagentInfoForContext; // 子代理信息(用于 JSONL 写入) } /** diff --git a/src/api/schemas.ts b/src/api/schemas.ts new file mode 100644 index 00000000..4f515b1f --- /dev/null +++ b/src/api/schemas.ts @@ -0,0 +1,110 @@ +import { z } from 'zod'; + +export const PermissionModeSchema = z.enum(['default', 'autoEdit', 'yolo', 'plan', 'spec']); +export type PermissionMode = z.infer; + +export const PermissionModeEnum = { + DEFAULT: 'default', + AUTO_EDIT: 'autoEdit', + YOLO: 'yolo', + PLAN: 'plan', + SPEC: 'spec', +} as const; + +export const MessageRoleSchema = z.enum(['user', 'assistant', 'system', 'tool']); +export type MessageRole = z.infer; + +export const MessageSchema = z.object({ + id: z.string(), + role: MessageRoleSchema, + content: z.string(), + timestamp: z.number(), + metadata: z.record(z.unknown()).optional(), + thinkingContent: z.string().optional(), + tool_call_id: z.string().optional(), + name: z.string().optional(), + tool_calls: z.unknown().optional(), +}); +export type Message = z.infer; + +export const SessionSchema = z.object({ + sessionId: z.string(), + projectPath: z.string(), + title: z.string().optional(), + gitBranch: z.string().optional(), + messageCount: z.number(), + firstMessageTime: z.string(), + lastMessageTime: z.string(), + hasErrors: z.boolean(), + filePath: z.string().optional(), +}); +export type Session = z.infer; + +export const BusEventSchema = z.object({ + type: z.string(), + properties: z.record(z.unknown()), +}); +export type BusEvent = z.infer; + +export const SendMessageRequestSchema = z.object({ + content: z.string(), + permissionMode: PermissionModeSchema.optional(), + attachments: z.array(z.object({ + type: z.enum(['file', 'image', 'url']), + path: z.string().optional(), + url: z.string().optional(), + content: z.string().optional(), + })).optional(), +}); +export type SendMessageRequest = z.infer; + +export const SendMessageResponseSchema = z.object({ + messageId: z.string(), + role: MessageRoleSchema, + content: z.string(), + timestamp: z.string(), +}); +export type SendMessageResponse = z.infer; + +export const PermissionResponseSchema = z.object({ + approved: z.boolean(), + remember: z.boolean().optional(), + scope: z.enum(['once', 'session']).optional(), + targetMode: PermissionModeSchema.optional(), + feedback: z.string().optional(), + answers: z.record(z.union([z.string(), z.array(z.string())])).optional(), +}); +export type PermissionResponse = z.infer; + +export const ModelConfigSchema = z.object({ + id: z.string(), + name: z.string(), + provider: z.string(), + model: z.string(), + baseUrl: z.string().optional(), + apiKey: z.string().optional(), + maxContextTokens: z.number().optional(), +}); +export type ModelConfig = z.infer; + +export const EditorThemeSchema = z.enum(['vs-dark', 'vs-light', 'hc-black']); +export type EditorTheme = z.infer; + +export const UiThemeSchema = z.enum(['light', 'dark', 'system']); +export type UiTheme = z.infer; + +export const GeneralSettingsSchema = z.object({ + language: z.string(), + theme: z.string(), + uiTheme: UiThemeSchema, + autoSaveSessions: z.boolean(), + notifyBuild: z.boolean(), + notifyErrors: z.boolean(), + notifySounds: z.boolean(), + privacyTelemetry: z.boolean(), + privacyCrash: z.boolean(), +}); +export type GeneralSettings = z.infer; + +export const GeneralSettingsUpdateSchema = GeneralSettingsSchema.partial(); +export type GeneralSettingsUpdate = z.infer; diff --git a/src/blade.tsx b/src/blade.tsx index f1b1cf78..16aed9f6 100644 --- a/src/blade.tsx +++ b/src/blade.tsx @@ -18,12 +18,14 @@ import { doctorCommands } from './commands/doctor.js'; import { installCommands } from './commands/install.js'; import { mcpCommands } from './commands/mcp.js'; import { handlePrintMode } from './commands/print.js'; +import { serveCommand } from './commands/serve.js'; import { updateCommands } from './commands/update.js'; +import { webCommand } from './commands/web.js'; import { Logger } from './logging/Logger.js'; import { initializeGracefulShutdown } from './services/GracefulShutdown.js'; import { checkVersionOnStartup } from './services/VersionChecker.js'; -import { AppWrapper as BladeApp } from './ui/App.js'; import type { AppProps } from './ui/App.js'; +import { AppWrapper as BladeApp } from './ui/App.js'; // ⚠️ 关键:在创建任何 logger 之前,先解析 --debug 参数并设置全局配置 // 这样可以确保所有 logger(包括 middleware、commands 中的)都能正确输出到终端 @@ -96,6 +98,8 @@ export async function main() { .command(doctorCommands) .command(updateCommands) .command(installCommands) + .command(webCommand) + .command(serveCommand) // 自动生成补全(隐藏,避免干扰普通用户) .completion('completion', false) diff --git a/src/cli/network.ts b/src/cli/network.ts new file mode 100644 index 00000000..444bb07d --- /dev/null +++ b/src/cli/network.ts @@ -0,0 +1,33 @@ +import type { Options } from 'yargs'; + +export interface NetworkOptions { + port: number; + hostname: string; + cors: string[]; +} + +export const networkOptions: Record = { + port: { + type: 'number', + describe: 'Port to listen on (0 for auto)', + default: 0, + }, + hostname: { + type: 'string', + describe: 'Hostname to listen on', + default: '127.0.0.1', + }, + cors: { + type: 'array', + describe: 'Additional domains to allow for CORS', + default: [] as string[], + }, +}; + +export function resolveNetworkOptions(args: Partial): NetworkOptions { + return { + port: args.port ?? 0, + hostname: args.hostname ?? '127.0.0.1', + cors: (args.cors ?? []) as string[], + }; +} diff --git a/src/commands/serve.ts b/src/commands/serve.ts new file mode 100644 index 00000000..362d8723 --- /dev/null +++ b/src/commands/serve.ts @@ -0,0 +1,42 @@ +import chalk from 'chalk'; +import type { ArgumentsCamelCase, CommandModule } from 'yargs'; +import { networkOptions, resolveNetworkOptions } from '../cli/network.js'; +import { BladeServer, getNetworkIPs } from '../server/index.js'; +import { ensureStoreInitialized } from '../store/vanilla.js'; + +interface ServeArgs { + port?: number; + hostname?: string; + cors?: string[]; +} + +export const serveCommand: CommandModule = { + command: 'serve', + describe: 'Start a headless Blade server (no browser)', + builder: networkOptions, + handler: async (args: ArgumentsCamelCase) => { + const opts = resolveNetworkOptions(args); + + await ensureStoreInitialized(); + + if (!process.env.BLADE_SERVER_PASSWORD) { + console.log(chalk.yellow('Warning: BLADE_SERVER_PASSWORD is not set; server is unsecured.')); + } + + const server = await BladeServer.listenAsync(opts); + + console.log(`Blade server listening on http://${server.hostname}:${server.port}`); + + if (opts.hostname === '0.0.0.0') { + const networkIPs = getNetworkIPs(); + for (const ip of networkIPs) { + console.log(` Network: http://${ip}:${server.port}`); + } + } + + await new Promise(() => { + // Keep the server running until process is terminated + }); + await server.stop(); + }, +}; diff --git a/src/commands/web.ts b/src/commands/web.ts new file mode 100644 index 00000000..db24c6b3 --- /dev/null +++ b/src/commands/web.ts @@ -0,0 +1,70 @@ +import chalk from 'chalk'; +import open from 'open'; +import type { ArgumentsCamelCase, CommandModule } from 'yargs'; +import { networkOptions, resolveNetworkOptions } from '../cli/network.js'; +import { BladeServer, getNetworkIPs } from '../server/index.js'; +import { ensureStoreInitialized } from '../store/vanilla.js'; +import { getVersion } from '../utils/packageInfo.js'; + +const BLADE_LOGO = ` + ____ _ _ + | __ )| | __ _ __| | ___ + | _ \\| |/ _\` |/ _\` |/ _ \\ + | |_) | | (_| | (_| | __/ + |____/|_|\\__,_|\\__,_|\\___| +`; + +interface WebArgs { + port?: number; + hostname?: string; + cors?: string[]; +} + +export const webCommand: CommandModule = { + command: 'web', + describe: 'Start Blade server and open web interface', + builder: networkOptions, + handler: async (args: ArgumentsCamelCase) => { + const opts = resolveNetworkOptions(args); + + await ensureStoreInitialized(); + + if (!process.env.BLADE_SERVER_PASSWORD) { + console.log(chalk.yellow('⚠️ BLADE_SERVER_PASSWORD is not set; server is unsecured.')); + console.log(chalk.gray(' Set this environment variable to enable Basic Auth.\n')); + } + + const server = await BladeServer.listenAsync(opts); + + console.log(chalk.cyan(BLADE_LOGO)); + console.log(chalk.gray(` v${getVersion()}\n`)); + + if (opts.hostname === '0.0.0.0') { + const localhostUrl = `http://localhost:${server.port}`; + console.log(chalk.blue.bold(' Local access: '), chalk.white(localhostUrl)); + + const networkIPs = getNetworkIPs(); + for (const ip of networkIPs) { + console.log(chalk.blue.bold(' Network access: '), chalk.white(`http://${ip}:${server.port}`)); + } + + open(localhostUrl).catch(() => { + // Ignore errors when opening browser + }); + } else { + const displayUrl = server.url.toString(); + console.log(chalk.blue.bold(' Web interface: '), chalk.white(displayUrl)); + open(displayUrl).catch(() => { + // Ignore errors when opening browser + }); + } + + console.log(''); + console.log(chalk.gray(' Press Ctrl+C to stop the server.\n')); + + await new Promise(() => { + // Keep the server running until process is terminated + }); + await server.stop(); + }, +}; diff --git a/src/config/ConfigService.ts b/src/config/ConfigService.ts index 89d31c0d..6b4f1e84 100644 --- a/src/config/ConfigService.ts +++ b/src/config/ConfigService.ts @@ -89,6 +89,12 @@ const FIELD_ROUTING_TABLE: Record = { mergeStrategy: 'replace', persistable: true, }, + uiTheme: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, language: { target: 'config', defaultScope: 'global', @@ -101,6 +107,48 @@ const FIELD_ROUTING_TABLE: Record = { mergeStrategy: 'replace', persistable: true, }, + fontSize: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + autoSaveSessions: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + notifyBuild: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + notifyErrors: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + notifySounds: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + privacyTelemetry: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, + privacyCrash: { + target: 'config', + defaultScope: 'global', + mergeStrategy: 'replace', + persistable: true, + }, // ===== settings.json 字段(行为配置)===== permissionMode: { @@ -141,7 +189,7 @@ const FIELD_ROUTING_TABLE: Record = { }, mcpServers: { target: 'config', - defaultScope: 'project', // MCP 服务器配置默认存储在项目配置中 + defaultScope: 'global', // MCP 服务器配置存储在用户全局配置中 mergeStrategy: 'replace', persistable: true, }, @@ -166,12 +214,6 @@ const FIELD_ROUTING_TABLE: Record = { mergeStrategy: 'replace', persistable: false, }, - fontSize: { - target: 'config', - defaultScope: 'global', - mergeStrategy: 'replace', - persistable: false, - }, mcpEnabled: { target: 'config', defaultScope: 'global', diff --git a/src/config/defaults.ts b/src/config/defaults.ts index f03c589c..8b16c771 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -23,10 +23,19 @@ export const DEFAULT_CONFIG: BladeConfig = { timeout: 180000, // 180秒超时(长上下文 agentic 场景需要更长时间) // UI - theme: 'GitHub', + theme: 'dracula', + uiTheme: 'system', language: 'zh-CN', fontSize: 14, + // General Settings + autoSaveSessions: true, + notifyBuild: true, + notifyErrors: false, + notifySounds: false, + privacyTelemetry: false, + privacyCrash: true, + // 核心 debug: false, diff --git a/src/config/types.ts b/src/config/types.ts index 7e1f86b6..dd1e4d81 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -50,13 +50,6 @@ export enum PermissionMode { SPEC = 'spec', } -/** - * Hooks 配置 - * 导入自 hooks 模块 - */ -import type { HookConfig as HookConfigType } from '../hooks/types/HookTypes.js'; -export type HookConfig = HookConfigType; - export interface ModelConfig { id: string; name: string; @@ -76,6 +69,14 @@ export interface ModelConfig { providerId?: string; } +import { UiTheme } from '@/api/schemas.js'; +/** + * Hooks 配置 + * 导入自 hooks 模块 + */ +import type { HookConfig as HookConfigType } from '../hooks/types/HookTypes.js'; +export type HookConfig = HookConfigType; + export interface BladeConfig { // ===================================== // 基础配置 (来自 config.json - 扁平化) @@ -96,9 +97,18 @@ export interface BladeConfig { // UI theme: string; + uiTheme: UiTheme; language: string; fontSize: number; + // General Settings + autoSaveSessions: boolean; + notifyBuild: boolean; + notifyErrors: boolean; + notifySounds: boolean; + privacyTelemetry: boolean; + privacyCrash: boolean; + // 核心 // debug 支持 boolean 或字符串过滤器(如 "agent,ui" 或 "!chat,!loop") debug: string | boolean; diff --git a/src/context/ContextManager.ts b/src/context/ContextManager.ts index b5f96e01..2cc0d268 100644 --- a/src/context/ContextManager.ts +++ b/src/context/ContextManager.ts @@ -259,9 +259,21 @@ export class ContextManager { metadata?: { model?: string; usage?: { input_tokens: number; output_tokens: number }; + }, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; } ): Promise { - return this.persistent.saveMessage(sessionId, role, content, parentUuid, metadata); + return this.persistent.saveMessage( + sessionId, + role, + content, + parentUuid, + metadata, + subagentInfo + ); } /** @@ -271,9 +283,20 @@ export class ContextManager { sessionId: string, toolName: string, toolInput: JsonValue, - parentUuid: string | null = null + parentUuid: string | null = null, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; + } ): Promise { - return this.persistent.saveToolUse(sessionId, toolName, toolInput, parentUuid); + return this.persistent.saveToolUse( + sessionId, + toolName, + toolInput, + parentUuid, + subagentInfo + ); } /** @@ -282,16 +305,31 @@ export class ContextManager { async saveToolResult( sessionId: string, toolId: string, + toolName: string, toolOutput: JsonValue, parentUuid: string | null = null, - error?: string + error?: string, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; + }, + subagentRef?: { + subagentSessionId: string; + subagentType: string; + subagentStatus: 'running' | 'completed' | 'failed' | 'cancelled'; + subagentSummary?: string; + } ): Promise { return this.persistent.saveToolResult( sessionId, toolId, + toolName, toolOutput, parentUuid, - error + error, + subagentInfo, + subagentRef ); } diff --git a/src/context/storage/PersistentStore.ts b/src/context/storage/PersistentStore.ts index af21ebef..20a16b93 100644 --- a/src/context/storage/PersistentStore.ts +++ b/src/context/storage/PersistentStore.ts @@ -3,17 +3,17 @@ import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import type { JsonValue, MessageRole } from '../../store/types.js'; import type { - BladeJSONLEntry, - ContextData, - ConversationContext, - SessionContext, + BladeJSONLEntry, + ContextData, + ConversationContext, + SessionContext, } from '../types.js'; import { JSONLStore } from './JSONLStore.js'; import { - detectGitBranch, - getProjectStoragePath, - getSessionFilePath, - listProjectDirectories, + detectGitBranch, + getProjectStoragePath, + getSessionFilePath, + listProjectDirectories, } from './pathUtils.js'; /** @@ -59,6 +59,11 @@ export class PersistentStore { metadata?: { model?: string; usage?: { input_tokens: number; output_tokens: number }; + }, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; } ): Promise { try { @@ -86,6 +91,11 @@ export class PersistentStore { content, ...(metadata || {}), }, + ...(subagentInfo && { + parentSessionId: subagentInfo.parentSessionId, + isSidechain: subagentInfo.isSidechain, + subagentType: subagentInfo.subagentType, + }), }; await store.append(entry); @@ -103,7 +113,12 @@ export class PersistentStore { sessionId: string, toolName: string, toolInput: JsonValue, - parentUuid: string | null = null + parentUuid: string | null = null, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; + } ): Promise { try { const filePath = getSessionFilePath(this.projectPath, sessionId); @@ -127,6 +142,11 @@ export class PersistentStore { name: toolName, input: toolInput, }, + ...(subagentInfo && { + parentSessionId: subagentInfo.parentSessionId, + isSidechain: subagentInfo.isSidechain, + subagentType: subagentInfo.subagentType, + }), }; await store.append(entry); @@ -146,9 +166,21 @@ export class PersistentStore { async saveToolResult( sessionId: string, toolId: string, + toolName: string, toolOutput: JsonValue, parentUuid: string | null = null, - error?: string + error?: string, + subagentInfo?: { + parentSessionId: string; + subagentType: string; + isSidechain: boolean; + }, + subagentRef?: { + subagentSessionId: string; + subagentType: string; + subagentStatus: 'running' | 'completed' | 'failed' | 'cancelled'; + subagentSummary?: string; + } ): Promise { try { const filePath = getSessionFilePath(this.projectPath, sessionId); @@ -167,11 +199,27 @@ export class PersistentStore { role: 'assistant', content: '', }, + tool: { + id: toolId, + name: toolName, + input: {}, + }, toolResult: { id: toolId, output: toolOutput, error, }, + ...(subagentInfo && { + parentSessionId: subagentInfo.parentSessionId, + isSidechain: subagentInfo.isSidechain, + subagentType: subagentInfo.subagentType, + }), + ...(subagentRef && { + subagentSessionId: subagentRef.subagentSessionId, + subagentType: subagentRef.subagentType, + subagentStatus: subagentRef.subagentStatus, + subagentSummary: subagentRef.subagentSummary, + }), }; await store.append(entry); diff --git a/src/context/storage/SubagentPersistentStore.ts b/src/context/storage/SubagentPersistentStore.ts new file mode 100644 index 00000000..854a4e6f --- /dev/null +++ b/src/context/storage/SubagentPersistentStore.ts @@ -0,0 +1,228 @@ +import { nanoid } from 'nanoid'; +import * as fs from 'node:fs/promises'; +import type { JsonValue, MessageRole } from '../../store/types.js'; +import type { BladeJSONLEntry } from '../types.js'; +import { JSONLStore } from './JSONLStore.js'; +import { detectGitBranch, getProjectStoragePath, getSubagentFilePath } from './pathUtils.js'; + +export interface SubagentInfo { + agentId: string; + subagentType: string; + parentSessionId: string; +} + +export class SubagentPersistentStore { + private readonly projectPath: string; + private readonly version: string; + + constructor(projectPath: string = process.cwd(), version: string = '0.0.10') { + this.projectPath = projectPath; + this.version = version; + } + + async initialize(): Promise { + try { + const storagePath = getProjectStoragePath(this.projectPath); + await fs.mkdir(storagePath, { recursive: true, mode: 0o755 }); + } catch (error) { + console.warn('[SubagentPersistentStore] 无法创建存储目录:', error); + } + } + + async saveMessage( + subagentInfo: SubagentInfo, + messageRole: MessageRole, + content: string, + parentUuid: string | null = null, + metadata?: { + model?: string; + usage?: { input_tokens: number; output_tokens: number }; + } + ): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, subagentInfo.agentId); + const store = new JSONLStore(filePath); + + const entry: BladeJSONLEntry = { + uuid: nanoid(), + parentUuid, + sessionId: subagentInfo.agentId, + parentSessionId: subagentInfo.parentSessionId, + isSidechain: true, + timestamp: new Date().toISOString(), + type: + messageRole === 'user' + ? 'user' + : messageRole === 'assistant' + ? 'assistant' + : messageRole === 'tool' + ? 'tool_result' + : 'system', + cwd: this.projectPath, + gitBranch: detectGitBranch(this.projectPath), + version: this.version, + message: { + role: messageRole, + content, + ...(metadata || {}), + }, + subagentType: subagentInfo.subagentType, + }; + + await store.append(entry); + return entry.uuid; + } catch (error) { + console.error( + `[SubagentPersistentStore] 保存消息失败 (agent: ${subagentInfo.agentId}):`, + error + ); + throw error; + } + } + + async saveToolUse( + subagentInfo: SubagentInfo, + toolName: string, + toolInput: JsonValue, + parentUuid: string | null = null + ): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, subagentInfo.agentId); + const store = new JSONLStore(filePath); + + const entry: BladeJSONLEntry = { + uuid: nanoid(), + parentUuid, + sessionId: subagentInfo.agentId, + parentSessionId: subagentInfo.parentSessionId, + isSidechain: true, + timestamp: new Date().toISOString(), + type: 'tool_use', + cwd: this.projectPath, + gitBranch: detectGitBranch(this.projectPath), + version: this.version, + message: { + role: 'assistant', + content: '', + }, + tool: { + id: nanoid(), + name: toolName, + input: toolInput, + }, + subagentType: subagentInfo.subagentType, + }; + + await store.append(entry); + return entry.uuid; + } catch (error) { + console.error( + `[SubagentPersistentStore] 保存工具调用失败 (agent: ${subagentInfo.agentId}):`, + error + ); + throw error; + } + } + + async saveToolResult( + subagentInfo: SubagentInfo, + toolId: string, + toolName: string, + toolOutput: JsonValue, + parentUuid: string | null = null, + error?: string + ): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, subagentInfo.agentId); + const store = new JSONLStore(filePath); + + const entry: BladeJSONLEntry = { + uuid: nanoid(), + parentUuid, + sessionId: subagentInfo.agentId, + parentSessionId: subagentInfo.parentSessionId, + isSidechain: true, + timestamp: new Date().toISOString(), + type: 'tool_result', + cwd: this.projectPath, + gitBranch: detectGitBranch(this.projectPath), + version: this.version, + message: { + role: 'assistant', + content: '', + }, + tool: { + id: toolId, + name: toolName, + input: {}, + }, + toolResult: { + id: toolId, + output: toolOutput, + error, + }, + subagentType: subagentInfo.subagentType, + }; + + await store.append(entry); + return entry.uuid; + } catch (error) { + console.error( + `[SubagentPersistentStore] 保存工具结果失败 (agent: ${subagentInfo.agentId}):`, + error + ); + throw error; + } + } + + async readAll(agentId: string): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, agentId); + const store = new JSONLStore(filePath); + return await store.readAll(); + } catch (error) { + console.error( + `[SubagentPersistentStore] 读取子代理会话失败 (agent: ${agentId}):`, + error + ); + return []; + } + } + + async exists(agentId: string): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, agentId); + const store = new JSONLStore(filePath); + return await store.exists(); + } catch { + return false; + } + } + + async delete(agentId: string): Promise { + try { + const filePath = getSubagentFilePath(this.projectPath, agentId); + const store = new JSONLStore(filePath); + await store.delete(); + } catch (error) { + console.warn( + `[SubagentPersistentStore] 删除子代理会话失败 (agent: ${agentId}):`, + error + ); + } + } + + async getStats(agentId: string): Promise<{ + exists: boolean; + size: number; + lineCount: number; + }> { + try { + const filePath = getSubagentFilePath(this.projectPath, agentId); + const store = new JSONLStore(filePath); + return await store.getStats(); + } catch { + return { exists: false, size: 0, lineCount: 0 }; + } + } +} diff --git a/src/context/storage/pathUtils.ts b/src/context/storage/pathUtils.ts index 208fccd7..c6a1a712 100644 --- a/src/context/storage/pathUtils.ts +++ b/src/context/storage/pathUtils.ts @@ -68,6 +68,19 @@ export function getSessionFilePath(projectPath: string, sessionId: string): stri return path.join(getProjectStoragePath(projectPath), `${sessionId}.jsonl`); } +/** + * 获取子代理会话文件路径 + * 子代理 JSONL 文件与主会话存储在同一目录下 + * + * @param projectPath 项目绝对路径 + * @param agentId 子代理 ID (格式: agent_) + * @returns ~/.blade/projects/{escaped-path}/{agentId}.jsonl + */ +export function getSubagentFilePath(projectPath: string, agentId: string): string { + const safeId = agentId.replace(/[^a-zA-Z0-9_-]/g, '_'); + return path.join(getProjectStoragePath(projectPath), `${safeId}.jsonl`); +} + /** * 检测当前项目的 Git 分支 * @param projectPath 项目路径 diff --git a/src/context/types.ts b/src/context/types.ts index 947e13d0..2b3fe561 100644 --- a/src/context/types.ts +++ b/src/context/types.ts @@ -184,4 +184,20 @@ export interface BladeJSONLEntry { /** 包含的文件列表 */ filesIncluded?: string[]; }; + + // === 子代理关联字段 === + /** 父会话 ID(子代理 JSONL 必带,用于回链主会话) */ + parentSessionId?: string; + /** 是否为侧链/子代理会话(Claude 概念兼容) */ + isSidechain?: boolean; + + // === 主会话中的子代理引用字段 === + /** 关联的子代理会话 ID */ + subagentSessionId?: string; + /** 子代理类型 */ + subagentType?: string; + /** 子代理状态 */ + subagentStatus?: 'running' | 'completed' | 'failed' | 'cancelled'; + /** 子代理结果摘要(避免重复全文) */ + subagentSummary?: string; } diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index 32a6ce8a..5d6be3ac 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -1,22 +1,21 @@ /** - * 统一日志服务(基于 Pino) + * 统一日志服务 * * 设计原则: * 1. 只在 debug 模式下输出终端日志 - * 2. 始终将日志写入文件 (~/.blade/logs/blade.log) + * 2. 始终将日志写入文件 (~/.blade/logs/blade-{sessionId}.jsonl) * 3. 支持分类日志(agent, ui, tool, service 等) * 4. 提供多级别日志(debug, info, warn, error) * 5. 使用 Logger.setGlobalDebug() 设置配置(避免循环依赖) * * 双路输出架构: - * - 文件:Pino file transport(JSON 格式,始终记录) + * - 文件:appendFileSync 写入 JSONL 格式 * - 终端:手动 console.error 输出(应用分类过滤) */ -import { promises as fs } from 'node:fs'; +import { appendFileSync, existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import pino, { type Logger as PinoLogger } from 'pino'; export enum LogLevel { DEBUG = 0, @@ -41,295 +40,163 @@ export enum LogCategory { } export interface LoggerOptions { - enabled?: boolean; // 强制启用/禁用(覆盖 ConfigManager) - minLevel?: LogLevel; // 最小日志级别(默认 DEBUG) - category?: LogCategory; // 日志分类 + enabled?: boolean; + minLevel?: LogLevel; + category?: LogCategory; } -// Pino 日志级别映射 -const PINO_LEVELS: Record = { - [LogLevel.DEBUG]: 'debug', - [LogLevel.INFO]: 'info', - [LogLevel.WARN]: 'warn', - [LogLevel.ERROR]: 'error', -}; - -/** - * 优雅关闭日志系统 - * 在应用退出前调用,确保所有日志都已写入 - */ -export async function shutdownLogger(): Promise { - if (pinoInstance) { - try { - // Pino 的 flush 方法是同步的,但我们包装成 Promise 以便统一处理 - if (typeof pinoInstance.flush === 'function') { - pinoInstance.flush(); - } - // 给一点时间让 Worker 线程完成写入 - await new Promise((resolve) => setTimeout(resolve, 100)); - } catch (error) { - // 忽略关闭错误,避免影响退出流程 - console.error('[Logger] 关闭日志系统时出错:', error); - } - } - pinoInstance = null; - pinoInitPromise = null; -} - -/** - * 当前 session ID(由外部设置) - */ let currentSessionId: string | null = null; +let logDirPath: string | null = null; +let logDirInitialized = false; -/** - * 设置当前 session ID(用于日志文件命名) - * 应该在 session 初始化时调用 - */ export function setLoggerSessionId(sessionId: string): void { - // 如果 session ID 变化,重置 Pino 实例以使用新的日志文件 if (currentSessionId !== sessionId) { currentSessionId = sessionId; - resetPinoInstance(); } } -/** - * 获取或创建日志文件路径 - * 每个 session 使用独立的日志文件 - */ -async function ensureLogDirectory(): Promise { +export async function shutdownLogger(): Promise { + // appendFileSync 是同步的,不需要特殊的关闭逻辑 +} + +function getLogDir(): string | null { + if (logDirInitialized) { + return logDirPath; + } + + logDirInitialized = true; + try { const logDir = path.join(os.homedir(), '.blade', 'logs'); - await fs.mkdir(logDir, { recursive: true, mode: 0o755 }); - - // 检查目录权限(检测是否属于 root) - try { - const stats = await fs.stat(logDir); - if (stats.uid === 0 && process.getuid && process.getuid() !== 0) { - // 目录属于 root,但当前不是 root 用户 - console.error(''); - console.error('❌ 权限错误:~/.blade/logs 目录属于 root 用户'); - console.error(''); - console.error('这通常是因为您曾经使用 sudo 运行过 blade。'); - console.error(''); - console.error('解决方法:'); - console.error(' sudo chown -R $USER:$USER ~/.blade/'); - console.error(''); - console.error('然后重新运行 blade(不要使用 sudo)'); - console.error(''); - return null; // 降级:不使用文件日志 - } - } catch (_statError) { - // 忽略 stat 错误,继续尝试创建日志文件 + + if (!existsSync(logDir)) { + mkdirSync(logDir, { recursive: true, mode: 0o755 }); } - // 清理旧日志(保留最近 30 天) - await cleanOldLogs(logDir, 30); + const stats = statSync(logDir); + if (stats.uid === 0 && process.getuid && process.getuid() !== 0) { + console.error(''); + console.error('❌ 权限错误:~/.blade/logs 目录属于 root 用户'); + console.error(''); + console.error('这通常是因为您曾经使用 sudo 运行过 blade。'); + console.error(''); + console.error('解决方法:'); + console.error(' sudo chown -R $USER:$USER ~/.blade/'); + console.error(''); + console.error('然后重新运行 blade(不要使用 sudo)'); + console.error(''); + return null; + } - // 使用 session ID 作为日志文件名 - const sessionId = currentSessionId || 'default'; - const logFileName = `blade-${sessionId}.log`; - return path.join(logDir, logFileName); + cleanOldLogs(logDir, 30); + logDirPath = logDir; + return logDir; } catch (error) { console.error('[Logger] 无法创建日志目录:', error); - return null; // 降级:不使用文件日志 + return null; } } -/** - * 清理旧日志文件 - * @param logDir 日志目录 - * @param maxAgeDays 最大保留天数 - */ -async function cleanOldLogs(logDir: string, maxAgeDays: number): Promise { +function cleanOldLogs(logDir: string, maxAgeDays: number): void { try { - const files = await fs.readdir(logDir); + const files = readdirSync(logDir); const now = Date.now(); const maxAge = maxAgeDays * 24 * 60 * 60 * 1000; for (const file of files) { - // 只清理 blade-*.log 文件 - if (!file.startsWith('blade-') || !file.endsWith('.log')) { + if (!file.startsWith('blade-') || (!file.endsWith('.log') && !file.endsWith('.jsonl'))) { continue; } const filePath = path.join(logDir, file); try { - const stat = await fs.stat(filePath); + const stat = statSync(filePath); if (now - stat.mtimeMs > maxAge) { - await fs.unlink(filePath); - console.error(`[Logger] 已清理旧日志: ${file}`); + unlinkSync(filePath); } } catch (_error) { // 忽略单个文件的错误 } } - } catch (error) { + } catch (_error) { // 清理失败不影响日志功能 - console.error('[Logger] 清理旧日志失败:', error); } } -/** - * 创建 Pino 日志实例(单例) - * 注意:只用于文件日志,终端输出由 Logger.log() 手动控制 - */ -let pinoInstance: PinoLogger | null = null; -let pinoInitPromise: Promise | null = null; - -async function getPinoInstance(): Promise { - // 已有实例直接返回 - if (pinoInstance) { - return pinoInstance; - } - - // 使用 Promise 缓存防止并发初始化 - if (pinoInitPromise) { - return pinoInitPromise; - } - - pinoInitPromise = (async () => { - try { - const logFilePath = await ensureLogDirectory(); - - // 如果日志目录创建失败,降级为只使用终端输出 - if (!logFilePath) { - console.warn('[Logger] 文件日志已禁用(目录创建失败)'); - return null; - } - - // 只配置文件传输(始终记录 JSON 格式日志) - // 终端输出由 Logger.log() 手动控制(应用分类过滤) - pinoInstance = pino({ - level: 'debug', - transport: { - target: 'pino/file', - options: { destination: logFilePath }, - }, - }); - - return pinoInstance; - } catch (error) { - console.error('[Logger] Pino 初始化失败:', error); - return null; // 降级:只使用 console 输出 - } - })(); +function getLogFilePath(): string | null { + const logDir = getLogDir(); + if (!logDir) return null; - return pinoInitPromise; + const sessionId = currentSessionId || 'default'; + return path.join(logDir, `blade-${sessionId}.jsonl`); } -function resetPinoInstance(): void { - // 先关闭旧的 Pino 实例 - if (pinoInstance && typeof pinoInstance.flush === 'function') { - try { - pinoInstance.flush(); - } catch (_error) { - // 忽略关闭错误 - } +function writeLogEntry(category: LogCategory, level: LogLevel, message: string): void { + const filePath = getLogFilePath(); + if (!filePath) return; + + try { + const entry = { + timestamp: new Date().toISOString(), + level: LogLevel[level], + category, + message, + }; + appendFileSync(filePath, JSON.stringify(entry) + '\n'); + } catch (_error) { + // 写入失败时静默处理,避免影响主流程 } - pinoInstance = null; - pinoInitPromise = null; } -/** - * Logger 类 - 统一日志管理 - */ export class Logger { - // 静态全局 debug 配置(优先级最高) - // 支持 boolean 或字符串过滤器(如 "agent,ui" 或 "!chat,!loop") private static globalDebugConfig: string | boolean | null = null; private enabled: boolean; private minLevel: LogLevel; private category: LogCategory; - private pinoLogger: PinoLogger | null = null; constructor(options: LoggerOptions = {}) { - // 优先级:options.enabled > globalDebugConfig > 默认禁用 if (options.enabled !== undefined) { this.enabled = options.enabled; } else if (Logger.globalDebugConfig !== null) { this.enabled = Boolean(Logger.globalDebugConfig); } else { - // 默认禁用,必须通过 Logger.setGlobalDebug() 显式启用 this.enabled = false; } this.minLevel = options.minLevel ?? LogLevel.DEBUG; this.category = options.category ?? LogCategory.GENERAL; - - // 异步初始化 pino - this.initPino(); - } - - /** - * 异步初始化 Pino 实例 - */ - private async initPino(): Promise { - try { - const basePino = await getPinoInstance(); - // 创建 child logger 用于分类(如果 basePino 为 null,则不使用文件日志) - if (basePino) { - this.pinoLogger = basePino.child({ category: this.category }); - } - } catch (error) { - console.error('[Logger] Failed to initialize pino:', error); - } } - /** - * 设置全局 debug 配置(用于 CLI 参数覆盖) - * 调用此方法后,所有 Logger 实例都会使用此配置 - * - * @param config - debug 配置(boolean 或字符串过滤器) - */ public static setGlobalDebug(config: string | boolean): void { Logger.globalDebugConfig = config; - // 重置 pino 实例以应用新配置 - resetPinoInstance(); } - /** - * 清除全局 debug 配置(恢复使用 ConfigManager) - */ public static clearGlobalDebug(): void { Logger.globalDebugConfig = null; - resetPinoInstance(); } - /** - * 动态更新 enabled 状态(用于运行时切换 debug 模式) - */ public setEnabled(enabled: boolean): void { this.enabled = enabled; } - /** - * 解析 debug 过滤器 - * @param debugValue - debug 配置值(true/false/"api,hooks"/"!statsig,!file") - * @returns { enabled: boolean, filter?: { mode: 'include' | 'exclude', categories: string[] } } - */ private parseDebugFilter(debugValue: string | boolean): { enabled: boolean; filter?: { mode: 'include' | 'exclude'; categories: string[] }; } { - // debug 未开启 if (!debugValue) { return { enabled: false }; } - // debug 开启但无过滤(--debug 或 debug: true) if (debugValue === true || debugValue === 'true' || debugValue === '1') { return { enabled: true }; } - // 解析过滤字符串 const filterStr = String(debugValue).trim(); if (!filterStr) { return { enabled: true }; } - // 负向过滤:--debug "!statsig,!file" if (filterStr.startsWith('!')) { const categories = filterStr .split(',') @@ -341,7 +208,6 @@ export class Logger { }; } - // 正向过滤:--debug "api,hooks" const categories = filterStr .split(',') .map((s) => s.trim()) @@ -352,124 +218,71 @@ export class Logger { }; } - /** - * 检查分类是否应该输出日志 - */ - private shouldLogCategory(filter?: { - mode: 'include' | 'exclude'; - categories: string[]; - }): boolean { - // 无过滤器,输出所有分类 + private shouldLogCategory(filter?: { mode: 'include' | 'exclude'; categories: string[] }): boolean { if (!filter) { return true; } const categoryName = this.category.toLowerCase(); - // 正向过滤:只输出指定分类 if (filter.mode === 'include') { return filter.categories.some((cat) => categoryName.includes(cat.toLowerCase())); } - // 负向过滤:排除指定分类 return !filter.categories.some((cat) => categoryName.includes(cat.toLowerCase())); } - /** - * 检查当前是否应该输出日志到终端 - * - * 注意:文件日志始终记录,此方法仅影响终端输出 - */ private shouldLogToConsole(level: LogLevel): boolean { - // 检查全局 debug 配置(由 AppWrapper 在初始化时设置) if (Logger.globalDebugConfig !== null) { - // 解析 debug 配置和过滤器 const { enabled, filter } = this.parseDebugFilter(Logger.globalDebugConfig); - // debug 未启用 if (!enabled) { return false; } - // 检查日志级别 if (level < this.minLevel) { return false; } - // 检查分类过滤 return this.shouldLogCategory(filter); } - // 如果全局配置未设置,回退到实例级别的 enabled return this.enabled && level >= this.minLevel; } - /** - * 内部日志输出方法(双路输出) - * - 文件:始终通过 Pino 写入 - * - 终端:应用 shouldLogToConsole 过滤(支持分类过滤) - */ private log(level: LogLevel, ...args: unknown[]): void { const message = args .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : String(arg))) .join(' '); - // 1. 始终写入文件(通过 Pino) - if (this.pinoLogger) { - const pinoLevel = PINO_LEVELS[level]; - this.pinoLogger[pinoLevel as 'debug' | 'info' | 'warn' | 'error'](message); - } + writeLogEntry(this.category, level, message); - // 2. 根据过滤规则决定是否输出到终端 if (this.shouldLogToConsole(level)) { const levelName = LogLevel[level]; const prefix = `[${this.category}] [${levelName}]`; - - // 使用 console.error 确保输出到 stderr(不被 Ink patchConsole 拦截) console.error(prefix, ...args); } } - /** - * Debug 级别日志(最详细) - */ public debug(...args: unknown[]): void { this.log(LogLevel.DEBUG, ...args); } - /** - * Info 级别日志(一般信息) - */ public info(...args: unknown[]): void { this.log(LogLevel.INFO, ...args); } - /** - * Warn 级别日志(警告) - */ public warn(...args: unknown[]): void { this.log(LogLevel.WARN, ...args); } - /** - * Error 级别日志(错误) - */ public error(...args: unknown[]): void { this.log(LogLevel.ERROR, ...args); } } -/** - * 创建分类 Logger 的工厂函数 - */ -export function createLogger( - category: LogCategory, - options?: Omit -): Logger { +export function createLogger(category: LogCategory, options?: Omit): Logger { return new Logger({ ...options, category }); } -/** - * 默认 Logger 实例(用于快速调试) - */ export const logger = new Logger({ category: LogCategory.GENERAL }); diff --git a/src/prompts/builder.ts b/src/prompts/builder.ts index fe276e06..bbe234a7 100644 --- a/src/prompts/builder.ts +++ b/src/prompts/builder.ts @@ -63,6 +63,11 @@ export interface BuildSystemPromptOptions { * Spec 模式专用:Steering 上下文 */ steeringContext?: string | null; + + /** + * AI 回复语言(如 'zh-CN', 'en-US') + */ + language?: string; } /** @@ -113,6 +118,7 @@ export async function buildSystemPrompt( includeEnvironment = true, currentSpec, steeringContext, + language, } = options; const parts: string[] = []; @@ -177,6 +183,9 @@ export async function buildSystemPrompt( // 注入 Skills 元数据到 占位符 prompt = injectSkillsToPrompt(prompt); + // 注入语言指令 + prompt = injectLanguageInstruction(prompt, language); + return { prompt, sources }; } @@ -199,6 +208,29 @@ function injectSkillsToPrompt(prompt: string): string { ); } +const LANGUAGE_NAMES: Record = { + 'zh-CN': 'Chinese (Simplified Chinese)', + 'zh-TW': 'Chinese (Traditional Chinese)', + 'en-US': 'English', + 'en-GB': 'English (British)', + 'ja-JP': 'Japanese', + 'ko-KR': 'Korean', + 'es-ES': 'Spanish', + 'fr-FR': 'French', + 'de-DE': 'German', + 'pt-BR': 'Portuguese (Brazilian)', + 'ru-RU': 'Russian', +}; + +function injectLanguageInstruction(prompt: string, language?: string): string { + const lang = language || 'zh-CN'; + const langName = LANGUAGE_NAMES[lang] || lang; + + const instruction = `IMPORTANT: Always respond in ${langName}. All your responses must be in ${langName}.`; + + return prompt.replace('{{LANGUAGE_INSTRUCTION}}', instruction); +} + /** * 加载项目 BLADE.md 配置 */ diff --git a/src/prompts/default.ts b/src/prompts/default.ts index c317b662..ef89bbe4 100644 --- a/src/prompts/default.ts +++ b/src/prompts/default.ts @@ -160,7 +160,7 @@ assistant: Clients are marked as failed in the \`connectToServer\` function in s # Language Requirement -IMPORTANT: Always respond in Chinese (Simplified Chinese). Translate all your responses to Chinese before sending them to the user.`; +{{LANGUAGE_INSTRUCTION}}`; diff --git a/src/server/bus.ts b/src/server/bus.ts new file mode 100644 index 00000000..f1ad79f8 --- /dev/null +++ b/src/server/bus.ts @@ -0,0 +1,28 @@ +import { EventEmitter } from 'events'; + +class GlobalBus extends EventEmitter { + private static instance: GlobalBus; + + private constructor() { + super(); + this.setMaxListeners(100); + } + + static getInstance(): GlobalBus { + if (!GlobalBus.instance) { + GlobalBus.instance = new GlobalBus(); + } + return GlobalBus.instance; + } + + publish(sessionId: string, type: string, properties: Record) { + this.emit('event', { sessionId, type, properties }); + } + + subscribe(callback: (event: { sessionId: string; type: string; properties: Record }) => void) { + this.on('event', callback); + return () => this.off('event', callback); + } +} + +export const Bus = GlobalBus.getInstance(); diff --git a/src/server/error.ts b/src/server/error.ts new file mode 100644 index 00000000..302337a8 --- /dev/null +++ b/src/server/error.ts @@ -0,0 +1,52 @@ +import { z } from 'zod'; + +export class BladeServerError extends Error { + constructor( + public readonly code: string, + message: string, + public readonly statusCode: number = 500 + ) { + super(message); + this.name = 'BladeServerError'; + } + + toObject() { + return { + error: { + code: this.code, + message: this.message, + }, + }; + } +} + +export class NotFoundError extends BladeServerError { + constructor(resource: string, id?: string) { + super( + 'NOT_FOUND', + id ? `${resource} not found: ${id}` : `${resource} not found`, + 404 + ); + } +} + +export class BadRequestError extends BladeServerError { + constructor(message: string) { + super('BAD_REQUEST', message, 400); + } +} + +export class UnauthorizedError extends BladeServerError { + constructor(message = 'Unauthorized') { + super('UNAUTHORIZED', message, 401); + } +} + +export const ErrorResponse = z.object({ + error: z.object({ + code: z.string(), + message: z.string(), + }), +}); + +export type ErrorResponse = z.infer; diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 00000000..6f206223 --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,3 @@ +export * from './error.js'; +export { BladeServer, getNetworkIPs, type ServerOptions } from './server.js'; + diff --git a/src/server/routes/config.ts b/src/server/routes/config.ts new file mode 100644 index 00000000..91be6dad --- /dev/null +++ b/src/server/routes/config.ts @@ -0,0 +1,60 @@ +import { Hono } from 'hono'; +import { z } from 'zod'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { BadRequestError } from '../error.js'; +import { getConfig, configActions } from '../../store/vanilla.js'; + +const logger = createLogger(LogCategory.SERVICE); + +const UpdateConfigSchema = z.object({ + updates: z.record(z.any()), + options: z.object({ + scope: z.enum(['local', 'project', 'global']).optional(), + immediate: z.boolean().optional(), + }).optional(), +}); + +export const ConfigRoutes = () => { + const app = new Hono(); + + app.get('/', async (c) => { + try { + const config = getConfig(); + return c.json(config || {}); + } catch (error) { + logger.error('[ConfigRoutes] Failed to get config:', error); + return c.json({}); + } + }); + + app.put('/', async (c) => { + try { + const body = await c.req.json(); + const parsed = UpdateConfigSchema.safeParse(body); + + if (!parsed.success) { + throw new BadRequestError('Invalid config update format'); + } + + const { updates, options } = parsed.data; + await configActions().updateConfig(updates, options); + + return c.json({ success: true, updates }); + } catch (error) { + logger.error('[ConfigRoutes] Failed to update config:', error); + throw error; + } + }); + + app.get('/permissions', async (c) => { + try { + const config = getConfig(); + return c.json(config?.permissions || {}); + } catch (error) { + logger.error('[ConfigRoutes] Failed to get permissions:', error); + return c.json({}); + } + }); + + return app; +}; diff --git a/src/server/routes/global.ts b/src/server/routes/global.ts new file mode 100644 index 00000000..57bb0640 --- /dev/null +++ b/src/server/routes/global.ts @@ -0,0 +1,29 @@ +import { Hono } from 'hono'; +import { getVersion } from '../../utils/packageInfo.js'; + +type Variables = { + directory: string; +}; + +export const GlobalRoutes = (): Hono<{ Variables: Variables }> => { + const app = new Hono<{ Variables: Variables }>(); + + app.get('/health', (c) => { + return c.json({ + healthy: true, + version: getVersion(), + }); + }); + + app.get('/info', (c) => { + return c.json({ + version: getVersion(), + platform: process.platform, + arch: process.arch, + nodeVersion: process.version, + cwd: process.cwd(), + }); + }); + + return app; +}; diff --git a/src/server/routes/mcp.ts b/src/server/routes/mcp.ts new file mode 100644 index 00000000..76b97eef --- /dev/null +++ b/src/server/routes/mcp.ts @@ -0,0 +1,115 @@ +import { Hono } from 'hono'; +import type { McpServerConfig } from '../../config/types.js'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { McpRegistry } from '../../mcp/McpRegistry.js'; +import { McpConnectionStatus } from '../../mcp/types.js'; +import { configActions, getConfig } from '../../store/vanilla.js'; + +const logger = createLogger(LogCategory.SERVICE); + +export const McpRoutes = () => { + const app = new Hono(); + + app.get('/', async (c) => { + try { + const registry = McpRegistry.getInstance(); + const serversMap = registry.getAllServers(); + + const result = Array.from(serversMap.entries()).map(([name, info]) => ({ + id: name, + name, + status: info.status === McpConnectionStatus.CONNECTED + ? 'connected' + : info.status === McpConnectionStatus.CONNECTING + ? 'connecting' + : info.lastError + ? 'error' + : 'offline', + endpoint: info.config.command + ? `${info.config.command} ${(info.config.args || []).join(' ')}`.trim() + : info.config.url || 'Unknown', + description: `MCP server: ${name}`, + tools: info.tools.map(t => t.name), + connectedAt: info.connectedAt?.toISOString(), + error: info.lastError?.message, + })); + + return c.json(result); + } catch (error) { + logger.error('[McpRoutes] Failed to get MCP servers:', error); + return c.json([]); + } + }); + + app.post('/:name/connect', async (c) => { + try { + const name = c.req.param('name'); + const registry = McpRegistry.getInstance(); + await registry.connectServer(name); + return c.json({ success: true }); + } catch (error) { + logger.error('[McpRoutes] Failed to connect MCP server:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.post('/:name/disconnect', async (c) => { + try { + const name = c.req.param('name'); + const registry = McpRegistry.getInstance(); + await registry.disconnectServer(name); + return c.json({ success: true }); + } catch (error) { + logger.error('[McpRoutes] Failed to disconnect MCP server:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.delete('/:name', async (c) => { + try { + const name = c.req.param('name'); + const registry = McpRegistry.getInstance(); + await registry.unregisterServer(name); + + const config = getConfig(); + if (config?.mcpServers) { + const { [name]: _, ...rest } = config.mcpServers; + await configActions().updateConfig({ mcpServers: rest }); + } + + return c.json({ success: true }); + } catch (error) { + logger.error('[McpRoutes] Failed to delete MCP server:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.post('/', async (c) => { + try { + const body = await c.req.json() as { name: string; config: McpServerConfig }; + const { name, config: serverConfig } = body; + + if (!name || !serverConfig) { + return c.json({ success: false, error: 'Missing name or config' }, 400); + } + + const registry = McpRegistry.getInstance(); + await registry.registerServer(name, serverConfig); + + const currentConfig = getConfig(); + await configActions().updateConfig({ + mcpServers: { + ...currentConfig?.mcpServers, + [name]: serverConfig, + }, + }); + + return c.json({ success: true }); + } catch (error) { + logger.error('[McpRoutes] Failed to add MCP server:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + return app; +}; diff --git a/src/server/routes/models.ts b/src/server/routes/models.ts new file mode 100644 index 00000000..f13918fa --- /dev/null +++ b/src/server/routes/models.ts @@ -0,0 +1,84 @@ +import { Hono } from 'hono'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { + configActions, + getAllModels, + getCurrentModel, +} from '../../store/vanilla.js'; +import { BadRequestError } from '../error.js'; + +const logger = createLogger(LogCategory.SERVICE); + +export const ModelsRoutes = () => { + const app = new Hono(); + + app.get('/', async (c) => { + try { + const models = getAllModels(); + const current = getCurrentModel(); + + return c.json({ + configured: models, + current, + }); + } catch (error) { + logger.error('[ModelsRoutes] Failed to get models:', error); + return c.json({ configured: [], current: null }); + } + }); + + app.post('/', async (c) => { + try { + const body = await c.req.json(); + const { provider, name, model, baseUrl, apiKey } = body; + + if (!model) { + throw new BadRequestError('model is required'); + } + + const modelConfig = await configActions().addModel({ + provider: provider || 'openai-compatible', + name: name || model, + model, + baseUrl: baseUrl || undefined, + apiKey: apiKey || undefined, + }); + + return c.json({ success: true, model: modelConfig }); + } catch (error) { + logger.error('[ModelsRoutes] Failed to add model:', error); + throw error; + } + }); + + app.put('/:modelId', async (c) => { + try { + const modelId = c.req.param('modelId'); + const body = await c.req.json(); + const { baseUrl, apiKey, model } = body; + + await configActions().updateModel(modelId, { + baseUrl: baseUrl || undefined, + apiKey: apiKey || undefined, + model: model || undefined, + }); + return c.json({ success: true }); + } catch (error) { + logger.error('[ModelsRoutes] Failed to update model:', error); + throw error; + } + }); + + app.delete('/:modelId', async (c) => { + try { + const modelId = c.req.param('modelId'); + await configActions().removeModel(modelId); + return c.json({ success: true }); + } catch (error) { + logger.error('[ModelsRoutes] Failed to delete model:', error); + throw error; + } + }); + + return app; +}; diff --git a/src/server/routes/permission.ts b/src/server/routes/permission.ts new file mode 100644 index 00000000..f298b3f3 --- /dev/null +++ b/src/server/routes/permission.ts @@ -0,0 +1,68 @@ +import { Hono } from 'hono'; +import { z } from 'zod'; +import { PermissionMode } from '../../config/types.js'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import type { ConfirmationResponse } from '../../tools/types/ExecutionTypes.js'; +import { BadRequestError, NotFoundError } from '../error.js'; +import { respondToPermission } from './session.js'; + +const logger = createLogger(LogCategory.SERVICE); + +const PermissionResponseSchema = z.object({ + approved: z.boolean(), + remember: z.boolean().optional(), + scope: z.enum(['once', 'session']).optional(), + targetMode: z.enum(['default', 'autoEdit', 'plan', 'spec', 'yolo']).optional(), + feedback: z.string().optional(), + answers: z.record(z.union([z.string(), z.array(z.string())])).optional(), +}); + +export const PermissionRoutes = () => { + const app = new Hono(); + + app.post('/:permissionId', async (c) => { + const permissionId = c.req.param('permissionId'); + const sessionId = c.req.query('sessionId'); + + logger.info(`[PermissionRoutes] Received permission response: permissionId=${permissionId}, sessionId=${sessionId}`); + + try { + const body = await c.req.json(); + const parsed = PermissionResponseSchema.safeParse(body); + + if (!parsed.success) { + throw new BadRequestError('Invalid permission response format'); + } + + const { approved, remember, scope, targetMode, feedback, answers } = parsed.data; + + if (!sessionId) { + throw new BadRequestError('sessionId query parameter is required'); + } + + const response: ConfirmationResponse = { + approved, + reason: feedback, + scope, + targetMode: targetMode as PermissionMode | undefined, + feedback, + answers, + }; + + const success = respondToPermission(sessionId, permissionId, response); + + if (!success) { + throw new NotFoundError('Permission request', permissionId); + } + + logger.info(`[PermissionRoutes] Permission ${permissionId} ${approved ? 'approved' : 'denied'}`); + + return c.json({ success: true, approved, remember }); + } catch (error) { + logger.error('[PermissionRoutes] Failed to respond to permission:', error); + throw error; + } + }); + + return app; +}; diff --git a/src/server/routes/provider.ts b/src/server/routes/provider.ts new file mode 100644 index 00000000..34edbd3f --- /dev/null +++ b/src/server/routes/provider.ts @@ -0,0 +1,45 @@ +import { Hono } from 'hono'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { getModelsForProvider, getProviders } from '../../services/ModelsDevService.js'; +import { OAUTH_PROVIDERS } from '../../ui/components/model-config/types.js'; + +const logger = createLogger(LogCategory.SERVICE); + +export const ProviderRoutes = () => { + const app = new Hono(); + + app.get('/', async (c) => { + try { + const apiProviders = await getProviders(); + + const oauthProviders = Object.entries(OAUTH_PROVIDERS).map(([id, config]) => ({ + id, + name: id.charAt(0).toUpperCase() + id.slice(1), + icon: config.icon, + description: config.description, + isOAuth: true, + envVars: [], + bladeProvider: config.bladeProvider, + })); + + return c.json([...apiProviders, ...oauthProviders]); + } catch (error) { + logger.error('[ProviderRoutes] Failed to list providers:', error); + return c.json([]); + } + }); + + app.get('/:providerId/models', async (c) => { + const providerId = c.req.param('providerId'); + + try { + const models = await getModelsForProvider(providerId); + return c.json(models); + } catch (error) { + logger.error('[ProviderRoutes] Failed to list models:', error); + return c.json([]); + } + }); + + return app; +}; diff --git a/src/server/routes/session.ts b/src/server/routes/session.ts new file mode 100644 index 00000000..0f9b6796 --- /dev/null +++ b/src/server/routes/session.ts @@ -0,0 +1,537 @@ +import { Hono } from 'hono'; +import { streamSSE } from 'hono/streaming'; +import { LRUCache } from 'lru-cache'; +import { nanoid } from 'nanoid'; +import { z } from 'zod'; +import { Agent } from '../../agent/Agent.js'; +import type { ChatContext, LoopOptions } from '../../agent/types.js'; +import { PermissionMode } from '../../config/types.js'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import type { Message } from '../../services/ChatServiceInterface.js'; +import { SessionService } from '../../services/SessionService.js'; +import type { ConfirmationDetails, ConfirmationResponse } from '../../tools/types/ExecutionTypes.js'; +import type { ToolResultMetadata } from '../../tools/types/ToolTypes.js'; +import { Bus } from '../bus.js'; +import { BadRequestError, NotFoundError } from '../error.js'; + +const logger = createLogger(LogCategory.SERVICE); + +const CreateSessionSchema = z.object({ + title: z.string().optional(), + projectPath: z.string().optional(), +}); + +const SendMessageSchema = z.object({ + content: z.string(), + attachments: z.array(z.object({ + type: z.enum(['file', 'image', 'url']), + path: z.string().optional(), + url: z.string().optional(), + content: z.string().optional(), + })).optional(), + permissionMode: z.enum(['default', 'autoEdit', 'plan', 'spec', 'yolo']).optional(), +}); + +const UpdateSessionSchema = z.object({ + title: z.string().optional(), +}); + +export interface RunState { + id: string; + sessionId: string; + status: 'running' | 'waiting_permission' | 'completed' | 'failed' | 'cancelled'; + abortController: AbortController; + pendingPermission?: { + permissionId: string; + resolve: (response: ConfirmationResponse) => void; + details: ConfirmationDetails; + }; + createdAt: Date; +} + +interface SessionInfo { + id: string; + projectPath: string; + title: string; + createdAt: Date; + messages: Message[]; + currentRunId?: string; +} + +const sessions = new Map(); + +const activeRuns = new LRUCache({ + max: 100, + ttl: 30 * 60 * 1000, + dispose: (run: RunState, runId: string) => { + if (run.status === 'running' || run.status === 'waiting_permission') { + run.abortController.abort(); + logger.debug(`[SessionRoutes] Run ${runId} disposed due to cache eviction`); + } + }, +}); + +type Variables = { + directory: string; +}; + +const sanitizeToolMetadata = (metadata: ToolResultMetadata | undefined) => { + if (!metadata || typeof metadata !== 'object') return metadata; + const sanitized = { ...(metadata as Record) }; + const MAX_INLINE_CONTENT = 200000; + if (typeof sanitized.oldContent === 'string' && sanitized.oldContent.length > MAX_INLINE_CONTENT) { + delete sanitized.oldContent; + } + if (typeof sanitized.newContent === 'string' && sanitized.newContent.length > MAX_INLINE_CONTENT) { + delete sanitized.newContent; + } + return sanitized as ToolResultMetadata; +}; + +export const SessionRoutes = () => { + const app = new Hono<{ Variables: Variables }>(); + + app.get('/', async (c) => { + try { + const persistedSessions = await SessionService.listSessions(); + + const activeSessionsList = Array.from(sessions.values()).map((s) => ({ + sessionId: s.id, + projectPath: s.projectPath, + title: s.title, + messageCount: s.messages.length, + firstMessageTime: s.createdAt.toISOString(), + lastMessageTime: new Date().toISOString(), + hasErrors: false, + isActive: true, + })); + + const allSessions = [ + ...activeSessionsList, + ...persistedSessions.filter((s) => !sessions.has(s.sessionId)), + ]; + + return c.json(allSessions); + } catch (error) { + logger.error('[SessionRoutes] Failed to list sessions:', error); + return c.json([]); + } + }); + + app.post('/', async (c) => { + try { + const body = await c.req.json(); + const parsed = CreateSessionSchema.safeParse(body); + + if (!parsed.success) { + throw new BadRequestError('Invalid request body'); + } + + const { title, projectPath } = parsed.data; + const sessionId = nanoid(12); + const directory = projectPath || c.get('directory') || process.cwd(); + + const session: SessionInfo = { + id: sessionId, + projectPath: directory, + title: title || `Session ${sessionId.slice(0, 6)}`, + createdAt: new Date(), + messages: [], + }; + + sessions.set(sessionId, session); + + return c.json({ + sessionId, + projectPath: directory, + title: session.title, + messageCount: 0, + firstMessageTime: session.createdAt.toISOString(), + lastMessageTime: session.createdAt.toISOString(), + hasErrors: false, + }); + } catch (error) { + logger.error('[SessionRoutes] Failed to create session:', error); + throw error; + } + }); + + app.get('/:sessionId', async (c) => { + const sessionId = c.req.param('sessionId'); + + const session = sessions.get(sessionId); + if (session) { + return c.json({ + sessionId: session.id, + projectPath: session.projectPath, + title: session.title, + messageCount: session.messages.length, + firstMessageTime: session.createdAt.toISOString(), + lastMessageTime: new Date().toISOString(), + hasErrors: false, + isActive: true, + }); + } + + try { + const persistedSessions = await SessionService.listSessions(); + const persistedSession = persistedSessions.find((s) => s.sessionId === sessionId); + + if (!persistedSession) { + throw new NotFoundError('Session', sessionId); + } + + return c.json(persistedSession); + } catch (error) { + if (error instanceof NotFoundError) throw error; + logger.error('[SessionRoutes] Failed to get session:', error); + throw error; + } + }); + + app.patch('/:sessionId', async (c) => { + const sessionId = c.req.param('sessionId'); + + try { + const body = await c.req.json(); + const parsed = UpdateSessionSchema.safeParse(body); + + if (!parsed.success) { + throw new BadRequestError('Invalid request body'); + } + + const { title } = parsed.data; + const session = sessions.get(sessionId); + + if (session && title) { + session.title = title; + } + + return c.json({ success: true, title }); + } catch (error) { + logger.error('[SessionRoutes] Failed to update session:', error); + throw error; + } + }); + + app.delete('/:sessionId', async (c) => { + const sessionId = c.req.param('sessionId'); + + const session = sessions.get(sessionId); + if (session?.currentRunId) { + const run = activeRuns.get(session.currentRunId); + if (run) { + run.abortController.abort(); + Bus.publish(sessionId, 'run.cancelled', { runId: run.id }); + activeRuns.delete(session.currentRunId); + } + } + sessions.delete(sessionId); + + return c.json({ success: true }); + }); + + app.get('/:sessionId/message', async (c) => { + const sessionId = c.req.param('sessionId'); + + try { + const messages = await SessionService.loadSession(sessionId); + return c.json(messages); + } catch (error) { + logger.error('[SessionRoutes] Failed to get messages:', error); + return c.json([]); + } + }); + + app.get('/:sessionId/events', async (c) => { + const sessionId = c.req.param('sessionId'); + + let session = sessions.get(sessionId); + if (!session) { + const directory = c.get('directory') || process.cwd(); + session = { + id: sessionId, + projectPath: directory, + title: `Session ${sessionId.slice(0, 6)}`, + createdAt: new Date(), + messages: [], + }; + sessions.set(sessionId, session); + } + + return streamSSE(c, async (stream) => { + const HEARTBEAT_INTERVAL = 15000; + + await stream.writeSSE({ + data: JSON.stringify({ type: 'connected', properties: { sessionId, timestamp: Date.now() } }) + }); + + const unsubscribe = Bus.subscribe((event) => { + if (event.sessionId !== sessionId) return; + stream.writeSSE({ + data: JSON.stringify({ type: event.type, properties: { sessionId: event.sessionId, ...event.properties } }) + }).catch(() => { /* ignore write errors on closed streams */ }); + }); + + const heartbeatInterval = setInterval(() => { + if (!stream.aborted) { + stream.writeSSE({ + data: JSON.stringify({ type: 'heartbeat', properties: { timestamp: Date.now() } }) + }).catch(() => { /* ignore write errors on closed streams */ }); + } + }, HEARTBEAT_INTERVAL); + + stream.onAbort(() => { + clearInterval(heartbeatInterval); + unsubscribe(); + }); + + while (true) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + if (stream.aborted) break; + } + }); + }); + + app.post('/:sessionId/message', async (c) => { + const sessionId = c.req.param('sessionId'); + + const body = await c.req.json(); + const parsed = SendMessageSchema.safeParse(body); + + if (!parsed.success) { + throw new BadRequestError('Invalid message format'); + } + + const { content, permissionMode: requestedMode } = parsed.data; + const permissionMode = (requestedMode as PermissionMode) || PermissionMode.DEFAULT; + const directory = c.get('directory') || process.cwd(); + + let session = sessions.get(sessionId); + if (!session) { + session = { + id: sessionId, + projectPath: directory, + title: `Session ${sessionId.slice(0, 6)}`, + createdAt: new Date(), + messages: [], + }; + sessions.set(sessionId, session); + } + + const runId = nanoid(12); + const abortController = new AbortController(); + + const run: RunState = { + id: runId, + sessionId, + status: 'running', + abortController, + createdAt: new Date(), + }; + + activeRuns.set(runId, run); + session.currentRunId = runId; + + executeRunAsync(run, session, content, permissionMode).catch((error) => { + logger.error(`[SessionRoutes] Run ${runId} failed:`, error); + }); + + return c.json({ runId, status: 'running' }, 202); + }); + + app.post('/:sessionId/abort', async (c) => { + const sessionId = c.req.param('sessionId'); + + const session = sessions.get(sessionId); + if (session?.currentRunId) { + const run = activeRuns.get(session.currentRunId); + if (run) { + run.abortController.abort(); + run.status = 'cancelled'; + Bus.publish(sessionId, 'run.cancelled', { runId: run.id }); + } + } + + return c.json({ success: true }); + }); + + app.get('/:sessionId/status', async (c) => { + const sessionId = c.req.param('sessionId'); + + const session = sessions.get(sessionId); + if (!session?.currentRunId) { + return c.json({ sessionId, status: 'idle' }); + } + + const run = activeRuns.get(session.currentRunId); + return c.json({ + sessionId, + runId: session.currentRunId, + status: run?.status || 'idle', + }); + }); + + return app; +}; + +async function executeRunAsync( + run: RunState, + session: SessionInfo, + content: string, + permissionMode: PermissionMode +): Promise { + const { abortController, sessionId, id: runId } = run; + const userMessageId = nanoid(12); + const assistantMessageId = nanoid(12); + + const emit = (type: string, properties: Record) => { + Bus.publish(sessionId, type, properties); + }; + + try { + emit('message.created', { messageId: userMessageId, role: 'user', content }); + emit('session.status', { status: 'running' }); + emit('message.created', { messageId: assistantMessageId, role: 'assistant', content: '' }); + + const agent = await Agent.create({}); + + const requestConfirmation = async (details: ConfirmationDetails): Promise => { + const permissionId = nanoid(12); + const PERMISSION_TIMEOUT = 5 * 60 * 1000; + + run.status = 'waiting_permission'; + + const resultPromise = new Promise((resolve) => { + const timeout = setTimeout(() => { + logger.warn(`[SessionRoutes] Permission ${permissionId} timed out after ${PERMISSION_TIMEOUT}ms`); + emit('permission.timeout', { requestId: permissionId }); + resolve({ approved: false, reason: 'timeout' }); + }, PERMISSION_TIMEOUT); + + run.pendingPermission = { + permissionId, + resolve: (response) => { + clearTimeout(timeout); + resolve(response); + }, + details + }; + }); + + emit('permission.asked', { + requestId: permissionId, + toolName: details.toolName, + description: details.message, + args: details.args, + details, + }); + + logger.info(`[SessionRoutes] Permission request created: ${permissionId}, runId: ${runId}`); + + const response = await resultPromise; + logger.info(`[SessionRoutes] Permission response received: ${permissionId}, approved: ${response.approved}`); + run.status = 'running'; + run.pendingPermission = undefined; + + return response; + }; + + const chatContext: ChatContext = { + messages: session.messages, + userId: 'web-user', + sessionId, + workspaceRoot: session.projectPath, + signal: abortController.signal, + permissionMode, + confirmationHandler: { requestConfirmation }, + }; + + const loopOptions: LoopOptions = { + stream: true, + onContentDelta: async (delta: string) => { + emit('message.delta', { messageId: assistantMessageId, delta }); + }, + onThinkingDelta: async (delta: string) => { + emit('thinking.delta', { delta }); + }, + onStreamEnd: async () => { + emit('message.complete', { messageId: assistantMessageId }); + emit('thinking.completed', {}); + }, + onToolStart: async (toolCall, toolKind) => { + if (toolCall.type !== 'function') return; + emit('tool.start', { + toolName: toolCall.function.name, + toolCallId: toolCall.id, + arguments: toolCall.function.arguments, + toolKind, + }); + }, + onToolResult: async (toolCall, result) => { + if (toolCall.type !== 'function') return; + emit('tool.result', { + toolName: toolCall.function.name, + toolCallId: toolCall.id, + success: !result.error, + summary: result.metadata?.summary, + output: result.displayContent, + metadata: sanitizeToolMetadata(result.metadata), + }); + }, + onTokenUsage: async (usage) => { + emit('token.usage', usage); + }, + onTodoUpdate: async (todos) => { + emit('todo.updated', { todos }); + }, + }; + + const response = await agent.chat(content, chatContext, loopOptions); + + session.messages.push( + { role: 'user', content }, + { role: 'assistant', content: response } + ); + + run.status = 'completed'; + emit('session.completed', { runId }); + emit('session.status', { status: 'idle' }); + + } catch (error) { + logger.error('[SessionRoutes] Agent execution error:', error); + run.status = 'failed'; + emit('session.error', { error: error instanceof Error ? error.message : 'Unknown error' }); + emit('session.status', { status: 'error' }); + } +} + +export function respondToPermission( + sessionId: string, + permissionId: string, + response: ConfirmationResponse +): boolean { + logger.info(`[SessionRoutes] Looking for permission ${permissionId} in session ${sessionId}`); + logger.info(`[SessionRoutes] Active runs: ${activeRuns.size}`); + + for (const [runId, run] of activeRuns.entries()) { + logger.info(`[SessionRoutes] Checking run ${runId}: sessionId=${run.sessionId}, pendingPermission=${run.pendingPermission?.permissionId}`); + if (run.sessionId === sessionId && run.pendingPermission?.permissionId === permissionId) { + run.pendingPermission.resolve(response); + logger.info(`[SessionRoutes] Permission ${permissionId} responded, runId: ${run.id}`); + return true; + } + } + + logger.error(`[SessionRoutes] Permission not found: ${permissionId}`); + return false; +} + +export function cancelPendingPermissions(sessionId: string): void { + for (const run of activeRuns.values()) { + if (run.sessionId === sessionId && run.pendingPermission) { + run.pendingPermission.resolve({ approved: false }); + run.pendingPermission = undefined; + } + } +} diff --git a/src/server/routes/skills.ts b/src/server/routes/skills.ts new file mode 100644 index 00000000..b9fe6e15 --- /dev/null +++ b/src/server/routes/skills.ts @@ -0,0 +1,230 @@ +import { Hono } from 'hono'; +import * as fs from 'node:fs/promises'; +import { homedir } from 'node:os'; +import * as path from 'node:path'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { getSkillRegistry } from '../../skills/index.js'; +import { getSkillInstaller } from '../../skills/SkillInstaller.js'; + +const logger = createLogger(LogCategory.SERVICE); + +const SKILLS_CONFIG_PATH = path.join(homedir(), '.blade', 'skills-config.json'); + +interface SkillsConfig { + disabled: string[]; +} + +async function loadSkillsConfig(): Promise { + try { + const content = await fs.readFile(SKILLS_CONFIG_PATH, 'utf-8'); + return JSON.parse(content); + } catch { + return { disabled: [] }; + } +} + +async function saveSkillsConfig(config: SkillsConfig): Promise { + await fs.mkdir(path.dirname(SKILLS_CONFIG_PATH), { recursive: true }); + await fs.writeFile(SKILLS_CONFIG_PATH, JSON.stringify(config, null, 2)); +} + +async function setSkillEnabled(name: string, enabled: boolean): Promise { + const config = await loadSkillsConfig(); + if (enabled) { + config.disabled = config.disabled.filter(n => n !== name); + } else { + if (!config.disabled.includes(name)) { + config.disabled.push(name); + } + } + await saveSkillsConfig(config); +} + +interface CatalogSkill { + name: string; + description: string; + tag: 'Official' | 'Community'; + author: string; +} + +interface GitHubContent { + name: string; + type: 'file' | 'dir'; + path: string; +} + +let catalogCache: { data: CatalogSkill[]; timestamp: number } | null = null; +const CACHE_TTL = 5 * 60 * 1000; // 5 minutes + +async function fetchOfficialSkillsCatalog(): Promise { + if (catalogCache && Date.now() - catalogCache.timestamp < CACHE_TTL) { + return catalogCache.data; + } + + try { + const response = await fetch( + 'https://api.github.com/repos/anthropics/skills/contents/skills', + { + headers: { + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'Blade-Skills-Catalog', + }, + } + ); + + if (!response.ok) { + logger.warn(`GitHub API returned ${response.status}`); + return catalogCache?.data || []; + } + + const contents: GitHubContent[] = await response.json(); + const skills: CatalogSkill[] = []; + + for (const item of contents) { + if (item.type !== 'dir') continue; + + let description = `Official ${item.name} skill`; + try { + const skillMdResponse = await fetch( + `https://raw.githubusercontent.com/anthropics/skills/main/skills/${item.name}/SKILL.md`, + { headers: { 'User-Agent': 'Blade-Skills-Catalog' } } + ); + if (skillMdResponse.ok) { + const content = await skillMdResponse.text(); + const descMatch = content.match(/^#[^\n]*\n+([^\n#]+)/); + if (descMatch) { + description = descMatch[1].trim().slice(0, 100); + } + } + } catch { + // ignore, use default description + } + + skills.push({ + name: item.name, + description, + tag: 'Official', + author: 'Anthropic', + }); + } + + catalogCache = { data: skills, timestamp: Date.now() }; + return skills; + } catch (error) { + logger.warn('Failed to fetch skills catalog from GitHub:', error); + return catalogCache?.data || []; + } +} + +export const SkillsRoutes = () => { + const app = new Hono(); + + app.get('/', async (c) => { + try { + const registry = getSkillRegistry(); + await registry.initialize(); + const skills = registry.getAll(); + const config = await loadSkillsConfig(); + + const result = skills.map(skill => ({ + id: skill.name, + name: skill.name, + enabled: !config.disabled.includes(skill.name), + description: skill.description || '', + version: skill.version || '1.0.0', + provider: 'Local', + location: skill.source === 'builtin' ? 'Built-in' : skill.basePath, + capabilities: ['prompts'], + allowedTools: skill.allowedTools || [], + })); + + return c.json(result); + } catch (error) { + logger.error('[SkillsRoutes] Failed to get skills:', error); + return c.json([]); + } + }); + + app.post('/:name/toggle', async (c) => { + try { + const name = c.req.param('name'); + const body = await c.req.json() as { enabled: boolean }; + + await setSkillEnabled(name, body.enabled); + + return c.json({ success: true, enabled: body.enabled }); + } catch (error) { + logger.error('[SkillsRoutes] Failed to toggle skill:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.delete('/:name', async (c) => { + try { + const name = c.req.param('name'); + const skillPath = `${process.env.HOME}/.blade/skills/${name}`; + + await fs.rm(skillPath, { recursive: true, force: true }); + + const registry = getSkillRegistry(); + await registry.refresh(); + + return c.json({ success: true }); + } catch (error) { + logger.error('[SkillsRoutes] Failed to uninstall skill:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.post('/install', async (c) => { + try { + const body = await c.req.json() as { + source: 'catalog' | 'repo' | 'local'; + url?: string; + path?: string; + name?: string; + }; + + const installer = getSkillInstaller(); + const registry = getSkillRegistry(); + let success = false; + + if (body.source === 'catalog' && body.name) { + success = await installer.installOfficialSkill(body.name); + if (!success) { + return c.json({ success: false, error: 'Failed to install skill from catalog' }, 500); + } + } else if (body.source === 'repo' && body.url) { + success = await installer.installFromRepo(body.url, body.name); + if (!success) { + return c.json({ success: false, error: 'Failed to install skill from repository. Make sure the repo contains a SKILL.md file.' }, 500); + } + } else if (body.source === 'local' && body.path) { + success = await installer.installFromLocal(body.path, body.name); + if (!success) { + return c.json({ success: false, error: 'Failed to install skill from local path. Make sure the path exists and contains a SKILL.md file.' }, 500); + } + } else { + return c.json({ success: false, error: 'Invalid install parameters. Required: source with corresponding url/path/name' }, 400); + } + + await registry.refresh(); + return c.json({ success: true }); + } catch (error) { + logger.error('[SkillsRoutes] Failed to install skill:', error); + return c.json({ success: false, error: (error as Error).message }, 500); + } + }); + + app.get('/catalog', async (c) => { + try { + const catalog = await fetchOfficialSkillsCatalog(); + return c.json(catalog); + } catch (error) { + logger.error('[SkillsRoutes] Failed to get catalog:', error); + return c.json([]); + } + }); + + return app; +}; diff --git a/src/server/routes/suggestions.ts b/src/server/routes/suggestions.ts new file mode 100644 index 00000000..2d0fcbb5 --- /dev/null +++ b/src/server/routes/suggestions.ts @@ -0,0 +1,186 @@ +import fg from 'fast-glob'; +import { Hono } from 'hono'; +import { execSync } from 'node:child_process'; +import { readdir } from 'node:fs/promises'; +import path from 'node:path'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; +import { getFileSystemService } from '../../services/FileSystemService.js'; +import { getFuzzyCommandSuggestions } from '../../slash-commands/index.js'; +import { + DEFAULT_EXCLUDE_DIRS, + DEFAULT_EXCLUDE_FILE_PATTERNS, +} from '../../utils/filePatterns.js'; + +const logger = createLogger(LogCategory.SERVICE); + +const getGitBranch = (cwd: string): string | null => { + try { + const branch = execSync('git rev-parse --abbrev-ref HEAD', { + cwd, + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'pipe'], + }).trim(); + return branch || null; + } catch { + return null; + } +}; + +type Variables = { + directory: string; +}; + +const DEFAULT_IGNORE_PATTERNS = [ + ...DEFAULT_EXCLUDE_DIRS.map((dir) => `${dir}/**`), + ...DEFAULT_EXCLUDE_DIRS, + ...DEFAULT_EXCLUDE_FILE_PATTERNS.map((pattern) => `**/${pattern}`), +]; + +const WEB_EXCLUDED_COMMANDS = new Set([ + '/theme', + '/model', + '/permissions', + '/mcp', + '/skills', + '/exit', + '/resume', + '/ide', + '/login', + '/logout', +]); + +export const SuggestionsRoutes = () => { + const app = new Hono<{ Variables: Variables }>(); + + app.get('/commands', async (c) => { + try { + const query = c.req.query('q') || ''; + const suggestions = getFuzzyCommandSuggestions(query) + .filter((s) => !WEB_EXCLUDED_COMMANDS.has(s.command)); + return c.json(suggestions); + } catch (error) { + logger.error('[SuggestionsRoutes] Failed to get command suggestions:', error); + return c.json([]); + } + }); + + app.get('/files', async (c) => { + try { + const query = c.req.query('q') || ''; + const directory = c.get('directory') || process.cwd(); + const limit = Math.min(Number(c.req.query('limit')) || 100, 1000); + + const files = await fg('**/*', { + cwd: directory, + dot: false, + followSymbolicLinks: false, + onlyFiles: false, + markDirectories: true, + unique: true, + ignore: DEFAULT_IGNORE_PATTERNS, + }); + + const normalized = files.map((f) => f.replace(/\\/g, '/')); + + if (!query) { + return c.json(normalized.slice(0, limit)); + } + + const lowerQuery = query.toLowerCase(); + const filtered = normalized + .filter((file) => file.toLowerCase().includes(lowerQuery)) + .slice(0, limit); + + return c.json(filtered); + } catch (error) { + logger.error('[SuggestionsRoutes] Failed to get file suggestions:', error); + return c.json([]); + } + }); + + app.get('/files/tree', async (c) => { + try { + const directory = c.get('directory') || process.cwd(); + const subPath = c.req.query('path') || ''; + const targetDir = subPath ? path.join(directory, subPath) : directory; + + const entries = await readdir(targetDir, { withFileTypes: true }); + + const items: Array<{ name: string; path: string; type: 'dir' | 'file' }> = entries.map((entry) => ({ + name: entry.name, + path: subPath ? `${subPath}/${entry.name}` : entry.name, + type: entry.isDirectory() ? 'dir' : 'file', + })); + + items.sort((a, b) => { + if (a.type !== b.type) return a.type === 'dir' ? -1 : 1; + return a.name.localeCompare(b.name); + }); + + const excludeDirs = new Set(DEFAULT_EXCLUDE_DIRS); + const filtered = items.filter((item) => { + const name = item.name; + if (name.startsWith('.')) return false; + if (excludeDirs.has(name)) return false; + for (const pattern of DEFAULT_EXCLUDE_FILE_PATTERNS) { + if (name.match(new RegExp(pattern.replace('*', '.*')))) return false; + } + return true; + }); + + return c.json(filtered); + } catch (error) { + logger.error('[SuggestionsRoutes] Failed to get file tree:', error); + return c.json([]); + } + }); + + app.get('/files/content', async (c) => { + try { + const rawPath = c.req.query('path'); + if (!rawPath) { + return c.json({ error: 'Missing file path' }, 400); + } + + const directory = c.get('directory') || process.cwd(); + const resolvedPath = path.resolve(directory, rawPath); + const relative = path.relative(directory, resolvedPath); + if (relative.startsWith('..') || path.isAbsolute(relative)) { + return c.json({ error: 'Invalid file path' }, 400); + } + + const fsService = getFileSystemService(); + const stat = await fsService.stat(resolvedPath); + if (!stat?.isFile) { + return c.json({ error: 'File not found' }, 404); + } + + const MAX_CHARS = 200000; + const content = await fsService.readTextFile(resolvedPath); + const truncated = content.length > MAX_CHARS; + + return c.json({ + path: rawPath, + content: truncated ? content.slice(0, MAX_CHARS) : content, + truncated, + size: stat.size, + }); + } catch (error) { + logger.error('[SuggestionsRoutes] Failed to get file content:', error); + return c.json({ error: 'Failed to read file' }, 500); + } + }); + + app.get('/git-info', async (c) => { + try { + const directory = c.get('directory') || process.cwd(); + const branch = getGitBranch(directory); + return c.json({ branch }); + } catch (error) { + logger.error('[SuggestionsRoutes] Failed to get git info:', error); + return c.json({ branch: null }); + } + }); + + return app; +}; diff --git a/src/server/routes/terminal.ts b/src/server/routes/terminal.ts new file mode 100644 index 00000000..f74b3762 --- /dev/null +++ b/src/server/routes/terminal.ts @@ -0,0 +1,307 @@ +import { Hono } from 'hono'; +import { createLogger, LogCategory } from '../../logging/Logger.js'; + +const logger = createLogger(LogCategory.SERVICE); + +// Common PTY interface that works with both bun-pty and node-pty +interface IPtyProcess { + pid: number; + write(data: string): void; + resize?(cols: number, rows: number): void; + kill(signal?: string): void; + onData(callback: (data: string) => void): void; + onExit(callback: (exitInfo: { exitCode: number }) => void): void; +} + +interface TerminalSession { + id: string; + process: IPtyProcess; + cwd: string; + buffer: string; + subscribers: Set<{ send: (data: string) => void; close: () => void }>; +} + +const terminals = new Map(); +const BUFFER_LIMIT = 1024 * 1024 * 2; +const BUFFER_CHUNK = 64 * 1024; + +type Variables = { + directory: string; +}; + +function isBunRuntime(): boolean { + return typeof (globalThis as Record).Bun !== 'undefined'; +} + +// Spawn PTY process - works with both bun-pty and node-pty +async function spawnPty( + command: string, + args: string[], + options: { cwd: string; env: Record } +): Promise { + if (isBunRuntime()) { + // Use bun-pty in Bun runtime + // @ts-expect-error bun-pty is only available in Bun runtime + const { spawn } = await import('bun-pty'); + const ptyProcess = spawn(command, args, { + name: 'xterm-256color', + cwd: options.cwd, + env: options.env, + }); + return { + pid: ptyProcess.pid, + write: (data: string) => ptyProcess.write(data), + resize: (cols: number, rows: number) => ptyProcess.resize({ cols, rows }), + kill: () => ptyProcess.kill(), + onData: (callback: (data: string) => void) => ptyProcess.onData(callback), + onExit: (callback: (exitInfo: { exitCode: number }) => void) => ptyProcess.onExit(callback), + }; + } + // Use node-pty in Node.js runtime + const nodePty = await import('node-pty'); + const ptyProcess = nodePty.spawn(command, args, { + name: 'xterm-256color', + cwd: options.cwd, + env: options.env, + cols: 80, + rows: 24, + }); + return { + pid: ptyProcess.pid, + write: (data: string) => ptyProcess.write(data), + resize: (cols: number, rows: number) => ptyProcess.resize(cols, rows), + kill: () => ptyProcess.kill(), + onData: (callback: (data: string) => void) => { + ptyProcess.onData(callback); + }, + onExit: (callback: (exitInfo: { exitCode: number }) => void) => { + ptyProcess.onExit(callback); + }, + }; +} + +// Export websocket handler for Bun server - will be undefined in Node.js +export let terminalWebSocket: unknown = undefined; + +// Initialize Bun WebSocket support if in Bun runtime +let upgradeWebSocket: ReturnType['upgradeWebSocket'] | undefined; + +if (isBunRuntime()) { + try { + const { createBunWebSocket } = await import('hono/bun'); + const bunWs = createBunWebSocket(); + upgradeWebSocket = bunWs.upgradeWebSocket; + terminalWebSocket = bunWs.websocket; + } catch (err) { + logger.warn('[Terminal] Failed to initialize Bun WebSocket support:', err); + } +} + +// Create terminal session handler (shared between Bun and Node.js) +async function handleTerminalConnection( + cwd: string, + ws: { send: (data: string) => void; close: () => void } +): Promise<{ + session: TerminalSession; + cleanup: () => void; +}> { + const terminalId = `term-${Date.now()}`; + logger.info(`[Terminal] New connection: ${terminalId}, cwd: ${cwd}`); + + const shell = process.platform === 'win32' + ? 'powershell.exe' + : process.env.SHELL || '/bin/zsh'; + const shellArgs = shell.endsWith('sh') ? ['-l'] : []; + + const ptyProcess = await spawnPty(shell, shellArgs, { + cwd, + env: { + ...process.env, + TERM: 'xterm-256color', + COLORTERM: 'truecolor', + } as Record, + }); + + const session: TerminalSession = { + id: terminalId, + process: ptyProcess, + cwd, + buffer: '', + subscribers: new Set(), + }; + + terminals.set(terminalId, session); + session.subscribers.add(ws); + + ptyProcess.onData((data: string) => { + let open = false; + for (const sub of session.subscribers) { + try { + sub.send(data); + open = true; + } catch { + session.subscribers.delete(sub); + } + } + if (open) return; + session.buffer += data; + if (session.buffer.length > BUFFER_LIMIT) { + session.buffer = session.buffer.slice(-BUFFER_LIMIT); + } + }); + + ptyProcess.onExit(({ exitCode }) => { + logger.info(`[Terminal] Process exited: ${terminalId}, code: ${exitCode}`); + for (const sub of session.subscribers) { + try { + sub.close(); + } catch { + // WebSocket may already be closed + } + } + terminals.delete(terminalId); + }); + + // Send buffered data + if (session.buffer) { + const buffer = session.buffer; + session.buffer = ''; + for (let i = 0; i < buffer.length; i += BUFFER_CHUNK) { + ws.send(buffer.slice(i, i + BUFFER_CHUNK)); + } + } + + return { + session, + cleanup: () => { + logger.info(`[Terminal] Connection closed: ${terminalId}`); + session.subscribers.delete(ws); + if (session.subscribers.size === 0) { + try { + session.process.kill(); + } catch { + // Process may already be dead + } + terminals.delete(terminalId); + } + }, + }; +} + +// Node.js WebSocket handler - will be set up by the server +export function setupNodeWebSocket( + wss: import('ws').WebSocketServer, + getDirectory: () => string +): void { + wss.on('connection', async (ws, req) => { + const url = new URL(req.url || '/', `http://${req.headers.host}`); + if (!url.pathname.endsWith('/terminal/ws')) { + ws.close(); + return; + } + + const cwd = url.searchParams.get('cwd') || getDirectory() || process.cwd(); + + try { + const { session, cleanup } = await handleTerminalConnection(cwd, { + send: (data: string) => ws.send(data), + close: () => ws.close(), + }); + + ws.on('message', (data) => { + try { + session.process.write(String(data)); + } catch (err) { + logger.error('[Terminal] Failed to write to PTY:', err); + } + }); + + ws.on('close', cleanup); + ws.on('error', cleanup); + } catch (err) { + logger.error('[Terminal] Failed to spawn PTY:', err); + ws.close(); + } + }); +} + +export const TerminalRoutes = () => { + const app = new Hono<{ Variables: Variables }>(); + + app.get('/status', (c) => { + const activeTerminals = Array.from(terminals.values()).map((t) => ({ + id: t.id, + cwd: t.cwd, + })); + return c.json({ active: activeTerminals.length, terminals: activeTerminals }); + }); + + app.post('/kill/:terminalId', (c) => { + const terminalId = c.req.param('terminalId'); + const session = terminals.get(terminalId); + + if (session) { + try { + session.process.kill(); + } catch { + // Process may already be dead + } + for (const ws of session.subscribers) { + ws.close(); + } + terminals.delete(terminalId); + return c.json({ success: true }); + } + + return c.json({ success: false, error: 'Terminal not found' }, 404); + }); + + // Only register Hono WebSocket route in Bun runtime + if (upgradeWebSocket) { + app.get( + '/ws', + upgradeWebSocket((c) => { + const cwd = c.req.query('cwd') || c.get('directory') || process.cwd(); + let sessionData: Awaited> | undefined; + + return { + async onOpen(_event, ws) { + try { + sessionData = await handleTerminalConnection(cwd, { + send: (data: string) => (ws as unknown as { send: (d: string) => void }).send(data), + close: () => (ws as unknown as { close: () => void }).close(), + }); + } catch (err) { + logger.error('[Terminal] Failed to spawn PTY:', err); + (ws as unknown as { close: () => void }).close(); + } + }, + + onMessage(event) { + if (!sessionData) return; + try { + sessionData.session.process.write(String(event.data)); + } catch (err) { + logger.error('[Terminal] Failed to write to PTY:', err); + } + }, + + onClose() { + sessionData?.cleanup(); + }, + }; + }) + ); + } else { + // In Node.js, WebSocket is handled separately via setupNodeWebSocket + // Return info about how to connect + app.get('/ws', (c) => { + return c.json({ + message: 'WebSocket endpoint - connect via ws:// protocol', + hint: 'Use WebSocket client to connect to this endpoint', + }); + }); + } + + return app; +}; diff --git a/src/server/server.ts b/src/server/server.ts new file mode 100644 index 00000000..f0bb0c05 --- /dev/null +++ b/src/server/server.ts @@ -0,0 +1,546 @@ +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { existsSync, readFileSync } from 'node:fs'; +import { createServer, type Server as NodeServer } from 'node:http'; +import { networkInterfaces } from 'node:os'; +import { dirname, extname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { WebSocketServer } from 'ws'; +import { createLogger, LogCategory } from '../logging/Logger.js'; +import { getVersion } from '../utils/packageInfo.js'; +import { BladeServerError } from './error.js'; +import { ConfigRoutes } from './routes/config.js'; +import { GlobalRoutes } from './routes/global.js'; +import { McpRoutes } from './routes/mcp.js'; +import { ModelsRoutes } from './routes/models.js'; +import { PermissionRoutes } from './routes/permission.js'; +import { ProviderRoutes } from './routes/provider.js'; +import { SessionRoutes } from './routes/session.js'; +import { SkillsRoutes } from './routes/skills.js'; +import { SuggestionsRoutes } from './routes/suggestions.js'; +import { setupNodeWebSocket, TerminalRoutes, terminalWebSocket } from './routes/terminal.js'; + +const logger = createLogger(LogCategory.SERVICE); + +export interface ServerOptions { + port: number; + hostname: string; + cors?: string[]; + password?: string; + username?: string; +} + +let corsWhitelist: string[] = []; + +type Variables = { + directory: string; +}; + +function getWebDistPath(): string | null { + const currentDir = dirname(fileURLToPath(import.meta.url)); + + const possiblePaths = [ + join(currentDir, 'web'), + join(process.cwd(), 'dist/web'), + ]; + + for (const p of possiblePaths) { + if (existsSync(join(p, 'index.html'))) { + logger.debug(`[Server] Found web dist at: ${p}`); + return p; + } + } + + return null; +} + +function createApp(): Hono<{ Variables: Variables }> { + const app = new Hono<{ Variables: Variables }>(); + + app.onError((err, c) => { + logger.error('[Server] Request error:', err); + + if (err instanceof BladeServerError) { + return c.json(err.toObject(), err.statusCode as 400 | 401 | 403 | 404 | 409 | 500); + } + + const message = err instanceof Error ? err.message : String(err); + return c.json( + { error: { code: 'INTERNAL_ERROR', message } }, + 500 + ); + }); + + app.use(async (c, next) => { + const password = process.env.BLADE_SERVER_PASSWORD; + if (!password) { + return next(); + } + + const path = c.req.path; + if (path === '/' || path.startsWith('/assets/') || path.endsWith('.html') || path.endsWith('.js') || path.endsWith('.css')) { + return next(); + } + + const auth = c.req.header('Authorization'); + if (!auth?.startsWith('Basic ')) { + c.header('WWW-Authenticate', 'Basic realm="Blade Server"'); + return c.json({ error: { code: 'UNAUTHORIZED', message: 'Authentication required' } }, 401); + } + + const credentials = Buffer.from(auth.slice(6), 'base64').toString(); + const [username, pwd] = credentials.split(':'); + const expectedUsername = process.env.BLADE_SERVER_USERNAME ?? 'blade'; + + if (username !== expectedUsername || pwd !== password) { + return c.json({ error: { code: 'UNAUTHORIZED', message: 'Invalid credentials' } }, 401); + } + + return next(); + }); + + app.use(async (c, next) => { + const skipLogging = c.req.path === '/health' || c.req.path === '/global/health' || c.req.path.startsWith('/assets/'); + if (!skipLogging) { + logger.debug(`[Server] ${c.req.method} ${c.req.path}`); + } + + const start = Date.now(); + await next(); + + if (!skipLogging) { + const duration = Date.now() - start; + logger.debug(`[Server] ${c.req.method} ${c.req.path} - ${c.res.status} (${duration}ms)`); + } + }); + + app.use( + cors({ + origin(origin) { + if (!origin) return undefined; + + if (origin.startsWith('http://localhost:')) return origin; + if (origin.startsWith('http://127.0.0.1:')) return origin; + if (origin === 'tauri://localhost' || origin === 'http://tauri.localhost') return origin; + + if (corsWhitelist.includes(origin)) return origin; + + return undefined; + }, + }) + ); + + app.use(async (c, next) => { + let directory = c.req.query('directory') || c.req.header('x-blade-directory') || process.cwd(); + try { + directory = decodeURIComponent(directory); + } catch { + // Keep original directory if decoding fails + } + c.set('directory', directory); + return next(); + }); + + app.route('/global', GlobalRoutes()); + app.route('/sessions', SessionRoutes()); + app.route('/configs', ConfigRoutes()); + app.route('/permissions', PermissionRoutes()); + app.route('/providers', ProviderRoutes()); + app.route('/models', ModelsRoutes()); + app.route('/suggestions', SuggestionsRoutes()); + app.route('/terminal', TerminalRoutes()); + app.route('/mcp', McpRoutes()); + app.route('/skills', SkillsRoutes()); + + app.get('/health', (c) => { + return c.json({ healthy: true, version: getVersion() }); + }); + + const webDistPath = getWebDistPath(); + + if (webDistPath) { + logger.info(`[Server] Serving static files from ${webDistPath}`); + + app.get('/assets/*', (c) => { + const filePath = join(webDistPath, c.req.path); + + if (!existsSync(filePath)) { + return c.json({ error: { code: 'NOT_FOUND', message: `File not found: ${c.req.path}` } }, 404); + } + + const content = readFileSync(filePath); + const ext = extname(filePath).toLowerCase(); + + const mimeTypes: Record = { + '.js': 'application/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.eot': 'application/vnd.ms-fontobject', + }; + + const contentType = mimeTypes[ext] || 'application/octet-stream'; + + return new Response(content, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=31536000, immutable', + }, + }); + }); + + app.get('/', (c) => { + const indexPath = join(webDistPath, 'index.html'); + const html = readFileSync(indexPath, 'utf-8'); + return c.html(html); + }); + + app.get('*', (c) => { + const path = c.req.path; + + if (path.includes('.')) { + return c.json( + { error: { code: 'NOT_FOUND', message: `File not found: ${path}` } }, + 404 + ); + } + + const indexPath = join(webDistPath, 'index.html'); + const html = readFileSync(indexPath, 'utf-8'); + return c.html(html); + }); + } else { + logger.warn('[Server] Web UI not found. Run "cd web && pnpm build" to enable web interface.'); + + app.get('/', (c) => { + return c.json({ + message: 'Blade API Server', + version: getVersion(), + webUI: false, + hint: 'Web UI not built. Run "cd web && pnpm build" to enable.', + endpoints: { + health: '/health', + sessions: '/sessions', + configs: '/configs', + permissions: '/permissions', + providers: '/providers', + }, + }); + }); + + app.all('*', (c) => { + return c.json( + { error: { code: 'NOT_FOUND', message: `Route not found: ${c.req.path}` } }, + 404 + ); + }); + } + + return app; +} + +export function getNetworkIPs(): string[] { + const nets = networkInterfaces(); + const results: string[] = []; + + for (const name of Object.keys(nets)) { + const net = nets[name]; + if (!net) continue; + + for (const netInfo of net) { + if (netInfo.internal || netInfo.family !== 'IPv4') continue; + if (netInfo.address.startsWith('172.')) continue; + results.push(netInfo.address); + } + } + + return results; +} + +function isBunRuntime(): boolean { + return typeof (globalThis as Record).Bun !== 'undefined'; +} + +interface BunServer { + url: URL; + port: number; + hostname: string; + stop: (closeActiveConnections?: boolean) => void; +} + +interface ServerHandle { + url: URL; + port: number; + hostname: string; + stop: () => Promise; +} + +function startWithBun( + honoApp: Hono<{ Variables: Variables }>, + opts: ServerOptions +): ServerHandle { + const Bun = (globalThis as Record).Bun as { + serve: (options: { + hostname: string; + port: number; + fetch: (request: Request, server: unknown) => Response | Promise; + idleTimeout?: number; + websocket?: unknown; + }) => BunServer; + }; + + const tryServe = (port: number): BunServer | undefined => { + try { + return Bun.serve({ + hostname: opts.hostname, + port, + fetch: honoApp.fetch, + idleTimeout: 0, + websocket: terminalWebSocket, + }); + } catch (err) { + logger.error('Failed to start Bun server:', err); + return undefined; + } + }; + + const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port); + + if (!server) { + throw new Error(`Failed to start Bun server on port ${opts.port}`); + } + + return { + url: server.url, + port: server.port, + hostname: server.hostname, + stop: async () => { + server.stop(true); + }, + }; +} + +function startWithNode( + honoApp: Hono<{ Variables: Variables }>, + opts: ServerOptions +): Promise { + return new Promise((resolve, reject) => { + const server: NodeServer = createServer(async (req, res) => { + const url = new URL(req.url || '/', `http://${req.headers.host}`); + + const headers = new Headers(); + for (const [key, value] of Object.entries(req.headers)) { + if (value) { + if (Array.isArray(value)) { + for (const v of value) { + headers.append(key, v); + } + } else { + headers.set(key, value); + } + } + } + + let body: BodyInit | undefined; + if (req.method !== 'GET' && req.method !== 'HEAD') { + const buffer = await new Promise((resolve) => { + const chunks: Buffer[] = []; + req.on('data', (chunk: Buffer) => chunks.push(chunk)); + req.on('end', () => resolve(Buffer.concat(chunks))); + }); + body = buffer.toString(); + } + + const request = new Request(url.toString(), { + method: req.method, + headers, + body, + }); + + try { + const response = await honoApp.fetch(request); + + res.statusCode = response.status; + response.headers.forEach((value, key) => { + res.setHeader(key, value); + }); + + if (response.body) { + const reader = response.body.getReader(); + const pump = async (): Promise => { + const { done, value } = await reader.read(); + if (done) { + res.end(); + return; + } + res.write(value); + return pump(); + }; + await pump(); + } else { + const text = await response.text(); + res.end(text); + } + } catch (error) { + logger.error('[Server] Node request error:', error); + res.statusCode = 500; + res.end(JSON.stringify({ error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } })); + } + }); + + // Set up WebSocket server for terminal (noServer mode for manual upgrade handling) + const wss = new WebSocketServer({ noServer: true }); + const currentDirectory = process.cwd(); + setupNodeWebSocket(wss, () => currentDirectory); + + // Handle WebSocket upgrade requests + server.on('upgrade', (request, socket, head) => { + const url = new URL(request.url || '/', `http://${request.headers.host}`); + if (url.pathname === '/terminal/ws') { + wss.handleUpgrade(request, socket, head, (ws) => { + wss.emit('connection', ws, request); + }); + } else { + socket.destroy(); + } + }); + + const tryListen = (port: number): Promise => { + return new Promise((resolve, reject) => { + server.once('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EADDRINUSE') { + reject(err); + } else { + reject(err); + } + }); + server.listen(port, opts.hostname, () => { + const addr = server.address(); + if (addr && typeof addr === 'object') { + resolve(addr.port); + } else { + resolve(port); + } + }); + }); + }; + + const startServer = async () => { + let actualPort: number; + + if (opts.port === 0) { + try { + actualPort = await tryListen(4096); + } catch { + actualPort = await tryListen(0); + } + } else { + actualPort = await tryListen(opts.port); + } + + const url = new URL(`http://${opts.hostname === '0.0.0.0' ? 'localhost' : opts.hostname}:${actualPort}`); + + resolve({ + url, + port: actualPort, + hostname: opts.hostname, + stop: async () => { + return new Promise((resolve) => { + wss.close(() => { + server.close(() => resolve()); + }); + }); + }, + }); + }; + + startServer().catch(reject); + }); +} + +export namespace BladeServer { + let serverHandle: ServerHandle | undefined; + let app: Hono<{ Variables: Variables }> | undefined; + + export function getApp(): Hono<{ Variables: Variables }> { + if (!app) { + app = createApp(); + } + return app; + } + + export function listen(opts: ServerOptions): ServerHandle { + corsWhitelist = opts.cors ?? []; + + const honoApp = getApp(); + + if (isBunRuntime()) { + serverHandle = startWithBun(honoApp, opts); + logger.info(`[Server] Blade server listening on ${serverHandle.url} (Bun runtime)`); + } else { + throw new Error( + 'Blade web server requires Bun runtime. ' + + 'Please run with Bun: `bun run blade web` or install Bun from https://bun.sh' + ); + } + + const handle = serverHandle; + + return { + url: handle.url, + port: handle.port, + hostname: handle.hostname, + stop: async () => { + if (serverHandle) { + await serverHandle.stop(); + serverHandle = undefined; + app = undefined; + logger.info('[Server] Blade server stopped'); + } + }, + }; + } + + export async function listenAsync(opts: ServerOptions): Promise { + corsWhitelist = opts.cors ?? []; + + const honoApp = getApp(); + + if (isBunRuntime()) { + serverHandle = startWithBun(honoApp, opts); + logger.info(`[Server] Blade server listening on ${serverHandle.url} (Bun runtime)`); + } else { + serverHandle = await startWithNode(honoApp, opts); + logger.info(`[Server] Blade server listening on ${serverHandle.url} (Node.js runtime)`); + } + + const handle = serverHandle; + + return { + url: handle.url, + port: handle.port, + hostname: handle.hostname, + stop: async () => { + if (serverHandle) { + await serverHandle.stop(); + serverHandle = undefined; + app = undefined; + logger.info('[Server] Blade server stopped'); + } + }, + }; + } + + export function isRunning(): boolean { + return serverHandle !== undefined; + } +} diff --git a/src/services/SessionService.ts b/src/services/SessionService.ts index 31892158..a3785ef0 100644 --- a/src/services/SessionService.ts +++ b/src/services/SessionService.ts @@ -52,9 +52,11 @@ export class SessionService { const projectDirPath = path.join(projectsDir, dir.name); const projectPath = unescapeProjectPath(dir.name); - // 读取项目目录下的所有 JSONL 文件 + // 读取项目目录下的所有 JSONL 文件(排除子代理会话文件) const files = await readdir(projectDirPath); - const jsonlFiles = files.filter((f) => f.endsWith('.jsonl')); + const jsonlFiles = files.filter( + (f) => f.endsWith('.jsonl') && !f.startsWith('agent_') + ); for (const file of jsonlFiles) { const filePath = path.join(projectDirPath, file); @@ -249,7 +251,7 @@ export class SessionService { role: 'tool', content, tool_call_id: entry.toolResult.id, - name: entry.tool?.name, // 从对应的 tool_use 获取工具名称 + name: entry.tool?.name, }); } break; diff --git a/src/skills/SkillInstaller.ts b/src/skills/SkillInstaller.ts index af9515a7..91132038 100644 --- a/src/skills/SkillInstaller.ts +++ b/src/skills/SkillInstaller.ts @@ -144,6 +144,123 @@ export class SkillInstaller { } } + /** + * 从 GitHub 仓库安装 Skill + * @param repoUrl GitHub 仓库 URL (例如: https://github.com/user/skill-name) + * @param skillName 可选的 skill 名称,默认从 URL 提取 + */ + async installFromRepo(repoUrl: string, skillName?: string): Promise { + const name = skillName || this.extractRepoName(repoUrl); + const localPath = path.join(this.skillsDir, name); + const tempDir = path.join(this.skillsDir, `.tmp-repo-${name}-${Date.now()}`); + + try { + if (!(await this.isGitAvailable())) { + logger.warn('Git not available, cannot install from repo'); + return false; + } + + logger.info(`Installing skill from repo: ${repoUrl}...`); + + await fs.mkdir(this.skillsDir, { recursive: true, mode: 0o755 }); + + await execAsync( + `git clone --depth 1 "${repoUrl}" "${tempDir}"`, + { timeout: 60000 } + ); + + const skillMdPath = path.join(tempDir, 'SKILL.md'); + try { + await fs.access(skillMdPath); + } catch { + logger.warn(`No SKILL.md found in repository ${repoUrl}`); + await fs.rm(tempDir, { recursive: true, force: true }); + return false; + } + + await fs.rm(localPath, { recursive: true, force: true }); + await fs.rename(tempDir, localPath); + + try { + await fs.rm(path.join(localPath, '.git'), { recursive: true, force: true }); + } catch { + // ignore + } + + logger.info(`Successfully installed skill from repo: ${name}`); + return true; + } catch (error) { + try { + await fs.rm(tempDir, { recursive: true, force: true }); + } catch { + // ignore + } + logger.warn( + `Failed to install from repo ${repoUrl}: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + return false; + } + } + + /** + * 从本地路径安装 Skill(创建符号链接或复制) + * @param localSourcePath 本地 skill 路径 + * @param skillName 可选的 skill 名称,默认从路径提取 + * @param symlink 是否使用符号链接(默认 true,方便开发) + */ + async installFromLocal(localSourcePath: string, skillName?: string, symlink = true): Promise { + const name = skillName || path.basename(localSourcePath); + const targetPath = path.join(this.skillsDir, name); + + try { + const sourcePath = path.resolve(localSourcePath); + + try { + await fs.access(sourcePath); + } catch { + logger.warn(`Local path does not exist: ${sourcePath}`); + return false; + } + + const skillMdPath = path.join(sourcePath, 'SKILL.md'); + try { + await fs.access(skillMdPath); + } catch { + logger.warn(`No SKILL.md found in local path: ${sourcePath}`); + return false; + } + + logger.info(`Installing skill from local path: ${sourcePath}...`); + + await fs.mkdir(this.skillsDir, { recursive: true, mode: 0o755 }); + + await fs.rm(targetPath, { recursive: true, force: true }); + + if (symlink) { + await fs.symlink(sourcePath, targetPath, 'dir'); + logger.info(`Created symlink for skill: ${name}`); + } else { + await fs.cp(sourcePath, targetPath, { recursive: true }); + logger.info(`Copied skill to: ${name}`); + } + + return true; + } catch (error) { + logger.warn( + `Failed to install from local path ${localSourcePath}: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + return false; + } + } + + /** + * 从 URL 提取仓库名称 + */ + private extractRepoName(url: string): string { + const match = url.match(/\/([^/]+?)(\.git)?$/); + return match?.[1] || 'unknown-skill'; + } + /** * 安装所有官方 Skills */ diff --git a/src/tools/builtin/file/edit.ts b/src/tools/builtin/file/edit.ts index ae09f73d..f82cd737 100644 --- a/src/tools/builtin/file/edit.ts +++ b/src/tools/builtin/file/edit.ts @@ -110,11 +110,14 @@ export const editTool = createTool({ return { success: false, llmContent: `You must use your Read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.`, - displayContent: `❌ 编辑失败:必须先使用 Read 工具读取文件\n\n请先用 Read 工具查看文件内容,再进行编辑。`, + displayContent: `📖 我需要先读取文件内容,然后再进行编辑。`, error: { type: ToolErrorType.VALIDATION_ERROR, message: 'File not read before edit', }, + metadata: { + requiresRead: true, + }, }; } @@ -124,7 +127,7 @@ export const editTool = createTool({ return { success: false, llmContent: `The file has been modified by an external program since you last read it. You must use the Read tool again to see the current content before editing.\n\nDetails: ${externalModCheck.message}`, - displayContent: `❌ 编辑失败:文件已被外部程序修改\n\n${externalModCheck.message}\n\n💡 请重新使用 Read 工具读取最新内容后再编辑`, + displayContent: `❌ 编辑失败:文件已被外部程序修改\n\n${externalModCheck.message}\n\n💡 我需要重新读取文件内容后再编辑`, error: { type: ToolErrorType.VALIDATION_ERROR, message: 'File modified externally', @@ -647,10 +650,10 @@ Common issues: } displayContent += `\n📄 文件内容摘录 (${excerptStartLine + 1}-${excerptEndLine} 行):\n${excerpt}\n`; - displayContent += `\n🔧 建议:\n`; - displayContent += ` 1. 使用 Read 工具重新读取文件\n`; - displayContent += ` 2. 检查空格、换行符、引号是否完全匹配\n`; - displayContent += ` 3. 提供更多上下文代码确保唯一性`; + displayContent += `\n🔧 接下来我会:\n`; + displayContent += ` 1. 重新读取文件内容\n`; + displayContent += ` 2. 仔细核对空格、换行符、引号\n`; + displayContent += ` 3. 使用更多上下文代码确保唯一性`; return { llmContent, diff --git a/src/tools/builtin/file/write.ts b/src/tools/builtin/file/write.ts index cd8884d1..305a8bb3 100644 --- a/src/tools/builtin/file/write.ts +++ b/src/tools/builtin/file/write.ts @@ -107,11 +107,14 @@ export const writeTool = createTool({ return { success: false, llmContent: `If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.`, - displayContent: `❌ 写入失败:必须先使用 Read 工具读取文件\n\n文件 ${file_path} 已存在,请先用 Read 工具查看其内容。`, + displayContent: `📖 我需要先读取文件内容,然后再进行写入。`, error: { type: ToolErrorType.VALIDATION_ERROR, message: 'File not read before write', }, + metadata: { + requiresRead: true, + }, }; } @@ -121,7 +124,7 @@ export const writeTool = createTool({ return { success: false, llmContent: `The file has been modified by an external program since you last read it. You must use the Read tool again to see the current content before writing.\n\nDetails: ${externalModCheck.message}`, - displayContent: `❌ 写入失败:文件已被外部程序修改\n\n${externalModCheck.message}\n\n💡 请重新使用 Read 工具读取最新内容后再写入`, + displayContent: `❌ 写入失败:文件已被外部程序修改\n\n${externalModCheck.message}\n\n💡 我需要重新读取文件内容后再写入`, error: { type: ToolErrorType.VALIDATION_ERROR, message: 'File modified externally', @@ -162,7 +165,7 @@ export const writeTool = createTool({ return { success: false, llmContent: `Binary file writes are not supported in ACP mode. The IDE only supports text file operations. Please use encoding='utf8' for text files, or ask the user to write the file manually.`, - displayContent: `❌ ACP 模式不支持二进制文件写入\n\n当前通过 IDE 执行文件操作,但 IDE 仅支持文本文件。\n\n💡 建议:\n • 如果是文本文件,使用 encoding='utf8'\n • 如果必须写入二进制文件,请在本地终端执行`, + displayContent: `❌ ACP 模式不支持二进制文件写入\n\n当前通过 IDE 执行文件操作,但 IDE 仅支持文本文件。\n\n💡 如果是文本文件,我会使用 encoding='utf8' 重试;如果必须写入二进制文件,需要在本地终端执行`, error: { type: ToolErrorType.VALIDATION_ERROR, message: 'Binary writes not supported in ACP mode', diff --git a/src/tools/builtin/spec/TransitionSpecPhaseTool.ts b/src/tools/builtin/spec/TransitionSpecPhaseTool.ts index ab69268b..185fabdf 100644 --- a/src/tools/builtin/spec/TransitionSpecPhaseTool.ts +++ b/src/tools/builtin/spec/TransitionSpecPhaseTool.ts @@ -176,7 +176,7 @@ TransitionSpecPhase({ targetPhase: "design" }) '})\n' + '```\n\n' + 'After adding tasks, try transitioning again.', - displayContent: '❌ No tasks defined - use AddTask first', + displayContent: '❌ No tasks defined - 我需要先添加任务', error: { type: ToolErrorType.VALIDATION_ERROR, message: 'No tasks defined. Use AddTask tool to add tasks first.', diff --git a/src/tools/builtin/task/task.ts b/src/tools/builtin/task/task.ts index 415da914..64a49242 100644 --- a/src/tools/builtin/task/task.ts +++ b/src/tools/builtin/task/task.ts @@ -320,6 +320,7 @@ export const taskTool = createTool({ `✅ Subagent 任务完成\n\n` + `类型: ${subagent_type}\n` + `任务: ${description}\n` + + `Agent ID: ${result.agentId || 'N/A'}\n` + `耗时: ${duration}ms\n` + `工具调用: ${result.stats?.toolCalls || 0} 次\n` + `Token: ${result.stats?.tokens || 0}\n\n` + @@ -329,6 +330,10 @@ export const taskTool = createTool({ description, duration, stats: result.stats, + subagentSessionId: result.agentId, + subagentType: subagent_type, + subagentStatus: 'completed' as const, + subagentSummary: result.message.slice(0, 500), }, }; } else { @@ -339,12 +344,18 @@ export const taskTool = createTool({ `⚠️ Subagent 任务失败\n\n` + `类型: ${subagent_type}\n` + `任务: ${description}\n` + + `Agent ID: ${result.agentId || 'N/A'}\n` + `耗时: ${duration}ms\n` + `错误: ${result.error}`, error: { type: ToolErrorType.EXECUTION_ERROR, message: result.error || 'Unknown error', }, + metadata: { + subagentSessionId: result.agentId, + subagentType: subagent_type, + subagentStatus: 'failed' as const, + }, }; } } catch (error) { @@ -418,6 +429,9 @@ function handleBackgroundExecution( subagent_type: subagentConfig.name, description, background: true, + subagentSessionId: agentId, + subagentType: subagentConfig.name, + subagentStatus: 'running' as const, }, }; } @@ -458,7 +472,7 @@ function handleResume( return { success: false, llmContent: `Cannot resume agent ${agentId}: still running`, - displayContent: `❌ 无法恢复 Agent: ${agentId}\n\nAgent 仍在运行中,请使用 TaskOutput 获取结果`, + displayContent: `❌ 无法恢复 Agent: ${agentId}\n\nAgent 仍在运行中,我会使用 TaskOutput 获取结果`, error: { type: ToolErrorType.EXECUTION_ERROR, message: `Agent is still running: ${agentId}`, @@ -508,6 +522,9 @@ function handleResume( subagent_type: subagentConfig.name, description, background: true, + subagentSessionId: newAgentId, + subagentType: subagentConfig.name, + subagentStatus: 'running' as const, }, }; } diff --git a/src/tools/execution/PipelineStages.ts b/src/tools/execution/PipelineStages.ts index 61ce5d21..6dd6ef02 100644 --- a/src/tools/execution/PipelineStages.ts +++ b/src/tools/execution/PipelineStages.ts @@ -398,8 +398,10 @@ export class ConfirmationStage implements PipelineStage { // 如果提供了 confirmationHandler,使用它来请求用户确认 const confirmationHandler = execution.context.confirmationHandler; if (confirmationHandler) { + logger.info(`[ConfirmationStage] Requesting confirmation for ${tool.name}`); const response = await confirmationHandler.requestConfirmation(confirmationDetails); + logger.info(`[ConfirmationStage] Confirmation response: approved=${response.approved}`); if (!response.approved) { execution.abort( @@ -408,6 +410,7 @@ export class ConfirmationStage implements PipelineStage { ); return; } + logger.info(`[ConfirmationStage] User approved, continuing to execution stage`); const scope = response.scope || 'once'; if (scope === 'session' && execution._internal.permissionSignature) { diff --git a/src/tools/types/ExecutionTypes.ts b/src/tools/types/ExecutionTypes.ts index ab1e64ea..fd73facd 100644 --- a/src/tools/types/ExecutionTypes.ts +++ b/src/tools/types/ExecutionTypes.ts @@ -22,6 +22,8 @@ export interface ConfirmationDetails { | 'maxTurnsExceeded' | 'askUserQuestion'; // 确认类型 kind?: ToolKind; // 工具类型(readonly, write, execute),用于 ACP 权限模式判断 + toolName?: string; + args?: Record; title?: string; message: string; details?: string; // 🆕 Plan 方案内容或其他详细信息 diff --git a/web/.eslintrc.cjs b/web/.eslintrc.cjs new file mode 100644 index 00000000..742a93ed --- /dev/null +++ b/web/.eslintrc.cjs @@ -0,0 +1,16 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, +} diff --git a/web/.npmrc b/web/.npmrc new file mode 100644 index 00000000..214c29d1 --- /dev/null +++ b/web/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/web/components.json b/web/components.json new file mode 100644 index 00000000..62f1aa76 --- /dev/null +++ b/web/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 00000000..9e100f08 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + BladeCode + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 00000000..b8a7aba5 --- /dev/null +++ b/web/package.json @@ -0,0 +1,60 @@ +{ + "name": "blade-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@fontsource/jetbrains-mono": "^5.2.8", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@types/react-syntax-highlighter": "^15.5.13", + "@xterm/addon-fit": "^0.11.0", + "@xterm/addon-web-links": "^0.12.0", + "@xterm/xterm": "^6.0.0", + "ahooks": "^3.9.6", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "cmdk": "^1.1.1", + "lucide-react": "^0.300.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^16.1.0", + "remark-gfm": "^4.0.1", + "swr": "^2.2.4", + "tailwind-merge": "^2.2.0", + "tailwindcss-animate": "^1.0.7", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@monaco-editor/react": "^4.7.0", + "@types/node": "^20.11.0", + "@types/react": "^18.2.43", + "@types/react-dom": "^18.2.17", + "@typescript-eslint/eslint-plugin": "^6.14.0", + "@typescript-eslint/parser": "^6.14.0", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.16", + "eslint": "^8.55.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "postcss": "^8.4.32", + "tailwindcss": "^3.4.0", + "typescript": "^5.2.2", + "vite": "^5.0.8" + } +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 00000000..bb15dac7 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,4792 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fontsource/jetbrains-mono': + specifier: ^5.2.8 + version: 5.2.8 + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-separator': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': + specifier: ^1.0.2 + version: 1.2.4(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 + '@xterm/addon-fit': + specifier: ^0.11.0 + version: 0.11.0 + '@xterm/addon-web-links': + specifier: ^0.12.0 + version: 0.12.0 + '@xterm/xterm': + specifier: ^6.0.0 + version: 6.0.0 + ahooks: + specifier: ^3.9.6 + version: 3.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.1 + clsx: + specifier: ^2.1.0 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: + specifier: ^0.300.0 + version: 0.300.0(react@18.3.1) + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@18.3.27)(react@18.3.1) + react-syntax-highlighter: + specifier: ^16.1.0 + version: 16.1.0(react@18.3.1) + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + swr: + specifier: ^2.2.4 + version: 2.3.8(react@18.3.1) + tailwind-merge: + specifier: ^2.2.0 + version: 2.6.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + zustand: + specifier: ^4.5.0 + version: 4.5.7(@types/react@18.3.27)(react@18.3.1) + devDependencies: + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/node': + specifier: ^20.11.0 + version: 20.19.30 + '@types/react': + specifier: ^18.2.43 + version: 18.3.27 + '@types/react-dom': + specifier: ^18.2.17 + version: 18.3.7(@types/react@18.3.27) + '@typescript-eslint/eslint-plugin': + specifier: ^6.14.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.14.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.7.0(vite@5.4.21(@types/node@20.19.30)) + autoprefixer: + specifier: ^10.4.16 + version: 10.4.23(postcss@8.5.6) + eslint: + specifier: ^8.55.0 + version: 8.57.1 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.1) + eslint-plugin-react-refresh: + specifier: ^0.4.5 + version: 0.4.26(eslint@8.57.1) + postcss: + specifier: ^8.4.32 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.0 + version: 3.4.19 + typescript: + specifier: ^5.2.2 + version: 5.9.3 + vite: + specifier: ^5.0.8 + version: 5.4.21(@types/node@20.19.30) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@fontsource/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.56.0': + resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.56.0': + resolution: {integrity: sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.56.0': + resolution: {integrity: sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.56.0': + resolution: {integrity: sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.56.0': + resolution: {integrity: sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.56.0': + resolution: {integrity: sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.56.0': + resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.56.0': + resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.56.0': + resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.56.0': + resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.56.0': + resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.56.0': + resolution: {integrity: sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + resolution: {integrity: sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + resolution: {integrity: sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.56.0': + resolution: {integrity: sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.56.0': + resolution: {integrity: sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react-syntax-highlighter@15.5.13': + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@xterm/addon-fit@0.11.0': + resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + + '@xterm/addon-web-links@0.12.0': + resolution: {integrity: sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==} + + '@xterm/xterm@6.0.0': + resolution: {integrity: sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ahooks@3.9.6: + resolution: {integrity: sha512-Mr7f05swd5SmKlR9SZo5U6M0LsL4ErweLzpdgXjA1JPmnZ78Vr6wzx0jUtvoxrcqGKYnX0Yjc02iEASVxHFPjQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.9.17: + resolution: {integrity: sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + electron-to-chromium@1.5.278: + resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.300.0: + resolution: {integrity: sha512-rQxUUCmWAvNLoAsMZ5j04b2+OJv6UuNLYMY7VK0eVlm4aTwUEjEEHc09/DipkNIlhXUSDn2xoyIzVT0uh7dRsg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-syntax-highlighter@16.1.0: + resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.56.0: + resolution: {integrity: sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swr@2.3.8: + resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.10': {} + + '@fontsource/jetbrains-mono@5.2.8': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.52.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-label@2.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.27)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.27 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.56.0': + optional: true + + '@rollup/rollup-android-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-x64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.56.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.56.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.56.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.56.0': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/js-cookie@3.0.6': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/node@20.19.30': + dependencies: + undici-types: 6.21.0 + + '@types/prismjs@1.26.5': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.27)': + dependencies: + '@types/react': 18.3.27 + + '@types/react-syntax-highlighter@15.5.13': + dependencies: + '@types/react': 18.3.27 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/semver@7.7.1': {} + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.7.3 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.30))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@20.19.30) + transitivePeerDependencies: + - supports-color + + '@xterm/addon-fit@0.11.0': {} + + '@xterm/addon-web-links@0.12.0': {} + + '@xterm/xterm@6.0.0': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ahooks@3.9.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.6 + '@types/js-cookie': 3.0.6 + dayjs: 1.11.19 + intersection-observer: 0.12.2 + js-cookie: 3.0.5 + lodash: 4.17.23 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.8.1 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + array-union@2.1.0: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001766 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.17: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.17 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.278 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001766: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + dequal@2.0.3: {} + + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + electron-to-chromium@1.5.278: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react-refresh@0.4.26(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-is-identifier-name@3.0.0: {} + + esutils@2.0.3: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fault@1.0.4: + dependencies: + format: 0.2.2 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + format@0.2.2: {} + + fraction.js@5.3.4: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + + html-url-attributes@3.0.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inline-style-parser@0.2.7: {} + + intersection-observer@0.12.2: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-decimal@2.0.1: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@4.1.0: {} + + isexe@2.0.0: {} + + jiti@1.21.7: {} + + js-cookie@3.0.5: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.23: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.300.0(react@18.3.1): + dependencies: + react: 18.3.1 + + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.2 + + monaco-editor@0.52.2: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prismjs@1.30.0: {} + + property-information@7.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-fast-compare@3.2.2: {} + + react-markdown@10.1.0(@types/react@18.3.27)(react@18.3.1): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 18.3.27 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@18.3.27)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 + + react-remove-scroll@2.7.2(@types/react@18.3.27)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.27)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.27)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.27)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + + react-style-singleton@2.2.3(@types/react@18.3.27)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 + + react-syntax-highlighter@16.1.0(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.6 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 18.3.1 + refractor: 5.0.0 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/prismjs': 1.26.5 + hastscript: 9.0.1 + parse-entities: 4.0.2 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.56.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.56.0 + '@rollup/rollup-android-arm64': 4.56.0 + '@rollup/rollup-darwin-arm64': 4.56.0 + '@rollup/rollup-darwin-x64': 4.56.0 + '@rollup/rollup-freebsd-arm64': 4.56.0 + '@rollup/rollup-freebsd-x64': 4.56.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.56.0 + '@rollup/rollup-linux-arm-musleabihf': 4.56.0 + '@rollup/rollup-linux-arm64-gnu': 4.56.0 + '@rollup/rollup-linux-arm64-musl': 4.56.0 + '@rollup/rollup-linux-loong64-gnu': 4.56.0 + '@rollup/rollup-linux-loong64-musl': 4.56.0 + '@rollup/rollup-linux-ppc64-gnu': 4.56.0 + '@rollup/rollup-linux-ppc64-musl': 4.56.0 + '@rollup/rollup-linux-riscv64-gnu': 4.56.0 + '@rollup/rollup-linux-riscv64-musl': 4.56.0 + '@rollup/rollup-linux-s390x-gnu': 4.56.0 + '@rollup/rollup-linux-x64-gnu': 4.56.0 + '@rollup/rollup-linux-x64-musl': 4.56.0 + '@rollup/rollup-openbsd-x64': 4.56.0 + '@rollup/rollup-openharmony-arm64': 4.56.0 + '@rollup/rollup-win32-arm64-msvc': 4.56.0 + '@rollup/rollup-win32-ia32-msvc': 4.56.0 + '@rollup/rollup-win32-x64-gnu': 4.56.0 + '@rollup/rollup-win32-x64-msvc': 4.56.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + screenfull@5.2.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + state-local@1.0.7: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-json-comments@3.1.1: {} + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.3.8(react@18.3.1): + dependencies: + dequal: 2.0.3 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + + tailwind-merge@2.6.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@18.3.27)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 + + use-sidecar@1.1.3(@types/react@18.3.27)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.27 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@5.4.21(@types/node@20.19.30): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.56.0 + optionalDependencies: + '@types/node': 20.19.30 + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zustand@4.5.7(@types/react@18.3.27)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.27 + react: 18.3.1 + + zwitch@2.0.4: {} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 00000000..2e7af2b7 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/web/public/favicon.svg b/web/public/favicon.svg new file mode 100644 index 00000000..59a71a9b --- /dev/null +++ b/web/public/favicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/web/src/App.tsx b/web/src/App.tsx new file mode 100644 index 00000000..026db2a3 --- /dev/null +++ b/web/src/App.tsx @@ -0,0 +1,12 @@ +import { Layout } from '@/components/layout/Layout' +import { ChatView } from '@/components/chat/ChatView' + +function App() { + return ( + + + + ) +} + +export default App diff --git a/web/src/components/chat/ChatInput.tsx b/web/src/components/chat/ChatInput.tsx new file mode 100644 index 00000000..61703697 --- /dev/null +++ b/web/src/components/chat/ChatInput.tsx @@ -0,0 +1,369 @@ +import { Button } from '@/components/ui/button' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Textarea } from '@/components/ui/textarea' +import { applyAtMentionSuggestion, useAtMention } from '@/hooks/useAtMention' +import { useInputHistory } from '@/hooks/useInputHistory' +import { applySlashCommandSuggestion, useSlashCommand } from '@/hooks/useSlashCommand' +import { PermissionModeEnum, useConfigStore, type PermissionMode } from '@/store/ConfigStore' +import { useSessionStore } from '@/store/session' +import { ChevronDown, Paperclip, Send, Square } from 'lucide-react' +import { useCallback, useEffect, useRef, useState } from 'react' +import { SuggestionPopover } from './SuggestionPopover' + +interface ChatInputProps { + onSend: (message: string) => void + onAbort?: () => void + disabled?: boolean + isStreaming?: boolean +} + +const MODES: { value: PermissionMode; label: string }[] = [ + { value: PermissionModeEnum.DEFAULT, label: 'Default' }, + { value: PermissionModeEnum.AUTO_EDIT, label: 'Auto Edit' }, + { value: PermissionModeEnum.PLAN, label: 'Plan' }, + { value: PermissionModeEnum.SPEC, label: 'Spec' }, +] + +export function ChatInput({ onSend, onAbort, disabled, isStreaming }: ChatInputProps) { + const [input, setInput] = useState("") + const [cursorPosition, setCursorPosition] = useState(undefined) + const [modelOpen, setModelOpen] = useState(false) + const [modeOpen, setModeOpen] = useState(false) + const textareaRef = useRef(null) + + const { currentModelId, configuredModels, loadModels, setCurrentModel, currentMode, setMode } = useConfigStore() + const { setMaxContextTokens } = useSessionStore() + + const slashCommand = useSlashCommand(input, cursorPosition) + const atMention = useAtMention(input, cursorPosition) + const inputHistory = useInputHistory() + + const showSlashSuggestions = slashCommand.hasQuery && slashCommand.suggestions.length > 0 + const showAtSuggestions = atMention.hasQuery && atMention.suggestions.length > 0 + const showAnySuggestions = showSlashSuggestions || showAtSuggestions + + useEffect(() => { + loadModels() + }, [loadModels]) + + useEffect(() => { + if (configuredModels.length === 0) return + const modelInfo = configuredModels.find(m => m.id === currentModelId) + if (modelInfo) { + const hasConfiguredTokens = !!modelInfo.maxContextTokens + const maxTokens = modelInfo.maxContextTokens || 128000 + setMaxContextTokens(maxTokens, !hasConfiguredTokens) + } + }, [currentModelId, configuredModels, setMaxContextTokens]) + + const handleSend = useCallback(() => { + if (!input.trim() || disabled) return + inputHistory.addToHistory(input) + onSend(input) + setInput("") + setCursorPosition(undefined) + }, [input, disabled, onSend, inputHistory]) + + const handleSlashSelect = useCallback((index: number) => { + const suggestion = slashCommand.suggestions[index] + if (!suggestion) return + + const { newInput, newCursorPos } = applySlashCommandSuggestion( + input, + slashCommand, + suggestion + ) + setInput(newInput) + setCursorPosition(newCursorPos) + + requestAnimationFrame(() => { + if (textareaRef.current) { + textareaRef.current.focus() + textareaRef.current.setSelectionRange(newCursorPos, newCursorPos) + } + }) + }, [input, slashCommand]) + + const handleAtSelect = useCallback((index: number) => { + const suggestion = atMention.suggestions[index] + if (!suggestion) return + + const { newInput, newCursorPos } = applyAtMentionSuggestion( + input, + atMention, + suggestion + ) + setInput(newInput) + setCursorPosition(newCursorPos) + + requestAnimationFrame(() => { + if (textareaRef.current) { + textareaRef.current.focus() + textareaRef.current.setSelectionRange(newCursorPos, newCursorPos) + } + }) + }, [input, atMention]) + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (showSlashSuggestions) { + if (e.key === 'ArrowDown') { + e.preventDefault() + slashCommand.selectNext() + return + } + if (e.key === 'ArrowUp') { + e.preventDefault() + slashCommand.selectPrevious() + return + } + if (e.key === 'Tab' || (e.key === 'Enter' && !e.shiftKey)) { + e.preventDefault() + handleSlashSelect(slashCommand.selectedIndex) + return + } + if (e.key === 'Escape') { + e.preventDefault() + setCursorPosition(undefined) + return + } + } + + if (showAtSuggestions) { + if (e.key === 'ArrowDown') { + e.preventDefault() + atMention.selectNext() + return + } + if (e.key === 'ArrowUp') { + e.preventDefault() + atMention.selectPrevious() + return + } + if (e.key === 'Tab' || (e.key === 'Enter' && !e.shiftKey)) { + e.preventDefault() + handleAtSelect(atMention.selectedIndex) + return + } + if (e.key === 'Escape') { + e.preventDefault() + setCursorPosition(undefined) + return + } + } + + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSend() + return + } + + if (e.key === 'ArrowUp' && !e.shiftKey) { + const textarea = e.target as HTMLTextAreaElement + const isAtStart = textarea.selectionStart === 0 && textarea.selectionEnd === 0 + const isSingleLine = !input.includes('\n') + + if (isAtStart || isSingleLine) { + const prev = inputHistory.getPrevious(input) + if (prev !== null) { + e.preventDefault() + setInput(prev) + requestAnimationFrame(() => { + if (textareaRef.current) { + textareaRef.current.setSelectionRange(prev.length, prev.length) + } + }) + } + } + return + } + + if (e.key === 'ArrowDown' && !e.shiftKey) { + const textarea = e.target as HTMLTextAreaElement + const isAtEnd = textarea.selectionStart === input.length && textarea.selectionEnd === input.length + const isSingleLine = !input.includes('\n') + + if ((isAtEnd || isSingleLine) && inputHistory.historyIndex !== -1) { + const next = inputHistory.getNext() + if (next !== null) { + e.preventDefault() + setInput(next) + requestAnimationFrame(() => { + if (textareaRef.current) { + textareaRef.current.setSelectionRange(next.length, next.length) + } + }) + } + } + } + }, [ + showSlashSuggestions, + showAtSuggestions, + slashCommand, + atMention, + handleSlashSelect, + handleAtSelect, + handleSend, + input, + inputHistory, + ]) + + const handleInputChange = useCallback((e: React.ChangeEvent) => { + setInput(e.target.value) + setCursorPosition(e.target.selectionStart) + }, []) + + const handleSelect = useCallback((e: React.SyntheticEvent) => { + const target = e.target as HTMLTextAreaElement + setCursorPosition(target.selectionStart) + }, []) + + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'inherit' + textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px` + } + }, [input]) + + const currentModelInfo = configuredModels.find(m => m.id === currentModelId) + const displayModelName = currentModelInfo?.model || currentModelId || 'Select Model' + const currentModeLabel = MODES.find(m => m.value === currentMode)?.label || 'Default' + + return ( +
+
+
+ + + + +