From c5dbc2cb6eb0025211de057fc2b569ff5031b487 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Mon, 20 Apr 2026 21:56:58 -0700 Subject: [PATCH 001/129] Add TypeAgent Shell VS Code extension scaffold Scaffold a VS Code extension that embeds the TypeAgent shell chat: - Extension host: activation, sidebar WebviewViewProvider, editor panel command, agent server lifecycle manager, status bar item - Webview: chat UI (messages, input, status), WebSocket connection to agent server using the same RPC protocol as the shell renderer - Theming: uses VS Code CSS variables for full theme integration - esbuild: dual bundle (extension + webview), watch mode support - Settings: serverUrl, autoStart, serverPort configuration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/.gitignore | 3 + ts/extensions/typeagent-shell/.vscodeignore | 5 + ts/extensions/typeagent-shell/esbuild.mjs | 51 + ts/extensions/typeagent-shell/media/chat.css | 180 + .../typeagent-shell/media/typeagent-icon.svg | 6 + .../typeagent-shell/package-lock.json | 4464 +++++++++++++++++ ts/extensions/typeagent-shell/package.json | 84 + .../typeagent-shell/src/agentServerManager.ts | 206 + .../typeagent-shell/src/chatViewProvider.ts | 113 + .../typeagent-shell/src/extension.ts | 79 + .../src/webview/agentConnection.ts | 287 ++ .../typeagent-shell/src/webview/chatUI.ts | 125 + .../typeagent-shell/src/webview/main.ts | 38 + ts/extensions/typeagent-shell/tsconfig.json | 15 + 14 files changed, 5656 insertions(+) create mode 100644 ts/extensions/typeagent-shell/.gitignore create mode 100644 ts/extensions/typeagent-shell/.vscodeignore create mode 100644 ts/extensions/typeagent-shell/esbuild.mjs create mode 100644 ts/extensions/typeagent-shell/media/chat.css create mode 100644 ts/extensions/typeagent-shell/media/typeagent-icon.svg create mode 100644 ts/extensions/typeagent-shell/package-lock.json create mode 100644 ts/extensions/typeagent-shell/package.json create mode 100644 ts/extensions/typeagent-shell/src/agentServerManager.ts create mode 100644 ts/extensions/typeagent-shell/src/chatViewProvider.ts create mode 100644 ts/extensions/typeagent-shell/src/extension.ts create mode 100644 ts/extensions/typeagent-shell/src/webview/agentConnection.ts create mode 100644 ts/extensions/typeagent-shell/src/webview/chatUI.ts create mode 100644 ts/extensions/typeagent-shell/src/webview/main.ts create mode 100644 ts/extensions/typeagent-shell/tsconfig.json diff --git a/ts/extensions/typeagent-shell/.gitignore b/ts/extensions/typeagent-shell/.gitignore new file mode 100644 index 0000000000..a08e1da2de --- /dev/null +++ b/ts/extensions/typeagent-shell/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.vsix diff --git a/ts/extensions/typeagent-shell/.vscodeignore b/ts/extensions/typeagent-shell/.vscodeignore new file mode 100644 index 0000000000..a04e2bf110 --- /dev/null +++ b/ts/extensions/typeagent-shell/.vscodeignore @@ -0,0 +1,5 @@ +.gitignore +src/** +tsconfig.json +esbuild.mjs +node_modules/** diff --git a/ts/extensions/typeagent-shell/esbuild.mjs b/ts/extensions/typeagent-shell/esbuild.mjs new file mode 100644 index 0000000000..49f6314ecf --- /dev/null +++ b/ts/extensions/typeagent-shell/esbuild.mjs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as esbuild from "esbuild"; + +const watch = process.argv.includes("--watch"); + +/** @type {import('esbuild').BuildOptions} */ +const extensionConfig = { + entryPoints: ["src/extension.ts"], + bundle: true, + outfile: "dist/extension.js", + external: ["vscode"], + format: "cjs", + platform: "node", + target: "node20", + sourcemap: true, + minify: !watch, +}; + +/** @type {import('esbuild').BuildOptions} */ +const webviewConfig = { + entryPoints: ["src/webview/main.ts"], + bundle: true, + outfile: "dist/webview.js", + format: "iife", + platform: "browser", + target: "es2022", + sourcemap: true, + minify: !watch, +}; + +async function build() { + if (watch) { + const extCtx = await esbuild.context(extensionConfig); + const webCtx = await esbuild.context(webviewConfig); + await Promise.all([extCtx.watch(), webCtx.watch()]); + console.log("Watching for changes..."); + } else { + await Promise.all([ + esbuild.build(extensionConfig), + esbuild.build(webviewConfig), + ]); + console.log("Build complete"); + } +} + +build().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css new file mode 100644 index 0000000000..57b1599331 --- /dev/null +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -0,0 +1,180 @@ +/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ + +/* TypeAgent Shell Chat — VS Code Webview Styles + Uses VS Code CSS variables for theme integration. */ + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + color: var(--vscode-foreground); + background: var(--vscode-editor-background); + height: 100vh; + overflow: hidden; +} + +#chat-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +/* ── Status bar ──────────────────────────────────────────────── */ + +.status { + padding: 6px 12px; + font-size: 12px; + font-weight: 500; + flex-shrink: 0; + border-bottom: 1px solid var(--vscode-panel-border); +} + +.status.connected { + background: var(--vscode-testing-iconPassed); + color: var(--vscode-editor-background); +} + +.status.connecting { + background: var(--vscode-editorWarning-foreground); + color: var(--vscode-editor-background); +} + +.status.disconnected { + background: var(--vscode-errorForeground); + color: var(--vscode-editor-background); +} + +/* ── Messages area ───────────────────────────────────────────── */ + +#messages { + flex: 1; + overflow-y: auto; + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.message { + padding: 8px 12px; + border-radius: 8px; + max-width: 85%; + word-wrap: break-word; + white-space: pre-wrap; + line-height: 1.4; +} + +.message.user { + align-self: flex-end; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border-bottom-right-radius: 2px; +} + +.message.agent { + align-self: flex-start; + background: var(--vscode-editor-inactiveSelectionBackground); + color: var(--vscode-foreground); + border-bottom-left-radius: 2px; +} + +.message.agent.partial { + opacity: 0.8; +} + +.message.system { + align-self: center; + font-size: 11px; + color: var(--vscode-descriptionForeground); + font-style: italic; + background: none; + padding: 4px 8px; +} + +/* ── Input area ──────────────────────────────────────────────── */ + +#input-area { + display: flex; + align-items: flex-end; + padding: 8px 12px; + gap: 8px; + border-top: 1px solid var(--vscode-panel-border); + background: var(--vscode-sideBar-background); + flex-shrink: 0; +} + +#chat-input { + flex: 1; + resize: none; + border: 1px solid var(--vscode-input-border); + background: var(--vscode-input-background); + color: var(--vscode-input-foreground); + padding: 6px 10px; + border-radius: 4px; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); + line-height: 1.4; + max-height: 120px; + overflow-y: auto; +} + +#chat-input:focus { + outline: none; + border-color: var(--vscode-focusBorder); +} + +#chat-input:disabled { + opacity: 0.5; +} + +#chat-input::placeholder { + color: var(--vscode-input-placeholderForeground); +} + +#send-btn { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 4px; + width: 32px; + height: 32px; + cursor: pointer; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +#send-btn:hover { + background: var(--vscode-button-hoverBackground); +} + +#send-btn:disabled { + opacity: 0.5; + cursor: default; +} + +/* ── Scrollbar styling ───────────────────────────────────────── */ + +#messages::-webkit-scrollbar { + width: 6px; +} + +#messages::-webkit-scrollbar-track { + background: transparent; +} + +#messages::-webkit-scrollbar-thumb { + background: var(--vscode-scrollbarSlider-background); + border-radius: 3px; +} + +#messages::-webkit-scrollbar-thumb:hover { + background: var(--vscode-scrollbarSlider-hoverBackground); +} diff --git a/ts/extensions/typeagent-shell/media/typeagent-icon.svg b/ts/extensions/typeagent-shell/media/typeagent-icon.svg new file mode 100644 index 0000000000..75950976dd --- /dev/null +++ b/ts/extensions/typeagent-shell/media/typeagent-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ts/extensions/typeagent-shell/package-lock.json b/ts/extensions/typeagent-shell/package-lock.json new file mode 100644 index 0000000000..d3987bf900 --- /dev/null +++ b/ts/extensions/typeagent-shell/package-lock.json @@ -0,0 +1,4464 @@ +{ + "name": "typeagent-shell", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "typeagent-shell", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@types/vscode": "^1.90.0", + "@vscode/vsce": "^3.0.0", + "esbuild": "^0.25.0" + }, + "engines": { + "vscode": "^1.90.0" + } + }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", + "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.17.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^5.5.0", + "@azure/msal-node": "^5.1.0", + "open": "^10.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.7.0.tgz", + "integrity": "sha512-uYbJ0YarxkVGWEq814BysJry/IPvpDNkVKmc2bMZp4G+igUQkJ5nlFirycwPGUeA9ICLQqCxqExCA1Z1E07bPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.0.tgz", + "integrity": "sha512-i3eS/5pmxDbIU/mLMENs88Qg3k6XxqJytJy6PpB7L1tCBjdXHJDadCD3Hu1TyTooe7iQo7CYqbocgL/l/8u90g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.3.tgz", + "integrity": "sha512-LqT8mRZpEils9zGR9eW+Ljqifh2aMA99UF/X0jxIKDYZeHr6onlHwhVP4xHCeLhh55BI63JCbdf1iWJbMh1mPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "16.5.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.4.tgz", + "integrity": "sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.4.tgz", + "integrity": "sha512-D9qJedKBLmAo+kiudop4UKgSxXMi4O8U86KrCidVXZ9RsK0NSVIw6+r2rlMUOExq79iEY81FRENyzmNVRxDBsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/types": "15.5.4", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.18.1", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.4.tgz", + "integrity": "sha512-JyAUd26ll3IFF87LP0uGoa8Tzw5ZKiYvGs6v8jLlzyND1lUYCI4+2oIAslrODLkf0qwoCaJrBQWM3wsw+asVGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.4.tgz", + "integrity": "sha512-5GUagtpQuYcmhlOzBGdmVBvDu5lKgVTjwbxtdfoidN4OIqblIxThJHHjazU+ic+/bCIIzI2JcOjHGSaRmE8Gcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.4.tgz", + "integrity": "sha512-mY28j2U7nrWmZbxyKnRvB8vJxJab4AxqOobLfb6iozrLelJbqxcOTvBQednadWPfAk9XWaZVMqUr9Nird3mutg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.5.4" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vscode": { + "version": "1.116.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz", + "integrity": "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", + "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.1.tgz", + "integrity": "sha512-MPn5p+DoudI+3GfJSpAZZraE1lgLv0LcwbH3+xy7RgEhty3UIkmUMUA+5jPTDaxXae00AnX5u77FxGM8FhfKKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^3.2.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", + "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/azure-devops-node-api": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keytar": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", + "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "3.89.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", + "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", + "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", + "require-from-string": "^2.0.2" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz", + "integrity": "sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + } + } +} diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json new file mode 100644 index 0000000000..5f5cc25b48 --- /dev/null +++ b/ts/extensions/typeagent-shell/package.json @@ -0,0 +1,84 @@ +{ + "name": "typeagent-shell", + "displayName": "TypeAgent Shell", + "description": "Embeds the TypeAgent shell chat into VS Code", + "version": "0.0.1", + "private": true, + "publisher": "typeagent", + "license": "MIT", + "author": "Microsoft", + "homepage": "https://github.com/microsoft/TypeAgent#readme", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/TypeAgent.git", + "directory": "ts/extensions/typeagent-shell" + }, + "engines": { + "vscode": "^1.90.0" + }, + "categories": ["Other"], + "activationEvents": [], + "main": "./dist/extension.js", + "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "typeagent-shell", + "title": "TypeAgent", + "icon": "media/typeagent-icon.svg" + } + ] + }, + "views": { + "typeagent-shell": [ + { + "type": "webview", + "id": "typeagent-shell.chatView", + "name": "Chat" + } + ] + }, + "commands": [ + { + "command": "typeagent-shell.openChat", + "title": "Open Chat in Editor", + "category": "TypeAgent" + }, + { + "command": "typeagent-shell.focusChat", + "title": "Focus Chat", + "category": "TypeAgent" + } + ], + "configuration": { + "title": "TypeAgent Shell", + "properties": { + "typeagent.serverUrl": { + "type": "string", + "default": "ws://localhost:3000", + "description": "WebSocket URL of the TypeAgent agent server" + }, + "typeagent.autoStart": { + "type": "boolean", + "default": true, + "description": "Automatically start the agent server if not running" + }, + "typeagent.serverPort": { + "type": "number", + "default": 3000, + "description": "Port for the agent server when auto-starting" + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "node esbuild.mjs", + "watch": "node esbuild.mjs --watch" + }, + "devDependencies": { + "@types/vscode": "^1.90.0", + "esbuild": "^0.25.0", + "@vscode/vsce": "^3.0.0" + } +} diff --git a/ts/extensions/typeagent-shell/src/agentServerManager.ts b/ts/extensions/typeagent-shell/src/agentServerManager.ts new file mode 100644 index 0000000000..6008ef02ef --- /dev/null +++ b/ts/extensions/typeagent-shell/src/agentServerManager.ts @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from "vscode"; +import { ChildProcess, spawn } from "child_process"; +import * as path from "path"; + +/** + * Manages the TypeAgent server lifecycle: auto-start, health check, + * and connection URL resolution. + */ +export class AgentServerManager implements vscode.Disposable { + private _serverProcess: ChildProcess | undefined; + private _statusBarItem: vscode.StatusBarItem; + private _outputChannel: vscode.OutputChannel; + private _isRunning = false; + + constructor(private readonly _context: vscode.ExtensionContext) { + this._outputChannel = vscode.window.createOutputChannel( + "TypeAgent Server", + ); + this._statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + 50, + ); + this._statusBarItem.command = "typeagent-shell.focusChat"; + this._updateStatus("disconnected"); + this._statusBarItem.show(); + } + + public getServerUrl(): string { + const config = vscode.workspace.getConfiguration("typeagent"); + return config.get("serverUrl", "ws://localhost:3000"); + } + + /** + * Ensure the agent server is running. If auto-start is enabled and + * no server is detected, spawn one as a child process. + */ + public async ensureRunning(): Promise { + if (this._isRunning) { + return; + } + + const url = this.getServerUrl(); + this._updateStatus("connecting"); + + // Probe the server + const isUp = await this._probe(url); + if (isUp) { + this._isRunning = true; + this._updateStatus("connected"); + this._outputChannel.appendLine( + `Agent server already running at ${url}`, + ); + return; + } + + // Auto-start if configured + const config = vscode.workspace.getConfiguration("typeagent"); + if (!config.get("autoStart", true)) { + this._updateStatus("disconnected"); + this._outputChannel.appendLine( + "Agent server not running and auto-start is disabled", + ); + return; + } + + this._startServer(); + } + + private _startServer(): void { + const config = vscode.workspace.getConfiguration("typeagent"); + const port = config.get("serverPort", 3000); + + // Resolve the server entry point relative to the workspace + // The agent server is at ts/packages/agentServer/server/dist/server.js + const workspaceFolder = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + if (!workspaceFolder) { + this._outputChannel.appendLine( + "Cannot auto-start: no workspace folder open", + ); + this._updateStatus("disconnected"); + return; + } + + const serverPath = path.join( + workspaceFolder, + "ts", + "packages", + "agentServer", + "server", + "dist", + "server.js", + ); + + this._outputChannel.appendLine( + `Starting agent server: node ${serverPath} --port ${port}`, + ); + this._updateStatus("connecting"); + + const proc = spawn( + "node", + ["--disable-warning=DEP0190", serverPath, "--port", String(port)], + { + cwd: workspaceFolder, + env: { ...process.env }, + stdio: ["ignore", "pipe", "pipe"], + }, + ); + + proc.stdout?.on("data", (data: Buffer) => { + const text = data.toString(); + this._outputChannel.append(text); + if (text.includes("started at")) { + this._isRunning = true; + this._updateStatus("connected"); + } + }); + + proc.stderr?.on("data", (data: Buffer) => { + this._outputChannel.append(data.toString()); + }); + + proc.on("exit", (code) => { + this._outputChannel.appendLine( + `Agent server exited with code ${code}`, + ); + this._isRunning = false; + this._serverProcess = undefined; + this._updateStatus("disconnected"); + }); + + this._serverProcess = proc; + } + + /** + * Probe the server by attempting a WebSocket connection. + */ + private async _probe(url: string): Promise { + return new Promise((resolve) => { + try { + // Use a simple HTTP fetch to the server's expected port + // Agent server also serves HTTP on the same port + const httpUrl = url + .replace("ws://", "http://") + .replace("wss://", "https://"); + const controller = new AbortController(); + const timeout = setTimeout( + () => controller.abort(), + 2000, + ); + fetch(httpUrl, { signal: controller.signal }) + .then(() => { + clearTimeout(timeout); + resolve(true); + }) + .catch(() => { + clearTimeout(timeout); + resolve(false); + }); + } catch { + resolve(false); + } + }); + } + + private _updateStatus( + state: "connected" | "connecting" | "disconnected", + ): void { + switch (state) { + case "connected": + this._statusBarItem.text = "$(check) TypeAgent"; + this._statusBarItem.tooltip = "TypeAgent server connected"; + this._statusBarItem.backgroundColor = undefined; + break; + case "connecting": + this._statusBarItem.text = "$(sync~spin) TypeAgent"; + this._statusBarItem.tooltip = "Connecting to TypeAgent server…"; + this._statusBarItem.backgroundColor = + new vscode.ThemeColor( + "statusBarItem.warningBackground", + ); + break; + case "disconnected": + this._statusBarItem.text = "$(error) TypeAgent"; + this._statusBarItem.tooltip = + "TypeAgent server disconnected"; + this._statusBarItem.backgroundColor = + new vscode.ThemeColor( + "statusBarItem.errorBackground", + ); + break; + } + } + + public dispose(): void { + if (this._serverProcess) { + this._serverProcess.kill(); + this._serverProcess = undefined; + } + this._statusBarItem.dispose(); + this._outputChannel.dispose(); + } +} diff --git a/ts/extensions/typeagent-shell/src/chatViewProvider.ts b/ts/extensions/typeagent-shell/src/chatViewProvider.ts new file mode 100644 index 0000000000..51219f208e --- /dev/null +++ b/ts/extensions/typeagent-shell/src/chatViewProvider.ts @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from "vscode"; +import { AgentServerManager } from "./agentServerManager"; + +/** + * Provides the chat webview for both the sidebar panel and editor tabs. + */ +export class ChatViewProvider implements vscode.WebviewViewProvider { + public static readonly viewType = "typeagent-shell.chatView"; + + private _sidebarView?: vscode.WebviewView; + + constructor( + private readonly _extensionUri: vscode.Uri, + private readonly _serverManager: AgentServerManager, + ) {} + + public resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken, + ): void { + this._sidebarView = webviewView; + this.resolveWebviewPanel(webviewView.webview); + } + + /** + * Configure a webview (sidebar or editor panel) with the chat UI. + */ + public resolveWebviewPanel(webview: vscode.Webview): void { + webview.options = { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(this._extensionUri, "dist"), + vscode.Uri.joinPath(this._extensionUri, "media"), + ], + }; + + webview.html = this._getHtmlForWebview(webview); + + // Handle messages from the webview + webview.onDidReceiveMessage((message) => { + switch (message.type) { + case "getServerUrl": + webview.postMessage({ + type: "serverUrl", + url: this._serverManager.getServerUrl(), + }); + break; + case "openExternal": + if (message.url) { + vscode.env.openExternal( + vscode.Uri.parse(message.url), + ); + } + break; + case "log": + console.log("[TypeAgent Webview]", message.text); + break; + } + }); + } + + private _getHtmlForWebview(webview: vscode.Webview): string { + const scriptUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "dist", "webview.js"), + ); + const styleUri = webview.asWebviewUri( + vscode.Uri.joinPath(this._extensionUri, "media", "chat.css"), + ); + const nonce = getNonce(); + + return /* html */ ` + + + + + + + TypeAgent Chat + + +
+
Disconnected
+
+
+ + +
+
+ + +`; + } +} + +function getNonce(): string { + let text = ""; + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + for (let i = 0; i < 32; i++) { + text += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return text; +} diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts new file mode 100644 index 0000000000..528adbba4d --- /dev/null +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from "vscode"; +import { ChatViewProvider } from "./chatViewProvider"; +import { AgentServerManager } from "./agentServerManager"; + +let serverManager: AgentServerManager | undefined; + +export function activate(context: vscode.ExtensionContext): void { + serverManager = new AgentServerManager(context); + + const provider = new ChatViewProvider( + context.extensionUri, + serverManager, + ); + + // Sidebar webview provider + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + ChatViewProvider.viewType, + provider, + { webviewOptions: { retainContextWhenHidden: true } }, + ), + ); + + // Command: open chat as an editor tab + context.subscriptions.push( + vscode.commands.registerCommand( + "typeagent-shell.openChat", + () => { + const panel = vscode.window.createWebviewPanel( + "typeagent-shell.chatPanel", + "TypeAgent Chat", + vscode.ViewColumn.Beside, + getWebviewOptions(context.extensionUri), + ); + panel.iconPath = vscode.Uri.joinPath( + context.extensionUri, + "media", + "typeagent-icon.svg", + ); + provider.resolveWebviewPanel(panel.webview); + }, + ), + ); + + // Command: focus the sidebar chat + context.subscriptions.push( + vscode.commands.registerCommand("typeagent-shell.focusChat", () => { + vscode.commands.executeCommand( + "typeagent-shell.chatView.focus", + ); + }), + ); + + // Auto-start server if configured + const config = vscode.workspace.getConfiguration("typeagent"); + if (config.get("autoStart", true)) { + serverManager.ensureRunning(); + } +} + +export function deactivate(): void { + serverManager?.dispose(); +} + +function getWebviewOptions( + extensionUri: vscode.Uri, +): vscode.WebviewOptions & vscode.WebviewPanelOptions { + return { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.joinPath(extensionUri, "dist"), + vscode.Uri.joinPath(extensionUri, "media"), + ], + }; +} diff --git a/ts/extensions/typeagent-shell/src/webview/agentConnection.ts b/ts/extensions/typeagent-shell/src/webview/agentConnection.ts new file mode 100644 index 0000000000..b8d3204074 --- /dev/null +++ b/ts/extensions/typeagent-shell/src/webview/agentConnection.ts @@ -0,0 +1,287 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Agent server WebSocket connection for the VS Code webview. +// Uses the same RPC protocol as the shell renderer's webSocketAPI.ts: +// - dispatcher-rpc-call / dispatcher-rpc-reply for agent commands +// - clientio-rpc-call / clientio-rpc-reply for UI callbacks + +import { ChatUI } from "./chatUI"; + +interface RpcMessage { + name: string; + id: number; + args?: unknown[]; + result?: unknown; + error?: string; +} + +interface ServerMessage { + message: string; + data: RpcMessage; +} + +/** + * Manages the WebSocket connection to the TypeAgent agent server + * and translates RPC messages to/from the chat UI. + */ +export class AgentConnection { + private _ws: WebSocket | undefined; + private _url = ""; + private _rpcId = 0; + private _pendingCalls = new Map< + number, + { resolve: (v: unknown) => void; reject: (e: Error) => void } + >(); + private _reconnectTimer: ReturnType | undefined; + private _keepAliveTimer: ReturnType | undefined; + private _partialText = ""; + + constructor(private readonly _chatUI: ChatUI) {} + + public connect(url: string): void { + this._url = url; + this._chatUI.setStatus("connecting", `Connecting to ${url}…`); + this._doConnect(); + } + + /** + * Send a user request to the agent server via the dispatcher RPC. + */ + public sendRequest(text: string): void { + if (!this._ws || this._ws.readyState !== WebSocket.OPEN) { + this._chatUI.addSystemMessage( + "Not connected to agent server", + ); + return; + } + + this._chatUI.addUserMessage(text); + this._sendDispatcherCall("processCommand", [text]); + } + + // ── WebSocket lifecycle ───────────────────────────────────────── + + private _doConnect(): void { + if (this._reconnectTimer) { + clearTimeout(this._reconnectTimer); + this._reconnectTimer = undefined; + } + + try { + this._ws = new WebSocket(this._url); + } catch (err) { + this._chatUI.setStatus( + "disconnected", + `Failed to connect: ${err}`, + ); + this._scheduleReconnect(); + return; + } + + this._ws.onopen = () => { + this._chatUI.setStatus("connected"); + this._startKeepAlive(); + }; + + this._ws.onmessage = (event) => { + this._handleMessage(event.data as string); + }; + + this._ws.onclose = () => { + this._chatUI.setStatus("disconnected"); + this._stopKeepAlive(); + this._rejectAllPending("Connection closed"); + this._scheduleReconnect(); + }; + + this._ws.onerror = () => { + // onclose will also fire, so just log + console.warn("[AgentConnection] WebSocket error"); + }; + } + + private _scheduleReconnect(): void { + if (this._reconnectTimer) return; + this._reconnectTimer = setTimeout(() => { + this._reconnectTimer = undefined; + this._chatUI.setStatus( + "connecting", + "Reconnecting…", + ); + this._doConnect(); + }, 3000); + } + + private _startKeepAlive(): void { + this._stopKeepAlive(); + this._keepAliveTimer = setInterval(() => { + if (this._ws?.readyState === WebSocket.OPEN) { + this._ws.send( + JSON.stringify({ + source: "vscode-extension", + target: "none", + messageType: "keepAlive", + body: {}, + }), + ); + } + }, 20_000); + } + + private _stopKeepAlive(): void { + if (this._keepAliveTimer) { + clearInterval(this._keepAliveTimer); + this._keepAliveTimer = undefined; + } + } + + // ── RPC protocol ──────────────────────────────────────────────── + + private _sendDispatcherCall( + method: string, + args: unknown[], + ): void { + const id = ++this._rpcId; + const rpcMsg: RpcMessage = { name: method, id, args }; + this._ws?.send( + JSON.stringify({ + message: "dispatcher-rpc-call", + data: rpcMsg, + }), + ); + + // Track for response + new Promise((resolve, reject) => { + this._pendingCalls.set(id, { resolve, reject }); + }).catch(() => { + // Handled when rejected + }); + } + + private _sendClientIOReply(id: number, result: unknown): void { + this._ws?.send( + JSON.stringify({ + message: "clientio-rpc-reply", + data: { id, result }, + }), + ); + } + + private _handleMessage(raw: string): void { + let msg: ServerMessage; + try { + msg = JSON.parse(raw); + } catch { + console.warn("[AgentConnection] Invalid JSON:", raw); + return; + } + + switch (msg.message) { + case "dispatcher-rpc-reply": + this._handleDispatcherReply(msg.data); + break; + case "clientio-rpc-call": + this._handleClientIOCall(msg.data); + break; + case "setting-summary-changed": + // Ignore for now + break; + default: + console.log( + "[AgentConnection] Unhandled message:", + msg.message, + ); + } + } + + private _handleDispatcherReply(data: RpcMessage): void { + const pending = this._pendingCalls.get(data.id); + if (pending) { + this._pendingCalls.delete(data.id); + if (data.error) { + pending.reject(new Error(data.error)); + } else { + pending.resolve(data.result); + } + } + } + + /** + * Handle ClientIO RPC calls from the server — these are the + * display callbacks (setDisplay, appendDisplay, etc.). + */ + private _handleClientIOCall(data: RpcMessage): void { + const args = data.args ?? []; + switch (data.name) { + case "setDisplay": + this._partialText = String(args[0] ?? ""); + this._chatUI.updatePartialMessage(this._partialText); + this._sendClientIOReply(data.id, undefined); + break; + + case "appendDisplay": + this._partialText += String(args[0] ?? ""); + this._chatUI.updatePartialMessage(this._partialText); + this._sendClientIOReply(data.id, undefined); + break; + + case "setDynamicDisplay": + // Dynamic display is ephemeral status text + this._chatUI.updatePartialMessage( + String(args[1] ?? args[0] ?? ""), + ); + this._sendClientIOReply(data.id, undefined); + break; + + case "clear": + this._partialText = ""; + this._chatUI.finalizePartialMessage(); + this._sendClientIOReply(data.id, undefined); + break; + + case "setUserRequest": + // Acknowledge — the user message is already shown + this._sendClientIOReply(data.id, undefined); + break; + + case "question": + // Default to first choice for now + this._sendClientIOReply(data.id, args[3] ?? 0); + break; + + case "proposeAction": + // Auto-accept proposed actions + this._sendClientIOReply(data.id, undefined); + break; + + case "notify": { + const event = args[0] as string; + if ( + event === "showNotifications" || + event === "randomCommandSelected" + ) { + // Finalize any partial message + if (this._partialText) { + this._chatUI.finalizePartialMessage(); + this._partialText = ""; + } + } + this._sendClientIOReply(data.id, undefined); + break; + } + + default: + // Acknowledge unknown calls to prevent server-side timeouts + this._sendClientIOReply(data.id, undefined); + break; + } + } + + private _rejectAllPending(reason: string): void { + for (const [id, pending] of this._pendingCalls) { + pending.reject(new Error(reason)); + this._pendingCalls.delete(id); + } + } +} diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts new file mode 100644 index 0000000000..9334849c98 --- /dev/null +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Manages the chat UI elements in the webview. + */ +export class ChatUI { + private _messagesEl: HTMLElement; + private _inputEl: HTMLTextAreaElement; + private _sendBtn: HTMLButtonElement; + private _statusEl: HTMLElement; + private _sendCallback?: (text: string) => void; + + constructor() { + this._messagesEl = document.getElementById("messages")!; + this._inputEl = document.getElementById( + "chat-input", + ) as HTMLTextAreaElement; + this._sendBtn = document.getElementById( + "send-btn", + ) as HTMLButtonElement; + this._statusEl = document.getElementById("status-bar")!; + + this._sendBtn.addEventListener("click", () => this._handleSend()); + this._inputEl.addEventListener("keydown", (e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + this._handleSend(); + } + }); + + // Auto-resize textarea + this._inputEl.addEventListener("input", () => { + this._inputEl.style.height = "auto"; + this._inputEl.style.height = + Math.min(this._inputEl.scrollHeight, 120) + "px"; + }); + } + + public onSend(callback: (text: string) => void): void { + this._sendCallback = callback; + } + + public addUserMessage(text: string): void { + this._appendMessage(text, "user"); + } + + public addAgentMessage(text: string): void { + this._appendMessage(text, "agent"); + } + + public updatePartialMessage(text: string): void { + let partial = this._messagesEl.querySelector( + ".message.agent.partial", + ); + if (!partial) { + partial = document.createElement("div"); + partial.className = "message agent partial"; + this._messagesEl.appendChild(partial); + } + partial.textContent = text; + this._scrollToBottom(); + } + + public finalizePartialMessage(): void { + const partial = this._messagesEl.querySelector( + ".message.agent.partial", + ); + if (partial) { + partial.classList.remove("partial"); + } + } + + public setStatus( + state: "connected" | "connecting" | "disconnected", + detail?: string, + ): void { + this._statusEl.className = "status " + state; + switch (state) { + case "connected": + this._statusEl.textContent = + detail ?? "Connected to TypeAgent"; + this._inputEl.disabled = false; + this._sendBtn.disabled = false; + break; + case "connecting": + this._statusEl.textContent = detail ?? "Connecting…"; + this._inputEl.disabled = true; + this._sendBtn.disabled = true; + break; + case "disconnected": + this._statusEl.textContent = detail ?? "Disconnected"; + this._inputEl.disabled = true; + this._sendBtn.disabled = true; + break; + } + } + + public addSystemMessage(text: string): void { + this._appendMessage(text, "system"); + } + + private _handleSend(): void { + const text = this._inputEl.value.trim(); + if (!text) return; + this._inputEl.value = ""; + this._inputEl.style.height = "auto"; + this._sendCallback?.(text); + } + + private _appendMessage( + text: string, + role: "user" | "agent" | "system", + ): void { + const el = document.createElement("div"); + el.className = "message " + role; + el.textContent = text; + this._messagesEl.appendChild(el); + this._scrollToBottom(); + } + + private _scrollToBottom(): void { + this._messagesEl.scrollTop = this._messagesEl.scrollHeight; + } +} diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts new file mode 100644 index 0000000000..b1418117fd --- /dev/null +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Webview entry point — runs in the browser sandbox inside VS Code. +// Connects to the TypeAgent agent server via WebSocket and provides +// a chat UI for sending requests and displaying responses. + +import { ChatUI } from "./chatUI"; +import { AgentConnection } from "./agentConnection"; + +declare function acquireVsCodeApi(): { + postMessage(message: unknown): void; + getState(): unknown; + setState(state: unknown): void; +}; + +const vscode = acquireVsCodeApi(); + +const chatUI = new ChatUI(); +const connection = new AgentConnection(chatUI); + +// Request the server URL from the extension host +vscode.postMessage({ type: "getServerUrl" }); + +// Listen for messages from the extension host +window.addEventListener("message", (event) => { + const message = event.data; + switch (message.type) { + case "serverUrl": + connection.connect(message.url); + break; + } +}); + +// Wire up the send button and input +chatUI.onSend((text) => { + connection.sendRequest(text); +}); diff --git a/ts/extensions/typeagent-shell/tsconfig.json b/ts/extensions/typeagent-shell/tsconfig.json new file mode 100644 index 0000000000..00b281f405 --- /dev/null +++ b/ts/extensions/typeagent-shell/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "lib": ["ES2022"], + "sourceMap": true, + "strict": true, + "rootDir": "src", + "outDir": "dist", + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} From ef3da8fe9f53d3b85b7e82289ddda82386f53913 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Mon, 20 Apr 2026 22:56:20 -0700 Subject: [PATCH 002/129] Fix WebSocket RPC protocol for agent server connection Replace the broken raw WebSocket approach with the proper channel-multiplexed RPC protocol. The agent server uses createWebSocketChannelServer with named channels (agent-server, dispatcher:, clientio:). Architecture change: - Extension host (Node.js) now manages the real RPC connection using connectAgentServer() from @typeagent/agent-server-client - Webview communicates with extension host via postMessage bridge - Extension host implements ClientIO and forwards display events to webview Key changes: - New: agentServerBridge.ts - manages RPC connection + webview bridge - Removed: agentServerManager.ts (HTTP probe approach didn't work) - Removed: webview/agentConnection.ts (raw WebSocket didn't match protocol) - Updated: chatViewProvider.ts - uses bridge instead of server manager - Updated: webview/main.ts - uses postMessage instead of direct WebSocket - Updated: chatUI.ts - handles typed display events from the server - Updated: package.json - adds @typeagent/* workspace dependencies - Updated: esbuild.mjs - suppresses import.meta.url warning Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/esbuild.mjs | 5 + ts/extensions/typeagent-shell/media/chat.css | 18 + .../typeagent-shell/package-lock.json | 4112 +---------------- ts/extensions/typeagent-shell/package.json | 18 +- .../typeagent-shell/src/agentServerBridge.ts | 369 ++ .../typeagent-shell/src/agentServerManager.ts | 206 - .../typeagent-shell/src/chatViewProvider.ts | 38 +- .../typeagent-shell/src/extension.ts | 50 +- .../src/webview/agentConnection.ts | 287 -- .../typeagent-shell/src/webview/chatUI.ts | 113 +- .../typeagent-shell/src/webview/main.ts | 53 +- 11 files changed, 749 insertions(+), 4520 deletions(-) create mode 100644 ts/extensions/typeagent-shell/src/agentServerBridge.ts delete mode 100644 ts/extensions/typeagent-shell/src/agentServerManager.ts delete mode 100644 ts/extensions/typeagent-shell/src/webview/agentConnection.ts diff --git a/ts/extensions/typeagent-shell/esbuild.mjs b/ts/extensions/typeagent-shell/esbuild.mjs index 49f6314ecf..1d68be04db 100644 --- a/ts/extensions/typeagent-shell/esbuild.mjs +++ b/ts/extensions/typeagent-shell/esbuild.mjs @@ -16,6 +16,11 @@ const extensionConfig = { target: "node20", sourcemap: true, minify: !watch, + // The agentServerClient uses import.meta.url internally for auto-start + // which we don't use; suppress the warning by defining it away. + define: { + "import.meta.url": "undefined", + }, }; /** @type {import('esbuild').BuildOptions} */ diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 57b1599331..7ca311cf54 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -96,6 +96,24 @@ body { padding: 4px 8px; } +.message.error { + align-self: center; + font-size: 12px; + color: var(--vscode-errorForeground); + background: var(--vscode-inputValidation-errorBackground, rgba(255, 0, 0, 0.1)); + border: 1px solid var(--vscode-inputValidation-errorBorder, var(--vscode-errorForeground)); + padding: 6px 10px; +} + +.source-label { + display: block; + font-size: 10px; + font-weight: 600; + color: var(--vscode-descriptionForeground); + margin-bottom: 2px; + text-transform: uppercase; +} + /* ── Input area ──────────────────────────────────────────────── */ #input-area { diff --git a/ts/extensions/typeagent-shell/package-lock.json b/ts/extensions/typeagent-shell/package-lock.json index d3987bf900..9f1342ab37 100644 --- a/ts/extensions/typeagent-shell/package-lock.json +++ b/ts/extensions/typeagent-shell/package-lock.json @@ -8,224 +8,124 @@ "name": "typeagent-shell", "version": "0.0.1", "license": "MIT", + "dependencies": { + "@typeagent/agent-rpc": "file:../../packages/agentRpc", + "@typeagent/agent-sdk": "file:../../packages/agentSdk", + "@typeagent/agent-server-client": "file:../../packages/agentServer/client", + "@typeagent/agent-server-protocol": "file:../../packages/agentServer/protocol", + "@typeagent/dispatcher-rpc": "file:../../packages/dispatcher/rpc", + "@typeagent/dispatcher-types": "file:../../packages/dispatcher/types", + "debug": "^4.4.0", + "isomorphic-ws": "^5.0.0", + "ws": "^8.18.0" + }, "devDependencies": { + "@types/debug": "^4.1.0", "@types/vscode": "^1.90.0", - "@vscode/vsce": "^3.0.0", + "@types/ws": "^8.5.0", "esbuild": "^0.25.0" }, "engines": { "vscode": "^1.90.0" } }, - "node_modules/@azu/format-text": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", - "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@azu/style-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", - "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "@azu/format-text": "^1.0.1" - } - }, - "node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-auth": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", - "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-util": "^1.13.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-client": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", - "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", - "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-tracing": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", - "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-util": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", - "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", - "dev": true, + "../../packages/agentRpc": { + "name": "@typeagent/agent-rpc", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" + "@typeagent/agent-sdk": "workspace:*", + "@typeagent/common-utils": "workspace:*", + "debug": "^4.4.0" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@types/debug": "^4.1.12", + "@types/jest": "^29.5.7", + "jest": "^29.7.0", + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, - "node_modules/@azure/identity": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", - "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", - "dev": true, + "../../packages/agentSdk": { + "name": "@typeagent/agent-sdk", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.2", - "@azure/core-rest-pipeline": "^1.17.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^5.5.0", - "@azure/msal-node": "^5.1.0", - "open": "^10.1.0", - "tslib": "^2.2.0" + "debug": "^4.4.0", + "type-fest": "^4.39.1" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@types/debug": "^4.1.12", + "@types/jest": "^29.5.7", + "jest": "^29.7.0", + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, - "node_modules/@azure/logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", - "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", - "dev": true, + "../../packages/agentServer/client": { + "name": "@typeagent/agent-server-client", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" + "@typeagent/agent-rpc": "workspace:*", + "@typeagent/agent-server-protocol": "workspace:*", + "@typeagent/dispatcher-rpc": "workspace:*", + "debug": "^4.4.0", + "isomorphic-ws": "^5.0.0" }, - "engines": { - "node": ">=20.0.0" + "devDependencies": { + "@types/debug": "^4.1.12", + "@types/ws": "^8.5.10", + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, - "node_modules/@azure/msal-browser": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.7.0.tgz", - "integrity": "sha512-uYbJ0YarxkVGWEq814BysJry/IPvpDNkVKmc2bMZp4G+igUQkJ5nlFirycwPGUeA9ICLQqCxqExCA1Z1E07bPA==", - "dev": true, + "../../packages/agentServer/protocol": { + "name": "@typeagent/agent-server-protocol", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.0" + "@typeagent/dispatcher-rpc": "workspace:*", + "@typeagent/dispatcher-types": "workspace:*" }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-common": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.5.0.tgz", - "integrity": "sha512-i3eS/5pmxDbIU/mLMENs88Qg3k6XxqJytJy6PpB7L1tCBjdXHJDadCD3Hu1TyTooe7iQo7CYqbocgL/l/8u90g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" + "devDependencies": { + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, - "node_modules/@azure/msal-node": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.1.3.tgz", - "integrity": "sha512-LqT8mRZpEils9zGR9eW+Ljqifh2aMA99UF/X0jxIKDYZeHr6onlHwhVP4xHCeLhh55BI63JCbdf1iWJbMh1mPw==", - "dev": true, + "../../packages/dispatcher/rpc": { + "name": "@typeagent/dispatcher-rpc", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@azure/msal-common": "16.5.0", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" + "@typeagent/agent-rpc": "workspace:*", + "@typeagent/agent-sdk": "workspace:*", + "@typeagent/dispatcher-types": "workspace:*" }, - "engines": { - "node": ">=20" + "devDependencies": { + "@types/jest": "^29.5.7", + "@types/node": "^22.0.0", + "jest": "^29.7.0", + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, + "../../packages/dispatcher/types": { + "name": "@typeagent/dispatcher-types", + "version": "0.0.1", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@typeagent/agent-sdk": "workspace:*" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "devDependencies": { + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" } }, "node_modules/@esbuild/aix-ppc64": { @@ -670,3794 +570,174 @@ "node": ">=18" } }, - "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@typeagent/agent-rpc": { + "resolved": "../../packages/agentRpc", + "link": true }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } + "node_modules/@typeagent/agent-sdk": { + "resolved": "../../packages/agentSdk", + "link": true }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@typeagent/agent-server-client": { + "resolved": "../../packages/agentServer/client", + "link": true }, - "node_modules/@secretlint/config-creator": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", - "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@secretlint/types": "^10.2.2" - }, - "engines": { - "node": ">=20.0.0" - } + "node_modules/@typeagent/agent-server-protocol": { + "resolved": "../../packages/agentServer/protocol", + "link": true }, - "node_modules/@secretlint/config-loader": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", - "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@secretlint/profiler": "^10.2.2", - "@secretlint/resolver": "^10.2.2", - "@secretlint/types": "^10.2.2", - "ajv": "^8.17.1", - "debug": "^4.4.1", - "rc-config-loader": "^4.1.3" - }, - "engines": { - "node": ">=20.0.0" - } + "node_modules/@typeagent/dispatcher-rpc": { + "resolved": "../../packages/dispatcher/rpc", + "link": true }, - "node_modules/@secretlint/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", - "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@secretlint/profiler": "^10.2.2", - "@secretlint/types": "^10.2.2", - "debug": "^4.4.1", - "structured-source": "^4.0.0" - }, - "engines": { - "node": ">=20.0.0" - } + "node_modules/@typeagent/dispatcher-types": { + "resolved": "../../packages/dispatcher/types", + "link": true }, - "node_modules/@secretlint/formatter": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", - "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/resolver": "^10.2.2", - "@secretlint/types": "^10.2.2", - "@textlint/linter-formatter": "^15.2.0", - "@textlint/module-interop": "^15.2.0", - "@textlint/types": "^15.2.0", - "chalk": "^5.4.1", - "debug": "^4.4.1", - "pluralize": "^8.0.0", - "strip-ansi": "^7.1.0", - "table": "^6.9.0", - "terminal-link": "^4.0.0" - }, - "engines": { - "node": ">=20.0.0" + "@types/ms": "*" } }, - "node_modules/@secretlint/formatter/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } + "license": "MIT" }, - "node_modules/@secretlint/node": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", - "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/config-loader": "^10.2.2", - "@secretlint/core": "^10.2.2", - "@secretlint/formatter": "^10.2.2", - "@secretlint/profiler": "^10.2.2", - "@secretlint/source-creator": "^10.2.2", - "@secretlint/types": "^10.2.2", - "debug": "^4.4.1", - "p-map": "^7.0.3" - }, - "engines": { - "node": ">=20.0.0" + "undici-types": "~7.19.0" } }, - "node_modules/@secretlint/profiler": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", - "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", - "dev": true, - "license": "MIT" - }, - "node_modules/@secretlint/resolver": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", - "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "node_modules/@types/vscode": { + "version": "1.116.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz", + "integrity": "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA==", "dev": true, "license": "MIT" }, - "node_modules/@secretlint/secretlint-formatter-sarif": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", - "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { - "node-sarif-builder": "^3.2.0" + "@types/node": "*" } }, - "node_modules/@secretlint/secretlint-rule-no-dotenv": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", - "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", - "dev": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "@secretlint/types": "^10.2.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", - "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@secretlint/source-creator": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", - "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@secretlint/types": "^10.2.2", - "istextorbinary": "^9.5.0" + "node": ">=6.0" }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@secretlint/types": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", - "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">=18" + "bin": { + "esbuild": "bin/esbuild" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@textlint/ast-node-types": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.4.tgz", - "integrity": "sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@textlint/linter-formatter": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.4.tgz", - "integrity": "sha512-D9qJedKBLmAo+kiudop4UKgSxXMi4O8U86KrCidVXZ9RsK0NSVIw6+r2rlMUOExq79iEY81FRENyzmNVRxDBsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@azu/format-text": "^1.0.2", - "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "15.5.4", - "@textlint/resolver": "15.5.4", - "@textlint/types": "15.5.4", - "chalk": "^4.1.2", - "debug": "^4.4.3", - "js-yaml": "^4.1.1", - "lodash": "^4.18.1", - "pluralize": "^2.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "table": "^6.9.0", - "text-table": "^0.2.0" - } - }, - "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/@textlint/linter-formatter/node_modules/pluralize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", - "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=18" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, - "node_modules/@textlint/module-interop": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.4.tgz", - "integrity": "sha512-JyAUd26ll3IFF87LP0uGoa8Tzw5ZKiYvGs6v8jLlzyND1lUYCI4+2oIAslrODLkf0qwoCaJrBQWM3wsw+asVGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@textlint/resolver": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.4.tgz", - "integrity": "sha512-5GUagtpQuYcmhlOzBGdmVBvDu5lKgVTjwbxtdfoidN4OIqblIxThJHHjazU+ic+/bCIIzI2JcOjHGSaRmE8Gcg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@textlint/types": { - "version": "15.5.4", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.4.tgz", - "integrity": "sha512-mY28j2U7nrWmZbxyKnRvB8vJxJab4AxqOobLfb6iozrLelJbqxcOTvBQednadWPfAk9XWaZVMqUr9Nird3mutg==", - "dev": true, + "node_modules/isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", "license": "MIT", - "dependencies": { - "@textlint/ast-node-types": "15.5.4" + "peerDependencies": { + "ws": "*" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sarif": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", - "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", - "dev": true, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/@types/vscode": { - "version": "1.116.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz", - "integrity": "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA==", + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, - "node_modules/@typespec/ts-http-runtime": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz", - "integrity": "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw==", - "dev": true, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@vscode/vsce": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.1.tgz", - "integrity": "sha512-MPn5p+DoudI+3GfJSpAZZraE1lgLv0LcwbH3+xy7RgEhty3UIkmUMUA+5jPTDaxXae00AnX5u77FxGM8FhfKKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@azure/identity": "^4.1.0", - "@secretlint/node": "^10.1.2", - "@secretlint/secretlint-formatter-sarif": "^10.1.2", - "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", - "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", - "@vscode/vsce-sign": "^2.0.0", - "azure-devops-node-api": "^12.5.0", - "chalk": "^4.1.2", - "cheerio": "^1.0.0-rc.9", - "cockatiel": "^3.1.2", - "commander": "^12.1.0", - "form-data": "^4.0.0", - "glob": "^11.0.0", - "hosted-git-info": "^4.0.2", - "jsonc-parser": "^3.2.0", - "leven": "^3.1.0", - "markdown-it": "^14.1.0", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "secretlint": "^10.1.2", - "semver": "^7.5.2", - "tmp": "^0.2.3", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.5.0", - "yauzl": "^3.2.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" + "node": ">=10.0.0" }, - "engines": { - "node": ">= 20" + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" }, - "optionalDependencies": { - "keytar": "^7.7.0" - } - }, - "node_modules/@vscode/vsce-sign": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.9.tgz", - "integrity": "sha512-8IvaRvtFyzUnGGl3f5+1Cnor3LqaUWvhaUjAYO8Y39OUYlOf3cRd+dowuQYLpZcP3uwSG+mURwjEBOSq4SOJ0g==", - "dev": true, - "hasInstallScript": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optionalDependencies": { - "@vscode/vsce-sign-alpine-arm64": "2.0.6", - "@vscode/vsce-sign-alpine-x64": "2.0.6", - "@vscode/vsce-sign-darwin-arm64": "2.0.6", - "@vscode/vsce-sign-darwin-x64": "2.0.6", - "@vscode/vsce-sign-linux-arm": "2.0.6", - "@vscode/vsce-sign-linux-arm64": "2.0.6", - "@vscode/vsce-sign-linux-x64": "2.0.6", - "@vscode/vsce-sign-win32-arm64": "2.0.6", - "@vscode/vsce-sign-win32-x64": "2.0.6" - } - }, - "node_modules/@vscode/vsce-sign-alpine-arm64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", - "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "alpine" - ] - }, - "node_modules/@vscode/vsce-sign-alpine-x64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", - "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "alpine" - ] - }, - "node_modules/@vscode/vsce-sign-darwin-arm64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", - "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@vscode/vsce-sign-darwin-x64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", - "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@vscode/vsce-sign-linux-arm": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", - "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@vscode/vsce-sign-linux-arm64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", - "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@vscode/vsce-sign-linux-x64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", - "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@vscode/vsce-sign-win32-arm64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", - "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@vscode/vsce-sign-win32-x64": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", - "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.txt", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/azure-devops-node-api": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", - "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", - "dev": true, - "license": "MIT", - "dependencies": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/binaryextensions": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", - "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", - "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "editions": "^6.21.0" - }, - "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/boundary": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", - "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "peerDependenciesMeta": { + "bufferutil": { + "optional": true }, - { - "type": "consulting", - "url": "https://feross.org/support" + "utf-8-validate": { + "optional": true } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/cockatiel": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", - "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/editions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", - "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", - "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "version-range": "^4.15.0" - }, - "engines": { - "ecmascript": ">= es5", - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "license": "(MIT OR WTFPL)", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/index-to-position": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istextorbinary": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", - "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", - "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "binaryextensions": "^6.11.0", - "editions": "^6.21.0", - "textextensions": "^6.11.0" - }, - "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/jackspeak": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", - "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^9.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/keytar": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz", - "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-addon-api": "^4.3.0", - "prebuild-install": "^7.0.1" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" - }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz", - "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/node-sarif-builder": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", - "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sarif": "^2.1.7", - "fs-extra": "^11.1.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-semver": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", - "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^5.1.0" - } - }, - "node_modules/parse-semver/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", - "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "optional": true, - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc-config-loader": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", - "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "js-yaml": "^4.1.1", - "json5": "^2.2.3", - "require-from-string": "^2.0.2" - } - }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "mute-stream": "~0.0.4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/secretlint": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", - "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@secretlint/config-creator": "^10.2.2", - "@secretlint/formatter": "^10.2.2", - "@secretlint/node": "^10.2.2", - "@secretlint/profiler": "^10.2.2", - "debug": "^4.4.1", - "globby": "^14.1.0", - "read-pkg": "^9.0.1" - }, - "bin": { - "secretlint": "bin/secretlint.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/structured-source": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", - "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boundary": "^2.0.0" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/terminal-link": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", - "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "supports-hyperlinks": "^3.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/textextensions": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", - "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", - "dev": true, - "license": "Artistic-2.0", - "dependencies": { - "editions": "^6.21.0" - }, - "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.14" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-rest-client": { - "version": "1.8.11", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", - "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/underscore": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", - "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/url-join": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", - "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/version-range": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", - "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", - "dev": true, - "license": "Artistic-2.0", - "engines": { - "node": ">=4" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/yauzl": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz", - "integrity": "sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "pend": "~1.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3" } } } diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 5f5cc25b48..fb85b7ef06 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -60,7 +60,7 @@ }, "typeagent.autoStart": { "type": "boolean", - "default": true, + "default": false, "description": "Automatically start the agent server if not running" }, "typeagent.serverPort": { @@ -76,9 +76,21 @@ "compile": "node esbuild.mjs", "watch": "node esbuild.mjs --watch" }, + "dependencies": { + "@typeagent/agent-server-client": "file:../../packages/agentServer/client", + "@typeagent/agent-server-protocol": "file:../../packages/agentServer/protocol", + "@typeagent/agent-rpc": "file:../../packages/agentRpc", + "@typeagent/agent-sdk": "file:../../packages/agentSdk", + "@typeagent/dispatcher-rpc": "file:../../packages/dispatcher/rpc", + "@typeagent/dispatcher-types": "file:../../packages/dispatcher/types", + "isomorphic-ws": "^5.0.0", + "ws": "^8.18.0", + "debug": "^4.4.0" + }, "devDependencies": { "@types/vscode": "^1.90.0", - "esbuild": "^0.25.0", - "@vscode/vsce": "^3.0.0" + "@types/ws": "^8.5.0", + "@types/debug": "^4.1.0", + "esbuild": "^0.25.0" } } diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts new file mode 100644 index 0000000000..37ae10b50e --- /dev/null +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -0,0 +1,369 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from "vscode"; +import { connectAgentServer } from "@typeagent/agent-server-client"; +import type { + AgentServerConnection, + SessionDispatcher, +} from "@typeagent/agent-server-client"; +import type { ClientIO, Dispatcher } from "@typeagent/dispatcher-rpc/types"; +import type { IAgentMessage, RequestId } from "@typeagent/dispatcher-types"; +import type { DisplayAppendMode, TypeAgentAction } from "@typeagent/agent-sdk"; +import type { TemplateEditConfig } from "@typeagent/dispatcher-types"; +import type { PendingInteractionRequest } from "@typeagent/dispatcher-types"; + +/** + * Messages from extension host → webview + */ +export type BridgeToWebviewMessage = + | { type: "status"; connected: boolean; sessionId?: string } + | { type: "setDisplay"; message: IAgentMessage; seq?: number } + | { + type: "appendDisplay"; + message: IAgentMessage; + mode: DisplayAppendMode; + seq?: number; + } + | { + type: "setDisplayInfo"; + requestId: RequestId; + source: string; + actionIndex?: number; + action?: TypeAgentAction | string[]; + seq?: number; + } + | { + type: "setUserRequest"; + requestId: RequestId; + command: string; + seq?: number; + } + | { type: "clear"; requestId: RequestId } + | { type: "notify"; event: string; data: any; source: string; seq?: number } + | { type: "commandResult"; requestId: string; result: any } + | { type: "error"; message: string }; + +/** + * Messages from webview → extension host + */ +export type BridgeFromWebviewMessage = + | { type: "sendCommand"; command: string; requestId?: string } + | { type: "connect" } + | { type: "disconnect" } + | { type: "getStatus" }; + +/** + * Manages the RPC connection to the agent server from the extension host + * and bridges messages to/from webview panels. + */ +export class AgentServerBridge { + private connection: AgentServerConnection | undefined; + private session: SessionDispatcher | undefined; + private webviews: Set = new Set(); + private statusBarItem: vscode.StatusBarItem; + private isConnected = false; + private reconnectTimer: NodeJS.Timeout | undefined; + + constructor() { + this.statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + 100, + ); + this.statusBarItem.command = "typeagent-shell.focusChat"; + this.updateStatusBar(false); + this.statusBarItem.show(); + } + + /** + * Register a webview to receive messages from the server. + */ + registerWebview(webview: vscode.Webview): vscode.Disposable { + this.webviews.add(webview); + + // Handle messages from the webview + const disposable = webview.onDidReceiveMessage((msg) => + this.handleWebviewMessage(msg, webview), + ); + + // Send current status + this.postToWebview(webview, { + type: "status", + connected: this.isConnected, + sessionId: this.session?.sessionId, + }); + + return { + dispose: () => { + this.webviews.delete(webview); + disposable.dispose(); + }, + }; + } + + /** + * Connect to the agent server. + */ + async connect(): Promise { + if (this.isConnected) { + return; + } + + const config = vscode.workspace.getConfiguration("typeagent"); + const serverUrl = config.get( + "serverUrl", + "ws://localhost:3000", + ); + + try { + this.connection = await connectAgentServer(serverUrl, () => { + // onDisconnect callback + this.isConnected = false; + this.session = undefined; + this.updateStatusBar(false); + this.broadcastToWebviews({ type: "status", connected: false }); + this.scheduleReconnect(); + }); + + // Join the default session with our ClientIO implementation + const clientIO = this.createClientIO(); + this.session = await this.connection.joinSession(clientIO, { + clientType: "extension", + }); + + this.isConnected = true; + this.updateStatusBar(true); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + }); + } catch (e: any) { + const msg = e?.message ?? String(e); + this.broadcastToWebviews({ type: "error", message: msg }); + this.updateStatusBar(false); + this.scheduleReconnect(); + } + } + + /** + * Disconnect from the agent server. + */ + async disconnect(): Promise { + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + if (this.connection) { + await this.connection.close(); + this.connection = undefined; + this.session = undefined; + this.isConnected = false; + this.updateStatusBar(false); + this.broadcastToWebviews({ type: "status", connected: false }); + } + } + + dispose(): void { + this.disconnect(); + this.statusBarItem.dispose(); + } + + private async handleWebviewMessage( + msg: BridgeFromWebviewMessage, + _webview: vscode.Webview, + ): Promise { + switch (msg.type) { + case "sendCommand": + await this.sendCommand(msg.command); + break; + case "connect": + await this.connect(); + break; + case "disconnect": + await this.disconnect(); + break; + case "getStatus": + this.broadcastToWebviews({ + type: "status", + connected: this.isConnected, + sessionId: this.session?.sessionId, + }); + break; + } + } + + private async sendCommand(command: string): Promise { + if (!this.session) { + this.broadcastToWebviews({ + type: "error", + message: "Not connected to agent server", + }); + return; + } + + try { + const result = + await this.session.dispatcher.processCommand(command); + // Result will be communicated through ClientIO callbacks + if (result) { + this.broadcastToWebviews({ + type: "commandResult", + requestId: "", + result, + }); + } + } catch (e: any) { + this.broadcastToWebviews({ + type: "error", + message: e?.message ?? String(e), + }); + } + } + + /** + * Create a ClientIO implementation that forwards calls to the webview. + */ + private createClientIO(): ClientIO { + return { + question: async ( + _requestId: RequestId | undefined, + message: string, + choices: string[], + _defaultId?: number, + _source?: string, + ): Promise => { + // Show VS Code quick pick for questions + const items = choices.map((c, i) => ({ + label: c, + index: i, + })); + const pick = await vscode.window.showQuickPick(items, { + placeHolder: message, + }); + return pick?.index ?? 0; + }, + proposeAction: async ( + _requestId: RequestId, + _actionTemplates: TemplateEditConfig, + _source: string, + ): Promise => { + return undefined; + }, + openLocalView: async () => {}, + closeLocalView: async () => {}, + + // ClientIO call functions (fire-and-forget notifications) + clear: (requestId: RequestId) => { + this.broadcastToWebviews({ type: "clear", requestId }); + }, + exit: (_requestId: RequestId) => { + // No-op in extension context + }, + setUserRequest: ( + requestId: RequestId, + command: string, + seq?: number, + ) => { + this.broadcastToWebviews({ + type: "setUserRequest", + requestId, + command, + seq, + }); + }, + setDisplayInfo: ( + requestId: RequestId, + source: string, + actionIndex?: number, + action?: TypeAgentAction | string[], + seq?: number, + ) => { + this.broadcastToWebviews({ + type: "setDisplayInfo", + requestId, + source, + actionIndex, + action, + seq, + }); + }, + setDisplay: (message: IAgentMessage, seq?: number) => { + this.broadcastToWebviews({ + type: "setDisplay", + message, + seq, + }); + }, + appendDisplay: ( + message: IAgentMessage, + mode: DisplayAppendMode, + seq?: number, + ) => { + this.broadcastToWebviews({ + type: "appendDisplay", + message, + mode, + seq, + }); + }, + appendDiagnosticData: () => {}, + setDynamicDisplay: () => {}, + notify: ( + _notificationId: string | RequestId | undefined, + event: string, + data: any, + source: string, + seq?: number, + ) => { + this.broadcastToWebviews({ + type: "notify", + event, + data, + source, + seq, + }); + }, + requestChoice: () => {}, + requestInteraction: (_interaction: PendingInteractionRequest) => {}, + interactionResolved: () => {}, + interactionCancelled: () => {}, + takeAction: () => {}, + }; + } + + private broadcastToWebviews(msg: BridgeToWebviewMessage): void { + for (const webview of this.webviews) { + this.postToWebview(webview, msg); + } + } + + private postToWebview( + webview: vscode.Webview, + msg: BridgeToWebviewMessage, + ): void { + webview.postMessage(msg); + } + + private updateStatusBar(connected: boolean): void { + if (connected) { + this.statusBarItem.text = "$(plug) TypeAgent: Connected"; + this.statusBarItem.backgroundColor = undefined; + } else { + this.statusBarItem.text = "$(debug-disconnect) TypeAgent: Disconnected"; + this.statusBarItem.backgroundColor = new vscode.ThemeColor( + "statusBarItem.warningBackground", + ); + } + } + + private scheduleReconnect(): void { + if (this.reconnectTimer) { + return; + } + this.reconnectTimer = setTimeout(() => { + this.reconnectTimer = undefined; + this.connect(); + }, 5000); + } +} diff --git a/ts/extensions/typeagent-shell/src/agentServerManager.ts b/ts/extensions/typeagent-shell/src/agentServerManager.ts deleted file mode 100644 index 6008ef02ef..0000000000 --- a/ts/extensions/typeagent-shell/src/agentServerManager.ts +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as vscode from "vscode"; -import { ChildProcess, spawn } from "child_process"; -import * as path from "path"; - -/** - * Manages the TypeAgent server lifecycle: auto-start, health check, - * and connection URL resolution. - */ -export class AgentServerManager implements vscode.Disposable { - private _serverProcess: ChildProcess | undefined; - private _statusBarItem: vscode.StatusBarItem; - private _outputChannel: vscode.OutputChannel; - private _isRunning = false; - - constructor(private readonly _context: vscode.ExtensionContext) { - this._outputChannel = vscode.window.createOutputChannel( - "TypeAgent Server", - ); - this._statusBarItem = vscode.window.createStatusBarItem( - vscode.StatusBarAlignment.Left, - 50, - ); - this._statusBarItem.command = "typeagent-shell.focusChat"; - this._updateStatus("disconnected"); - this._statusBarItem.show(); - } - - public getServerUrl(): string { - const config = vscode.workspace.getConfiguration("typeagent"); - return config.get("serverUrl", "ws://localhost:3000"); - } - - /** - * Ensure the agent server is running. If auto-start is enabled and - * no server is detected, spawn one as a child process. - */ - public async ensureRunning(): Promise { - if (this._isRunning) { - return; - } - - const url = this.getServerUrl(); - this._updateStatus("connecting"); - - // Probe the server - const isUp = await this._probe(url); - if (isUp) { - this._isRunning = true; - this._updateStatus("connected"); - this._outputChannel.appendLine( - `Agent server already running at ${url}`, - ); - return; - } - - // Auto-start if configured - const config = vscode.workspace.getConfiguration("typeagent"); - if (!config.get("autoStart", true)) { - this._updateStatus("disconnected"); - this._outputChannel.appendLine( - "Agent server not running and auto-start is disabled", - ); - return; - } - - this._startServer(); - } - - private _startServer(): void { - const config = vscode.workspace.getConfiguration("typeagent"); - const port = config.get("serverPort", 3000); - - // Resolve the server entry point relative to the workspace - // The agent server is at ts/packages/agentServer/server/dist/server.js - const workspaceFolder = - vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; - if (!workspaceFolder) { - this._outputChannel.appendLine( - "Cannot auto-start: no workspace folder open", - ); - this._updateStatus("disconnected"); - return; - } - - const serverPath = path.join( - workspaceFolder, - "ts", - "packages", - "agentServer", - "server", - "dist", - "server.js", - ); - - this._outputChannel.appendLine( - `Starting agent server: node ${serverPath} --port ${port}`, - ); - this._updateStatus("connecting"); - - const proc = spawn( - "node", - ["--disable-warning=DEP0190", serverPath, "--port", String(port)], - { - cwd: workspaceFolder, - env: { ...process.env }, - stdio: ["ignore", "pipe", "pipe"], - }, - ); - - proc.stdout?.on("data", (data: Buffer) => { - const text = data.toString(); - this._outputChannel.append(text); - if (text.includes("started at")) { - this._isRunning = true; - this._updateStatus("connected"); - } - }); - - proc.stderr?.on("data", (data: Buffer) => { - this._outputChannel.append(data.toString()); - }); - - proc.on("exit", (code) => { - this._outputChannel.appendLine( - `Agent server exited with code ${code}`, - ); - this._isRunning = false; - this._serverProcess = undefined; - this._updateStatus("disconnected"); - }); - - this._serverProcess = proc; - } - - /** - * Probe the server by attempting a WebSocket connection. - */ - private async _probe(url: string): Promise { - return new Promise((resolve) => { - try { - // Use a simple HTTP fetch to the server's expected port - // Agent server also serves HTTP on the same port - const httpUrl = url - .replace("ws://", "http://") - .replace("wss://", "https://"); - const controller = new AbortController(); - const timeout = setTimeout( - () => controller.abort(), - 2000, - ); - fetch(httpUrl, { signal: controller.signal }) - .then(() => { - clearTimeout(timeout); - resolve(true); - }) - .catch(() => { - clearTimeout(timeout); - resolve(false); - }); - } catch { - resolve(false); - } - }); - } - - private _updateStatus( - state: "connected" | "connecting" | "disconnected", - ): void { - switch (state) { - case "connected": - this._statusBarItem.text = "$(check) TypeAgent"; - this._statusBarItem.tooltip = "TypeAgent server connected"; - this._statusBarItem.backgroundColor = undefined; - break; - case "connecting": - this._statusBarItem.text = "$(sync~spin) TypeAgent"; - this._statusBarItem.tooltip = "Connecting to TypeAgent server…"; - this._statusBarItem.backgroundColor = - new vscode.ThemeColor( - "statusBarItem.warningBackground", - ); - break; - case "disconnected": - this._statusBarItem.text = "$(error) TypeAgent"; - this._statusBarItem.tooltip = - "TypeAgent server disconnected"; - this._statusBarItem.backgroundColor = - new vscode.ThemeColor( - "statusBarItem.errorBackground", - ); - break; - } - } - - public dispose(): void { - if (this._serverProcess) { - this._serverProcess.kill(); - this._serverProcess = undefined; - } - this._statusBarItem.dispose(); - this._outputChannel.dispose(); - } -} diff --git a/ts/extensions/typeagent-shell/src/chatViewProvider.ts b/ts/extensions/typeagent-shell/src/chatViewProvider.ts index 51219f208e..06f44117d3 100644 --- a/ts/extensions/typeagent-shell/src/chatViewProvider.ts +++ b/ts/extensions/typeagent-shell/src/chatViewProvider.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import * as vscode from "vscode"; -import { AgentServerManager } from "./agentServerManager"; +import { AgentServerBridge } from "./agentServerBridge"; /** * Provides the chat webview for both the sidebar panel and editor tabs. @@ -14,7 +14,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { constructor( private readonly _extensionUri: vscode.Uri, - private readonly _serverManager: AgentServerManager, + private readonly _bridge: AgentServerBridge, ) {} public resolveWebviewView( @@ -40,27 +40,18 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { webview.html = this._getHtmlForWebview(webview); - // Handle messages from the webview - webview.onDidReceiveMessage((message) => { - switch (message.type) { - case "getServerUrl": - webview.postMessage({ - type: "serverUrl", - url: this._serverManager.getServerUrl(), - }); - break; - case "openExternal": - if (message.url) { - vscode.env.openExternal( - vscode.Uri.parse(message.url), - ); - } - break; - case "log": - console.log("[TypeAgent Webview]", message.text); - break; - } - }); + // Register webview with the bridge — this connects it to the RPC stream + const bridgeDisposable = this._bridge.registerWebview(webview); + + // Auto-connect when webview opens + this._bridge.connect(); + + // Clean up on dispose + if (this._sidebarView) { + this._sidebarView.onDidDispose(() => { + bridgeDisposable.dispose(); + }); + } } private _getHtmlForWebview(webview: vscode.Webview): string { @@ -81,7 +72,6 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}'; - connect-src ws://localhost:* wss://localhost:*; img-src ${webview.cspSource} data:; font-src ${webview.cspSource};"> diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index 528adbba4d..c5ac964840 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -3,17 +3,14 @@ import * as vscode from "vscode"; import { ChatViewProvider } from "./chatViewProvider"; -import { AgentServerManager } from "./agentServerManager"; +import { AgentServerBridge } from "./agentServerBridge"; -let serverManager: AgentServerManager | undefined; +let bridge: AgentServerBridge | undefined; export function activate(context: vscode.ExtensionContext): void { - serverManager = new AgentServerManager(context); + bridge = new AgentServerBridge(); - const provider = new ChatViewProvider( - context.extensionUri, - serverManager, - ); + const provider = new ChatViewProvider(context.extensionUri, bridge); // Sidebar webview provider context.subscriptions.push( @@ -26,23 +23,20 @@ export function activate(context: vscode.ExtensionContext): void { // Command: open chat as an editor tab context.subscriptions.push( - vscode.commands.registerCommand( - "typeagent-shell.openChat", - () => { - const panel = vscode.window.createWebviewPanel( - "typeagent-shell.chatPanel", - "TypeAgent Chat", - vscode.ViewColumn.Beside, - getWebviewOptions(context.extensionUri), - ); - panel.iconPath = vscode.Uri.joinPath( - context.extensionUri, - "media", - "typeagent-icon.svg", - ); - provider.resolveWebviewPanel(panel.webview); - }, - ), + vscode.commands.registerCommand("typeagent-shell.openChat", () => { + const panel = vscode.window.createWebviewPanel( + "typeagent-shell.chatPanel", + "TypeAgent Chat", + vscode.ViewColumn.Beside, + getWebviewOptions(context.extensionUri), + ); + panel.iconPath = vscode.Uri.joinPath( + context.extensionUri, + "media", + "typeagent-icon.svg", + ); + provider.resolveWebviewPanel(panel.webview); + }), ); // Command: focus the sidebar chat @@ -53,16 +47,10 @@ export function activate(context: vscode.ExtensionContext): void { ); }), ); - - // Auto-start server if configured - const config = vscode.workspace.getConfiguration("typeagent"); - if (config.get("autoStart", true)) { - serverManager.ensureRunning(); - } } export function deactivate(): void { - serverManager?.dispose(); + bridge?.dispose(); } function getWebviewOptions( diff --git a/ts/extensions/typeagent-shell/src/webview/agentConnection.ts b/ts/extensions/typeagent-shell/src/webview/agentConnection.ts deleted file mode 100644 index b8d3204074..0000000000 --- a/ts/extensions/typeagent-shell/src/webview/agentConnection.ts +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Agent server WebSocket connection for the VS Code webview. -// Uses the same RPC protocol as the shell renderer's webSocketAPI.ts: -// - dispatcher-rpc-call / dispatcher-rpc-reply for agent commands -// - clientio-rpc-call / clientio-rpc-reply for UI callbacks - -import { ChatUI } from "./chatUI"; - -interface RpcMessage { - name: string; - id: number; - args?: unknown[]; - result?: unknown; - error?: string; -} - -interface ServerMessage { - message: string; - data: RpcMessage; -} - -/** - * Manages the WebSocket connection to the TypeAgent agent server - * and translates RPC messages to/from the chat UI. - */ -export class AgentConnection { - private _ws: WebSocket | undefined; - private _url = ""; - private _rpcId = 0; - private _pendingCalls = new Map< - number, - { resolve: (v: unknown) => void; reject: (e: Error) => void } - >(); - private _reconnectTimer: ReturnType | undefined; - private _keepAliveTimer: ReturnType | undefined; - private _partialText = ""; - - constructor(private readonly _chatUI: ChatUI) {} - - public connect(url: string): void { - this._url = url; - this._chatUI.setStatus("connecting", `Connecting to ${url}…`); - this._doConnect(); - } - - /** - * Send a user request to the agent server via the dispatcher RPC. - */ - public sendRequest(text: string): void { - if (!this._ws || this._ws.readyState !== WebSocket.OPEN) { - this._chatUI.addSystemMessage( - "Not connected to agent server", - ); - return; - } - - this._chatUI.addUserMessage(text); - this._sendDispatcherCall("processCommand", [text]); - } - - // ── WebSocket lifecycle ───────────────────────────────────────── - - private _doConnect(): void { - if (this._reconnectTimer) { - clearTimeout(this._reconnectTimer); - this._reconnectTimer = undefined; - } - - try { - this._ws = new WebSocket(this._url); - } catch (err) { - this._chatUI.setStatus( - "disconnected", - `Failed to connect: ${err}`, - ); - this._scheduleReconnect(); - return; - } - - this._ws.onopen = () => { - this._chatUI.setStatus("connected"); - this._startKeepAlive(); - }; - - this._ws.onmessage = (event) => { - this._handleMessage(event.data as string); - }; - - this._ws.onclose = () => { - this._chatUI.setStatus("disconnected"); - this._stopKeepAlive(); - this._rejectAllPending("Connection closed"); - this._scheduleReconnect(); - }; - - this._ws.onerror = () => { - // onclose will also fire, so just log - console.warn("[AgentConnection] WebSocket error"); - }; - } - - private _scheduleReconnect(): void { - if (this._reconnectTimer) return; - this._reconnectTimer = setTimeout(() => { - this._reconnectTimer = undefined; - this._chatUI.setStatus( - "connecting", - "Reconnecting…", - ); - this._doConnect(); - }, 3000); - } - - private _startKeepAlive(): void { - this._stopKeepAlive(); - this._keepAliveTimer = setInterval(() => { - if (this._ws?.readyState === WebSocket.OPEN) { - this._ws.send( - JSON.stringify({ - source: "vscode-extension", - target: "none", - messageType: "keepAlive", - body: {}, - }), - ); - } - }, 20_000); - } - - private _stopKeepAlive(): void { - if (this._keepAliveTimer) { - clearInterval(this._keepAliveTimer); - this._keepAliveTimer = undefined; - } - } - - // ── RPC protocol ──────────────────────────────────────────────── - - private _sendDispatcherCall( - method: string, - args: unknown[], - ): void { - const id = ++this._rpcId; - const rpcMsg: RpcMessage = { name: method, id, args }; - this._ws?.send( - JSON.stringify({ - message: "dispatcher-rpc-call", - data: rpcMsg, - }), - ); - - // Track for response - new Promise((resolve, reject) => { - this._pendingCalls.set(id, { resolve, reject }); - }).catch(() => { - // Handled when rejected - }); - } - - private _sendClientIOReply(id: number, result: unknown): void { - this._ws?.send( - JSON.stringify({ - message: "clientio-rpc-reply", - data: { id, result }, - }), - ); - } - - private _handleMessage(raw: string): void { - let msg: ServerMessage; - try { - msg = JSON.parse(raw); - } catch { - console.warn("[AgentConnection] Invalid JSON:", raw); - return; - } - - switch (msg.message) { - case "dispatcher-rpc-reply": - this._handleDispatcherReply(msg.data); - break; - case "clientio-rpc-call": - this._handleClientIOCall(msg.data); - break; - case "setting-summary-changed": - // Ignore for now - break; - default: - console.log( - "[AgentConnection] Unhandled message:", - msg.message, - ); - } - } - - private _handleDispatcherReply(data: RpcMessage): void { - const pending = this._pendingCalls.get(data.id); - if (pending) { - this._pendingCalls.delete(data.id); - if (data.error) { - pending.reject(new Error(data.error)); - } else { - pending.resolve(data.result); - } - } - } - - /** - * Handle ClientIO RPC calls from the server — these are the - * display callbacks (setDisplay, appendDisplay, etc.). - */ - private _handleClientIOCall(data: RpcMessage): void { - const args = data.args ?? []; - switch (data.name) { - case "setDisplay": - this._partialText = String(args[0] ?? ""); - this._chatUI.updatePartialMessage(this._partialText); - this._sendClientIOReply(data.id, undefined); - break; - - case "appendDisplay": - this._partialText += String(args[0] ?? ""); - this._chatUI.updatePartialMessage(this._partialText); - this._sendClientIOReply(data.id, undefined); - break; - - case "setDynamicDisplay": - // Dynamic display is ephemeral status text - this._chatUI.updatePartialMessage( - String(args[1] ?? args[0] ?? ""), - ); - this._sendClientIOReply(data.id, undefined); - break; - - case "clear": - this._partialText = ""; - this._chatUI.finalizePartialMessage(); - this._sendClientIOReply(data.id, undefined); - break; - - case "setUserRequest": - // Acknowledge — the user message is already shown - this._sendClientIOReply(data.id, undefined); - break; - - case "question": - // Default to first choice for now - this._sendClientIOReply(data.id, args[3] ?? 0); - break; - - case "proposeAction": - // Auto-accept proposed actions - this._sendClientIOReply(data.id, undefined); - break; - - case "notify": { - const event = args[0] as string; - if ( - event === "showNotifications" || - event === "randomCommandSelected" - ) { - // Finalize any partial message - if (this._partialText) { - this._chatUI.finalizePartialMessage(); - this._partialText = ""; - } - } - this._sendClientIOReply(data.id, undefined); - break; - } - - default: - // Acknowledge unknown calls to prevent server-side timeouts - this._sendClientIOReply(data.id, undefined); - break; - } - } - - private _rejectAllPending(reason: string): void { - for (const [id, pending] of this._pendingCalls) { - pending.reject(new Error(reason)); - this._pendingCalls.delete(id); - } - } -} diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 9334849c98..8962962cd8 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -10,6 +10,7 @@ export class ChatUI { private _sendBtn: HTMLButtonElement; private _statusEl: HTMLElement; private _sendCallback?: (text: string) => void; + private _lastAgentEl?: HTMLElement; constructor() { this._messagesEl = document.getElementById("messages")!; @@ -45,54 +46,86 @@ export class ChatUI { this._appendMessage(text, "user"); } - public addAgentMessage(text: string): void { - this._appendMessage(text, "agent"); + public addAgentMessage(text: string, source?: string): void { + const el = document.createElement("div"); + el.className = "message agent"; + if (source) { + const sourceEl = document.createElement("span"); + sourceEl.className = "source-label"; + sourceEl.textContent = source; + el.appendChild(sourceEl); + } + const content = document.createElement("span"); + content.textContent = text; + el.appendChild(content); + this._messagesEl.appendChild(el); + this._lastAgentEl = el; + this._scrollToBottom(); } - public updatePartialMessage(text: string): void { - let partial = this._messagesEl.querySelector( - ".message.agent.partial", - ); - if (!partial) { - partial = document.createElement("div"); - partial.className = "message agent partial"; - this._messagesEl.appendChild(partial); + public appendAgentMessage( + text: string, + source?: string, + _mode?: string, + ): void { + if (this._lastAgentEl) { + const content = this._lastAgentEl.querySelector( + "span:last-child", + ); + if (content) { + content.textContent += text; + } + } else { + this.addAgentMessage(text, source); } - partial.textContent = text; this._scrollToBottom(); } - public finalizePartialMessage(): void { - const partial = this._messagesEl.querySelector( - ".message.agent.partial", - ); - if (partial) { - partial.classList.remove("partial"); + public setDisplayInfo(source: string, action?: any): void { + // Show which agent is handling the request + if (source) { + const el = document.createElement("div"); + el.className = "message system"; + el.textContent = `[${source}]${action ? " processing..." : ""}`; + this._messagesEl.appendChild(el); + this._scrollToBottom(); } } - public setStatus( - state: "connected" | "connecting" | "disconnected", - detail?: string, - ): void { - this._statusEl.className = "status " + state; - switch (state) { - case "connected": - this._statusEl.textContent = - detail ?? "Connected to TypeAgent"; - this._inputEl.disabled = false; - this._sendBtn.disabled = false; - break; - case "connecting": - this._statusEl.textContent = detail ?? "Connecting…"; - this._inputEl.disabled = true; - this._sendBtn.disabled = true; - break; - case "disconnected": - this._statusEl.textContent = detail ?? "Disconnected"; - this._inputEl.disabled = true; - this._sendBtn.disabled = true; - break; + public clearMessages(): void { + this._messagesEl.innerHTML = ""; + this._lastAgentEl = undefined; + } + + public addNotification(event: string, _data: any, source: string): void { + const el = document.createElement("div"); + el.className = "message system"; + el.textContent = `[${source}] ${event}`; + this._messagesEl.appendChild(el); + this._scrollToBottom(); + } + + public addErrorMessage(text: string): void { + const el = document.createElement("div"); + el.className = "message error"; + el.textContent = `Error: ${text}`; + this._messagesEl.appendChild(el); + this._scrollToBottom(); + } + + public setStatus(connected: boolean, sessionId?: string): void { + if (connected) { + this._statusEl.className = "status connected"; + this._statusEl.textContent = sessionId + ? `Connected (${sessionId.substring(0, 8)}…)` + : "Connected to TypeAgent"; + this._inputEl.disabled = false; + this._sendBtn.disabled = false; + } else { + this._statusEl.className = "status disconnected"; + this._statusEl.textContent = "Disconnected"; + this._inputEl.disabled = true; + this._sendBtn.disabled = true; } } @@ -103,8 +136,10 @@ export class ChatUI { private _handleSend(): void { const text = this._inputEl.value.trim(); if (!text) return; + this.addUserMessage(text); this._inputEl.value = ""; this._inputEl.style.height = "auto"; + this._lastAgentEl = undefined; this._sendCallback?.(text); } diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index b1418117fd..6c58e47748 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -2,11 +2,10 @@ // Licensed under the MIT License. // Webview entry point — runs in the browser sandbox inside VS Code. -// Connects to the TypeAgent agent server via WebSocket and provides -// a chat UI for sending requests and displaying responses. +// Communicates with the extension host via postMessage. +// The extension host manages the actual RPC connection to the agent server. import { ChatUI } from "./chatUI"; -import { AgentConnection } from "./agentConnection"; declare function acquireVsCodeApi(): { postMessage(message: unknown): void; @@ -15,24 +14,50 @@ declare function acquireVsCodeApi(): { }; const vscode = acquireVsCodeApi(); - const chatUI = new ChatUI(); -const connection = new AgentConnection(chatUI); - -// Request the server URL from the extension host -vscode.postMessage({ type: "getServerUrl" }); -// Listen for messages from the extension host +// Listen for messages from the extension host (bridged from agent server) window.addEventListener("message", (event) => { - const message = event.data; - switch (message.type) { - case "serverUrl": - connection.connect(message.url); + const msg = event.data; + switch (msg.type) { + case "status": + chatUI.setStatus(msg.connected, msg.sessionId); + break; + case "setDisplay": + chatUI.addAgentMessage(msg.message.message, msg.message.source); + break; + case "appendDisplay": + chatUI.appendAgentMessage( + msg.message.message, + msg.message.source, + msg.mode, + ); + break; + case "setUserRequest": + chatUI.addUserMessage(msg.command); + break; + case "setDisplayInfo": + chatUI.setDisplayInfo(msg.source, msg.action); + break; + case "clear": + chatUI.clearMessages(); + break; + case "notify": + chatUI.addNotification(msg.event, msg.data, msg.source); + break; + case "error": + chatUI.addErrorMessage(msg.message); + break; + case "commandResult": + // Command completed — could update UI state break; } }); // Wire up the send button and input chatUI.onSend((text) => { - connection.sendRequest(text); + vscode.postMessage({ type: "sendCommand", command: text }); }); + +// Ask the extension host to connect +vscode.postMessage({ type: "connect" }); From b4c5eceea6a48ce1327d0687dc81634e6a1a84d0 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Mon, 20 Apr 2026 23:36:17 -0700 Subject: [PATCH 003/129] Fix chat display: proper DisplayContent rendering, message dedup, and agent progress - Handle DisplayContent types (string, string[], TypedDisplayContent) properly - Use appendDisplay mode 'temporary' for replaceable status messages - Add filter:true to joinSession to only see own request responses - Show user message immediately on send (no lag waiting for server echo) - Add commandComplete event to clean up temporary status on finish - Dedup consecutive identical inline content - Remove debug logging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 17 +- .../typeagent-shell/src/webview/chatUI.ts | 222 ++++++++++++++---- .../typeagent-shell/src/webview/main.ts | 12 +- 3 files changed, 196 insertions(+), 55 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 37ae10b50e..4d8f2f6ffd 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -42,6 +42,7 @@ export type BridgeToWebviewMessage = | { type: "clear"; requestId: RequestId } | { type: "notify"; event: string; data: any; source: string; seq?: number } | { type: "commandResult"; requestId: string; result: any } + | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string }; /** @@ -126,9 +127,11 @@ export class AgentServerBridge { }); // Join the default session with our ClientIO implementation + // filter: true ensures we only see responses to our own requests const clientIO = this.createClientIO(); this.session = await this.connection.joinSession(clientIO, { clientType: "extension", + filter: true, }); this.isConnected = true; @@ -205,14 +208,12 @@ export class AgentServerBridge { try { const result = await this.session.dispatcher.processCommand(command); - // Result will be communicated through ClientIO callbacks - if (result) { - this.broadcastToWebviews({ - type: "commandResult", - requestId: "", - result, - }); - } + // Command finished — tell webview to clean up temporary status + this.broadcastToWebviews({ + type: "commandComplete", + requestId: "", + result: result ?? null, + }); } catch (e: any) { this.broadcastToWebviews({ type: "error", diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 8962962cd8..72e216c4e1 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -3,6 +3,7 @@ /** * Manages the chat UI elements in the webview. + * Groups messages by requestId to match the shell's behavior. */ export class ChatUI { private _messagesEl: HTMLElement; @@ -10,7 +11,13 @@ export class ChatUI { private _sendBtn: HTMLButtonElement; private _statusEl: HTMLElement; private _sendCallback?: (text: string) => void; - private _lastAgentEl?: HTMLElement; + + // Track the active response bubble for setDisplay/appendDisplay + private _activeResponseEl?: HTMLElement; + // Track the status indicator element + private _statusIndicatorEl?: HTMLElement; + // Dedup: track last appended content to avoid duplicates + private _lastAppendedContent?: string; constructor() { this._messagesEl = document.getElementById("messages")!; @@ -43,58 +50,118 @@ export class ChatUI { } public addUserMessage(text: string): void { - this._appendMessage(text, "user"); + this._removeStatusIndicator(); + this._removeTemporary(); + this._activeResponseEl = undefined; + this._lastAppendedContent = undefined; + const el = document.createElement("div"); + el.className = "message user"; + el.textContent = text; + this._messagesEl.appendChild(el); + this._scrollToBottom(); } - public addAgentMessage(text: string, source?: string): void { - const el = document.createElement("div"); - el.className = "message agent"; + /** + * setDisplay: replace the content of the active agent bubble. + */ + public setAgentDisplay(content: any, source?: string): void { + this._removeStatusIndicator(); + this._removeTemporary(); + if (!this._activeResponseEl) { + this._activeResponseEl = this._createAgentBubble(source); + } if (source) { - const sourceEl = document.createElement("span"); - sourceEl.className = "source-label"; - sourceEl.textContent = source; - el.appendChild(sourceEl); + const sourceEl = this._activeResponseEl.querySelector(".source-label"); + if (sourceEl) { + sourceEl.textContent = source; + } + } + const contentEl = + this._activeResponseEl.querySelector(".agent-content"); + if (contentEl) { + contentEl.innerHTML = this._renderDisplayContent(content); } - const content = document.createElement("span"); - content.textContent = text; - el.appendChild(content); - this._messagesEl.appendChild(el); - this._lastAgentEl = el; this._scrollToBottom(); } - public appendAgentMessage( - text: string, + /** + * appendDisplay: append to the active agent bubble. + * mode "temporary" = replaceable status text (e.g. "Translating...") + * mode "inline" / "block" = permanent content + */ + public appendAgentDisplay( + content: any, source?: string, - _mode?: string, + mode?: string, ): void { - if (this._lastAgentEl) { - const content = this._lastAgentEl.querySelector( - "span:last-child", - ); - if (content) { - content.textContent += text; - } - } else { - this.addAgentMessage(text, source); + this._removeStatusIndicator(); + + if (mode === "temporary") { + // Show as a replaceable status line — each new temporary replaces the last + this._removeTemporary(); + const el = document.createElement("div"); + el.className = "message system temporary"; + const text = this._renderDisplayContent(content); + el.innerHTML = text; + this._messagesEl.appendChild(el); + this._scrollToBottom(); + return; + } + + // Permanent content — keep temporary status visible (shows agent progress) + const rendered = this._renderDisplayContent(content); + + // Dedup: skip if this exact content was just appended + if (rendered === this._lastAppendedContent && rendered.length > 0) { + return; + } + this._lastAppendedContent = rendered; + + if (!this._activeResponseEl) { + // Remove temporary when first real content arrives + this._removeTemporary(); + this._activeResponseEl = this._createAgentBubble(source); + } + const contentEl = + this._activeResponseEl.querySelector(".agent-content"); + if (contentEl) { + contentEl.innerHTML += rendered; } this._scrollToBottom(); } + /** + * setDisplayInfo: show which agent is processing (status indicator). + * Replaces previous status indicator rather than accumulating. + */ public setDisplayInfo(source: string, action?: any): void { - // Show which agent is handling the request - if (source) { - const el = document.createElement("div"); - el.className = "message system"; - el.textContent = `[${source}]${action ? " processing..." : ""}`; - this._messagesEl.appendChild(el); - this._scrollToBottom(); + if (!source) return; + + if (!this._statusIndicatorEl) { + this._statusIndicatorEl = document.createElement("div"); + this._statusIndicatorEl.className = "message system status-indicator"; + this._messagesEl.appendChild(this._statusIndicatorEl); } + const label = action ? `[${source}] processing...` : `[${source}] processing...`; + this._statusIndicatorEl.textContent = label; + this._scrollToBottom(); } public clearMessages(): void { this._messagesEl.innerHTML = ""; - this._lastAgentEl = undefined; + this._activeResponseEl = undefined; + this._statusIndicatorEl = undefined; + } + + /** + * Called when a command finishes processing. + * Cleans up temporary status messages. + */ + public onCommandComplete(): void { + this._removeTemporary(); + this._removeStatusIndicator(); + this._activeResponseEl = undefined; + this._lastAppendedContent = undefined; } public addNotification(event: string, _data: any, source: string): void { @@ -130,28 +197,97 @@ export class ChatUI { } public addSystemMessage(text: string): void { - this._appendMessage(text, "system"); + const el = document.createElement("div"); + el.className = "message system"; + el.textContent = text; + this._messagesEl.appendChild(el); + this._scrollToBottom(); } private _handleSend(): void { const text = this._inputEl.value.trim(); if (!text) return; + // Show user message immediately (don't wait for server echo) this.addUserMessage(text); this._inputEl.value = ""; this._inputEl.style.height = "auto"; - this._lastAgentEl = undefined; this._sendCallback?.(text); } - private _appendMessage( - text: string, - role: "user" | "agent" | "system", - ): void { + private _createAgentBubble(source?: string): HTMLElement { const el = document.createElement("div"); - el.className = "message " + role; - el.textContent = text; + el.className = "message agent"; + if (source) { + const sourceEl = document.createElement("span"); + sourceEl.className = "source-label"; + sourceEl.textContent = source; + el.appendChild(sourceEl); + } + const contentEl = document.createElement("span"); + contentEl.className = "agent-content"; + el.appendChild(contentEl); this._messagesEl.appendChild(el); - this._scrollToBottom(); + return el; + } + + private _removeStatusIndicator(): void { + if (this._statusIndicatorEl) { + this._statusIndicatorEl.remove(); + this._statusIndicatorEl = undefined; + } + } + + private _removeTemporary(): void { + const temps = this._messagesEl.querySelectorAll(".temporary"); + temps.forEach((el) => el.remove()); + } + + /** + * Convert DisplayContent (string | string[] | string[][] | TypedDisplayContent) + * into an HTML string for rendering. + */ + private _renderDisplayContent(content: any): string { + if (content === null || content === undefined) { + return ""; + } + if (typeof content === "string") { + return this._escapeHtml(content); + } + // TypedDisplayContent: { type, content, alternates? } + if (typeof content === "object" && !Array.isArray(content)) { + if (content.alternates) { + const textAlt = content.alternates.find( + (a: any) => a.type === "text", + ); + if (textAlt) { + return this._renderDisplayContent(textAlt.content); + } + } + if (content.content !== undefined) { + return this._renderDisplayContent(content.content); + } + return this._escapeHtml(JSON.stringify(content)); + } + // string[] or string[][] + if (Array.isArray(content)) { + if (content.length === 0) return ""; + if (Array.isArray(content[0])) { + return content + .map((row: string[]) => row.join(" | ")) + .map((line: string) => this._escapeHtml(line)) + .join("
"); + } + return content + .map((line: string) => this._escapeHtml(String(line))) + .join("
"); + } + return this._escapeHtml(String(content)); + } + + private _escapeHtml(text: string): string { + const div = document.createElement("div"); + div.textContent = text; + return div.innerHTML; } private _scrollToBottom(): void { diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 6c58e47748..ccef02ade2 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -24,17 +24,17 @@ window.addEventListener("message", (event) => { chatUI.setStatus(msg.connected, msg.sessionId); break; case "setDisplay": - chatUI.addAgentMessage(msg.message.message, msg.message.source); + chatUI.setAgentDisplay(msg.message.message, msg.message.source); break; case "appendDisplay": - chatUI.appendAgentMessage( + chatUI.appendAgentDisplay( msg.message.message, msg.message.source, msg.mode, ); break; case "setUserRequest": - chatUI.addUserMessage(msg.command); + // User message is shown immediately on send — skip server echo break; case "setDisplayInfo": chatUI.setDisplayInfo(msg.source, msg.action); @@ -49,7 +49,11 @@ window.addEventListener("message", (event) => { chatUI.addErrorMessage(msg.message); break; case "commandResult": - // Command completed — could update UI state + // Legacy — no-op + break; + case "commandComplete": + // Command finished — clean up any remaining temporary status + chatUI.onCommandComplete(); break; } }); From 3e1f286b541f397f123f715ce591225e760a41e0 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 10:32:39 -0700 Subject: [PATCH 004/129] Add session management commands - Switch Session: QuickPick to list and switch between sessions - New Session: Create a named session and switch to it - Rename Session: Rename the current session - Delete Session: Pick and delete a non-current session (with confirmation) - Status bar shows session name instead of truncated ID - Chat clears and shows notification on session switch - Commands registered in VS Code command palette (TypeAgent: *) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 20 ++ .../typeagent-shell/src/agentServerBridge.ts | 177 +++++++++++++++++- .../typeagent-shell/src/extension.ts | 20 ++ .../typeagent-shell/src/webview/chatUI.ts | 19 +- .../typeagent-shell/src/webview/main.ts | 5 +- 5 files changed, 236 insertions(+), 5 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index fb85b7ef06..748d2ed7a4 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -48,6 +48,26 @@ "command": "typeagent-shell.focusChat", "title": "Focus Chat", "category": "TypeAgent" + }, + { + "command": "typeagent-shell.switchSession", + "title": "Switch Session", + "category": "TypeAgent" + }, + { + "command": "typeagent-shell.newSession", + "title": "New Session", + "category": "TypeAgent" + }, + { + "command": "typeagent-shell.renameSession", + "title": "Rename Session", + "category": "TypeAgent" + }, + { + "command": "typeagent-shell.deleteSession", + "title": "Delete Session", + "category": "TypeAgent" } ], "configuration": { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 4d8f2f6ffd..36028fdbb5 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -12,12 +12,14 @@ import type { IAgentMessage, RequestId } from "@typeagent/dispatcher-types"; import type { DisplayAppendMode, TypeAgentAction } from "@typeagent/agent-sdk"; import type { TemplateEditConfig } from "@typeagent/dispatcher-types"; import type { PendingInteractionRequest } from "@typeagent/dispatcher-types"; +import type { SessionInfo } from "@typeagent/agent-server-protocol"; /** * Messages from extension host → webview */ export type BridgeToWebviewMessage = - | { type: "status"; connected: boolean; sessionId?: string } + | { type: "status"; connected: boolean; sessionId?: string; sessionName?: string } + | { type: "sessionChanged"; sessionId: string; sessionName: string } | { type: "setDisplay"; message: IAgentMessage; seq?: number } | { type: "appendDisplay"; @@ -140,6 +142,7 @@ export class AgentServerBridge { type: "status", connected: true, sessionId: this.session.sessionId, + sessionName: this.session.name, }); } catch (e: any) { const msg = e?.message ?? String(e); @@ -172,6 +175,178 @@ export class AgentServerBridge { this.statusBarItem.dispose(); } + // ── Session management ────────────────────────────────────── + + /** + * Show a QuickPick to switch sessions. + */ + async switchSession(): Promise { + if (!this.connection) { + vscode.window.showWarningMessage("Not connected to agent server."); + return; + } + + const sessions = await this.connection.listSessions(); + const currentId = this.session?.sessionId; + + const items = sessions.map((s) => ({ + label: s.name || s.sessionId.substring(0, 8), + description: s.sessionId === currentId ? "(current)" : "", + detail: `ID: ${s.sessionId} · Clients: ${s.clientCount}`, + sessionId: s.sessionId, + })); + + const pick = await vscode.window.showQuickPick(items, { + placeHolder: "Select a session to switch to", + }); + + if (!pick || pick.sessionId === currentId) { + return; + } + + await this.joinSpecificSession(pick.sessionId); + } + + /** + * Create a new session and switch to it. + */ + async newSession(): Promise { + if (!this.connection) { + vscode.window.showWarningMessage("Not connected to agent server."); + return; + } + + const name = await vscode.window.showInputBox({ + prompt: "Name for the new session", + placeHolder: "My Session", + }); + + if (!name) { + return; + } + + const info = await this.connection.createSession(name); + await this.joinSpecificSession(info.sessionId); + vscode.window.showInformationMessage( + `Created and joined session "${name}"`, + ); + } + + /** + * Rename the current session. + */ + async renameCurrentSession(): Promise { + if (!this.connection || !this.session) { + vscode.window.showWarningMessage("No active session."); + return; + } + + const newName = await vscode.window.showInputBox({ + prompt: "New name for the current session", + placeHolder: "My Session", + }); + + if (!newName) { + return; + } + + await this.connection.renameSession(this.session.sessionId, newName); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: newName, + }); + vscode.window.showInformationMessage( + `Session renamed to "${newName}"`, + ); + } + + /** + * Delete a session (shows picker, prevents deleting current). + */ + async deleteSession(): Promise { + if (!this.connection) { + vscode.window.showWarningMessage("Not connected to agent server."); + return; + } + + const sessions = await this.connection.listSessions(); + const currentId = this.session?.sessionId; + + const items = sessions + .filter((s) => s.sessionId !== currentId) + .map((s) => ({ + label: s.name || s.sessionId.substring(0, 8), + detail: `ID: ${s.sessionId}`, + sessionId: s.sessionId, + })); + + if (items.length === 0) { + vscode.window.showInformationMessage( + "No other sessions to delete.", + ); + return; + } + + const pick = await vscode.window.showQuickPick(items, { + placeHolder: "Select a session to delete", + }); + + if (!pick) { + return; + } + + const confirm = await vscode.window.showWarningMessage( + `Delete session "${pick.label}"?`, + { modal: true }, + "Delete", + ); + + if (confirm === "Delete") { + await this.connection.deleteSession(pick.sessionId); + vscode.window.showInformationMessage( + `Deleted session "${pick.label}"`, + ); + } + } + + /** + * Leave the current session and join a different one. + */ + private async joinSpecificSession(sessionId: string): Promise { + if (!this.connection) { + return; + } + + // Leave current session + if (this.session) { + await this.connection.leaveSession(this.session.sessionId); + this.session = undefined; + } + + // Join the new session + const clientIO = this.createClientIO(); + this.session = await this.connection.joinSession(clientIO, { + clientType: "extension", + filter: true, + sessionId, + }); + + // Notify webviews + this.broadcastToWebviews({ + type: "sessionChanged", + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); + } + private async handleWebviewMessage( msg: BridgeFromWebviewMessage, _webview: vscode.Webview, diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index c5ac964840..ef5b733663 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -47,6 +47,26 @@ export function activate(context: vscode.ExtensionContext): void { ); }), ); + + // Session management commands + context.subscriptions.push( + vscode.commands.registerCommand( + "typeagent-shell.switchSession", + () => bridge?.switchSession(), + ), + vscode.commands.registerCommand( + "typeagent-shell.newSession", + () => bridge?.newSession(), + ), + vscode.commands.registerCommand( + "typeagent-shell.renameSession", + () => bridge?.renameCurrentSession(), + ), + vscode.commands.registerCommand( + "typeagent-shell.deleteSession", + () => bridge?.deleteSession(), + ), + ); } export function deactivate(): void { diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 72e216c4e1..36863d8a02 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -180,11 +180,16 @@ export class ChatUI { this._scrollToBottom(); } - public setStatus(connected: boolean, sessionId?: string): void { + public setStatus( + connected: boolean, + sessionId?: string, + sessionName?: string, + ): void { if (connected) { this._statusEl.className = "status connected"; - this._statusEl.textContent = sessionId - ? `Connected (${sessionId.substring(0, 8)}…)` + const label = sessionName || sessionId?.substring(0, 8) || ""; + this._statusEl.textContent = label + ? `Connected · ${label}` : "Connected to TypeAgent"; this._inputEl.disabled = false; this._sendBtn.disabled = false; @@ -196,6 +201,14 @@ export class ChatUI { } } + /** + * Called when the user switches to a different session. + */ + public onSessionChanged(sessionName: string): void { + this.clearMessages(); + this.addSystemMessage(`Switched to session: ${sessionName}`); + } + public addSystemMessage(text: string): void { const el = document.createElement("div"); el.className = "message system"; diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index ccef02ade2..285504c9a5 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -21,7 +21,10 @@ window.addEventListener("message", (event) => { const msg = event.data; switch (msg.type) { case "status": - chatUI.setStatus(msg.connected, msg.sessionId); + chatUI.setStatus(msg.connected, msg.sessionId, msg.sessionName); + break; + case "sessionChanged": + chatUI.onSessionChanged(msg.sessionName); break; case "setDisplay": chatUI.setAgentDisplay(msg.message.message, msg.message.source); From ece0c5fb53f4bc5b59432aba0c5f314fa96ba37e Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 14:09:50 -0700 Subject: [PATCH 005/129] Fix session switch channel error and prevent duplicate session names - Add reconnectAndJoin fallback when channel conflicts occur during session switch - Validate session names for uniqueness (case-insensitive) on create and rename - Trim whitespace from session names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 109 ++++++++++++++++-- 1 file changed, 101 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 36028fdbb5..3b316a9bae 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -216,19 +216,33 @@ export class AgentServerBridge { return; } + const sessions = await this.connection.listSessions(); + const existingNames = new Set( + sessions.map((s) => s.name.toLowerCase()), + ); + const name = await vscode.window.showInputBox({ prompt: "Name for the new session", placeHolder: "My Session", + validateInput: (value) => { + if (!value.trim()) { + return "Session name cannot be empty"; + } + if (existingNames.has(value.trim().toLowerCase())) { + return `A session named "${value.trim()}" already exists`; + } + return undefined; + }, }); if (!name) { return; } - const info = await this.connection.createSession(name); + const info = await this.connection.createSession(name.trim()); await this.joinSpecificSession(info.sessionId); vscode.window.showInformationMessage( - `Created and joined session "${name}"`, + `Created and joined session "${name.trim()}"`, ); } @@ -241,24 +255,43 @@ export class AgentServerBridge { return; } + const sessions = await this.connection.listSessions(); + const existingNames = new Set( + sessions + .filter((s) => s.sessionId !== this.session!.sessionId) + .map((s) => s.name.toLowerCase()), + ); + const newName = await vscode.window.showInputBox({ prompt: "New name for the current session", placeHolder: "My Session", + validateInput: (value) => { + if (!value.trim()) { + return "Session name cannot be empty"; + } + if (existingNames.has(value.trim().toLowerCase())) { + return `A session named "${value.trim()}" already exists`; + } + return undefined; + }, }); if (!newName) { return; } - await this.connection.renameSession(this.session.sessionId, newName); + await this.connection.renameSession( + this.session.sessionId, + newName.trim(), + ); this.broadcastToWebviews({ type: "status", connected: true, sessionId: this.session.sessionId, - sessionName: newName, + sessionName: newName.trim(), }); vscode.window.showInformationMessage( - `Session renamed to "${newName}"`, + `Session renamed to "${newName.trim()}"`, ); } @@ -319,13 +352,72 @@ export class AgentServerBridge { return; } - // Leave current session + // Leave current session (cleans up its channels) if (this.session) { - await this.connection.leaveSession(this.session.sessionId); + try { + await this.connection.leaveSession(this.session.sessionId); + } catch { + // Best effort — session may already be gone + } this.session = undefined; } // Join the new session + try { + const clientIO = this.createClientIO(); + this.session = await this.connection.joinSession(clientIO, { + clientType: "extension", + filter: true, + sessionId, + }); + + // Notify webviews + this.broadcastToWebviews({ + type: "sessionChanged", + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); + } catch (e: any) { + // Channel conflict — reconnect from scratch + const msg = e?.message ?? String(e); + if (msg.includes("already exists")) { + await this.reconnectAndJoin(sessionId); + } else { + throw e; + } + } + } + + /** + * Full reconnect to resolve channel conflicts, then join a session. + */ + private async reconnectAndJoin(sessionId: string): Promise { + if (this.connection) { + await this.connection.close(); + this.connection = undefined; + this.session = undefined; + } + + const config = vscode.workspace.getConfiguration("typeagent"); + const serverUrl = config.get( + "serverUrl", + "ws://localhost:3000", + ); + + this.connection = await connectAgentServer(serverUrl, () => { + this.isConnected = false; + this.session = undefined; + this.updateStatusBar(false); + this.broadcastToWebviews({ type: "status", connected: false }); + this.scheduleReconnect(); + }); + const clientIO = this.createClientIO(); this.session = await this.connection.joinSession(clientIO, { clientType: "extension", @@ -333,7 +425,8 @@ export class AgentServerBridge { sessionId, }); - // Notify webviews + this.isConnected = true; + this.updateStatusBar(true); this.broadcastToWebviews({ type: "sessionChanged", sessionId: this.session.sessionId, From 02cd582a1f6f7f95a1e5f709abf3a6633c558468 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 14:18:15 -0700 Subject: [PATCH 006/129] Rename session to conversation in user-facing text Align extension UI with the rest of the project's terminology change from 'session' to 'conversation'. Internal API identifiers (command IDs, message types, SessionInfo fields) remain unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 8 ++-- .../typeagent-shell/src/agentServerBridge.ts | 44 +++++++++---------- .../typeagent-shell/src/extension.ts | 2 +- .../typeagent-shell/src/webview/chatUI.ts | 4 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 748d2ed7a4..fc817bcc11 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -51,22 +51,22 @@ }, { "command": "typeagent-shell.switchSession", - "title": "Switch Session", + "title": "Switch Conversation", "category": "TypeAgent" }, { "command": "typeagent-shell.newSession", - "title": "New Session", + "title": "New Conversation", "category": "TypeAgent" }, { "command": "typeagent-shell.renameSession", - "title": "Rename Session", + "title": "Rename Conversation", "category": "TypeAgent" }, { "command": "typeagent-shell.deleteSession", - "title": "Delete Session", + "title": "Delete Conversation", "category": "TypeAgent" } ], diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 3b316a9bae..9cac00045b 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -175,10 +175,10 @@ export class AgentServerBridge { this.statusBarItem.dispose(); } - // ── Session management ────────────────────────────────────── + // ── Conversation management ───────────────────────────────── /** - * Show a QuickPick to switch sessions. + * Show a QuickPick to switch conversations. */ async switchSession(): Promise { if (!this.connection) { @@ -197,7 +197,7 @@ export class AgentServerBridge { })); const pick = await vscode.window.showQuickPick(items, { - placeHolder: "Select a session to switch to", + placeHolder: "Select a conversation to switch to", }); if (!pick || pick.sessionId === currentId) { @@ -208,7 +208,7 @@ export class AgentServerBridge { } /** - * Create a new session and switch to it. + * Create a new conversation and switch to it. */ async newSession(): Promise { if (!this.connection) { @@ -222,14 +222,14 @@ export class AgentServerBridge { ); const name = await vscode.window.showInputBox({ - prompt: "Name for the new session", - placeHolder: "My Session", + prompt: "Name for the new conversation", + placeHolder: "My Conversation", validateInput: (value) => { if (!value.trim()) { - return "Session name cannot be empty"; + return "Conversation name cannot be empty"; } if (existingNames.has(value.trim().toLowerCase())) { - return `A session named "${value.trim()}" already exists`; + return `A conversation named "${value.trim()}" already exists`; } return undefined; }, @@ -242,16 +242,16 @@ export class AgentServerBridge { const info = await this.connection.createSession(name.trim()); await this.joinSpecificSession(info.sessionId); vscode.window.showInformationMessage( - `Created and joined session "${name.trim()}"`, + `Created and switched to conversation "${name.trim()}"`, ); } /** - * Rename the current session. + * Rename the current conversation. */ async renameCurrentSession(): Promise { if (!this.connection || !this.session) { - vscode.window.showWarningMessage("No active session."); + vscode.window.showWarningMessage("No active conversation."); return; } @@ -263,14 +263,14 @@ export class AgentServerBridge { ); const newName = await vscode.window.showInputBox({ - prompt: "New name for the current session", - placeHolder: "My Session", + prompt: "New name for the current conversation", + placeHolder: "My Conversation", validateInput: (value) => { if (!value.trim()) { - return "Session name cannot be empty"; + return "Conversation name cannot be empty"; } if (existingNames.has(value.trim().toLowerCase())) { - return `A session named "${value.trim()}" already exists`; + return `A conversation named "${value.trim()}" already exists`; } return undefined; }, @@ -291,12 +291,12 @@ export class AgentServerBridge { sessionName: newName.trim(), }); vscode.window.showInformationMessage( - `Session renamed to "${newName.trim()}"`, + `Renamed conversation to "${newName.trim()}"`, ); } /** - * Delete a session (shows picker, prevents deleting current). + * Delete a conversation (shows picker, prevents deleting current). */ async deleteSession(): Promise { if (!this.connection) { @@ -317,13 +317,13 @@ export class AgentServerBridge { if (items.length === 0) { vscode.window.showInformationMessage( - "No other sessions to delete.", + "No other conversations to delete.", ); return; } const pick = await vscode.window.showQuickPick(items, { - placeHolder: "Select a session to delete", + placeHolder: "Select a conversation to delete", }); if (!pick) { @@ -331,7 +331,7 @@ export class AgentServerBridge { } const confirm = await vscode.window.showWarningMessage( - `Delete session "${pick.label}"?`, + `Delete conversation "${pick.label}"?`, { modal: true }, "Delete", ); @@ -339,13 +339,13 @@ export class AgentServerBridge { if (confirm === "Delete") { await this.connection.deleteSession(pick.sessionId); vscode.window.showInformationMessage( - `Deleted session "${pick.label}"`, + `Deleted conversation "${pick.label}"`, ); } } /** - * Leave the current session and join a different one. + * Leave the current conversation and join a different one. */ private async joinSpecificSession(sessionId: string): Promise { if (!this.connection) { diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index ef5b733663..bd1fdea293 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -48,7 +48,7 @@ export function activate(context: vscode.ExtensionContext): void { }), ); - // Session management commands + // Conversation management commands context.subscriptions.push( vscode.commands.registerCommand( "typeagent-shell.switchSession", diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 36863d8a02..5bee40076f 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -202,11 +202,11 @@ export class ChatUI { } /** - * Called when the user switches to a different session. + * Called when the user switches to a different conversation. */ public onSessionChanged(sessionName: string): void { this.clearMessages(); - this.addSystemMessage(`Switched to session: ${sessionName}`); + this.addSystemMessage(`Switched to conversation: ${sessionName}`); } public addSystemMessage(text: string): void { From 14fb03ef3d35f3f8e1b42982ec20ad43c43a3539 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 14:29:34 -0700 Subject: [PATCH 007/129] Fix slow conversation switch and suppress disconnect error - Add isSwitching flag to suppress disconnect handler during intentional reconnects (prevents 'Agent channel disconnected' error surfacing) - Cancel pending auto-reconnect timers in reconnectAndJoin - Move webview notifications to after the try/finally so they always fire regardless of which code path (direct join or reconnect) succeeded Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 102 ++++++++++-------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 9cac00045b..3a424059c9 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -67,6 +67,8 @@ export class AgentServerBridge { private statusBarItem: vscode.StatusBarItem; private isConnected = false; private reconnectTimer: NodeJS.Timeout | undefined; + // Suppress disconnect handler during intentional reconnects + private isSwitching = false; constructor() { this.statusBarItem = vscode.window.createStatusBarItem( @@ -120,7 +122,10 @@ export class AgentServerBridge { try { this.connection = await connectAgentServer(serverUrl, () => { - // onDisconnect callback + // onDisconnect callback — ignore during intentional reconnects + if (this.isSwitching) { + return; + } this.isConnected = false; this.session = undefined; this.updateStatusBar(false); @@ -352,45 +357,52 @@ export class AgentServerBridge { return; } - // Leave current session (cleans up its channels) - if (this.session) { - try { - await this.connection.leaveSession(this.session.sessionId); - } catch { - // Best effort — session may already be gone + this.isSwitching = true; + try { + // Leave current session (cleans up its channels) + if (this.session) { + try { + await this.connection.leaveSession(this.session.sessionId); + } catch { + // Best effort — session may already be gone + } + this.session = undefined; } - this.session = undefined; - } - // Join the new session - try { - const clientIO = this.createClientIO(); - this.session = await this.connection.joinSession(clientIO, { - clientType: "extension", - filter: true, - sessionId, - }); + // Join the new session + try { + const clientIO = this.createClientIO(); + this.session = await this.connection.joinSession(clientIO, { + clientType: "extension", + filter: true, + sessionId, + }); + } catch (e: any) { + // Channel conflict — reconnect from scratch + const msg = e?.message ?? String(e); + if (msg.includes("already exists")) { + await this.reconnectAndJoin(sessionId); + } else { + throw e; + } + } // Notify webviews - this.broadcastToWebviews({ - type: "sessionChanged", - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); - this.broadcastToWebviews({ - type: "status", - connected: true, - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); - } catch (e: any) { - // Channel conflict — reconnect from scratch - const msg = e?.message ?? String(e); - if (msg.includes("already exists")) { - await this.reconnectAndJoin(sessionId); - } else { - throw e; + if (this.session) { + this.broadcastToWebviews({ + type: "sessionChanged", + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: this.session.name, + }); } + } finally { + this.isSwitching = false; } } @@ -398,6 +410,12 @@ export class AgentServerBridge { * Full reconnect to resolve channel conflicts, then join a session. */ private async reconnectAndJoin(sessionId: string): Promise { + // Cancel any pending auto-reconnect + if (this.reconnectTimer) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = undefined; + } + if (this.connection) { await this.connection.close(); this.connection = undefined; @@ -411,6 +429,9 @@ export class AgentServerBridge { ); this.connection = await connectAgentServer(serverUrl, () => { + if (this.isSwitching) { + return; + } this.isConnected = false; this.session = undefined; this.updateStatusBar(false); @@ -427,17 +448,6 @@ export class AgentServerBridge { this.isConnected = true; this.updateStatusBar(true); - this.broadcastToWebviews({ - type: "sessionChanged", - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); - this.broadcastToWebviews({ - type: "status", - connected: true, - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); } private async handleWebviewMessage( From ff8e7b32489d2ed23d547248b4a8f9be3717fcc4 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 16:06:09 -0700 Subject: [PATCH 008/129] Update default agent server port from 3000 to 8999 The agent server's default port changed to 8999 (see server.ts). Update extension defaults to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 4 ++-- ts/extensions/typeagent-shell/src/agentServerBridge.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index fc817bcc11..e951623621 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -75,7 +75,7 @@ "properties": { "typeagent.serverUrl": { "type": "string", - "default": "ws://localhost:3000", + "default": "ws://localhost:8999", "description": "WebSocket URL of the TypeAgent agent server" }, "typeagent.autoStart": { @@ -85,7 +85,7 @@ }, "typeagent.serverPort": { "type": "number", - "default": 3000, + "default": 8999, "description": "Port for the agent server when auto-starting" } } diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 3a424059c9..029c3dbe69 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -117,7 +117,7 @@ export class AgentServerBridge { const config = vscode.workspace.getConfiguration("typeagent"); const serverUrl = config.get( "serverUrl", - "ws://localhost:3000", + "ws://localhost:8999", ); try { @@ -425,7 +425,7 @@ export class AgentServerBridge { const config = vscode.workspace.getConfiguration("typeagent"); const serverUrl = config.get( "serverUrl", - "ws://localhost:3000", + "ws://localhost:8999", ); this.connection = await connectAgentServer(serverUrl, () => { From b7c51dbae2970d9ea29e95011907ac1962a3063f Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 16:43:20 -0700 Subject: [PATCH 009/129] Show 'Switching conversation...' state and disable input during switch - Add 'switching' message type from bridge to webview - Disable input/send button while a conversation switch is in progress - Display target conversation name in the status bar and input placeholder - Add CSS class for the switching state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 6 ++++ .../typeagent-shell/src/agentServerBridge.ts | 26 +++++++++++---- .../typeagent-shell/src/webview/chatUI.ts | 33 +++++++++++++++++-- .../typeagent-shell/src/webview/main.ts | 3 ++ 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 7ca311cf54..09ef74f985 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -49,6 +49,12 @@ body { color: var(--vscode-editor-background); } +.status.switching { + background: var(--vscode-editorWarning-foreground); + color: var(--vscode-editor-background); + font-style: italic; +} + /* ── Messages area ───────────────────────────────────────────── */ #messages { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 029c3dbe69..531f4ca003 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -45,7 +45,8 @@ export type BridgeToWebviewMessage = | { type: "notify"; event: string; data: any; source: string; seq?: number } | { type: "commandResult"; requestId: string; result: any } | { type: "commandComplete"; requestId: string; result: any } - | { type: "error"; message: string }; + | { type: "error"; message: string } + | { type: "switching"; switching: boolean; targetName?: string }; /** * Messages from webview → extension host @@ -209,7 +210,7 @@ export class AgentServerBridge { return; } - await this.joinSpecificSession(pick.sessionId); + await this.joinSpecificSession(pick.sessionId, pick.label); } /** @@ -244,10 +245,11 @@ export class AgentServerBridge { return; } - const info = await this.connection.createSession(name.trim()); - await this.joinSpecificSession(info.sessionId); + const trimmed = name.trim(); + const info = await this.connection.createSession(trimmed); + await this.joinSpecificSession(info.sessionId, trimmed); vscode.window.showInformationMessage( - `Created and switched to conversation "${name.trim()}"`, + `Created and switched to conversation "${trimmed}"`, ); } @@ -352,12 +354,20 @@ export class AgentServerBridge { /** * Leave the current conversation and join a different one. */ - private async joinSpecificSession(sessionId: string): Promise { + private async joinSpecificSession( + sessionId: string, + targetName?: string, + ): Promise { if (!this.connection) { return; } this.isSwitching = true; + this.broadcastToWebviews({ + type: "switching", + switching: true, + targetName, + }); try { // Leave current session (cleans up its channels) if (this.session) { @@ -403,6 +413,10 @@ export class AgentServerBridge { } } finally { this.isSwitching = false; + this.broadcastToWebviews({ + type: "switching", + switching: false, + }); } } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 5bee40076f..6a7ad8b973 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -19,6 +19,9 @@ export class ChatUI { // Dedup: track last appended content to avoid duplicates private _lastAppendedContent?: string; + // Track switching state to keep input disabled + private _isSwitching = false; + constructor() { this._messagesEl = document.getElementById("messages")!; this._inputEl = document.getElementById( @@ -191,8 +194,11 @@ export class ChatUI { this._statusEl.textContent = label ? `Connected · ${label}` : "Connected to TypeAgent"; - this._inputEl.disabled = false; - this._sendBtn.disabled = false; + // Don't re-enable input if a switch is in progress + if (!this._isSwitching) { + this._inputEl.disabled = false; + this._sendBtn.disabled = false; + } } else { this._statusEl.className = "status disconnected"; this._statusEl.textContent = "Disconnected"; @@ -201,6 +207,29 @@ export class ChatUI { } } + /** + * Disable input and show a status message while a conversation switch + * is in progress. + */ + public setSwitching(switching: boolean, targetName?: string): void { + this._isSwitching = switching; + if (switching) { + this._inputEl.disabled = true; + this._sendBtn.disabled = true; + const label = targetName + ? `Switching to conversation "${targetName}"…` + : "Switching conversation…"; + this._statusEl.className = "status switching"; + this._statusEl.textContent = label; + this._inputEl.placeholder = label; + } else { + this._inputEl.disabled = false; + this._sendBtn.disabled = false; + this._inputEl.placeholder = ""; + // Status will be refreshed by the next "status" message + } + } + /** * Called when the user switches to a different conversation. */ diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 285504c9a5..638167c5a5 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -58,6 +58,9 @@ window.addEventListener("message", (event) => { // Command finished — clean up any remaining temporary status chatUI.onCommandComplete(); break; + case "switching": + chatUI.setSwitching(msg.switching, msg.targetName); + break; } }); From f28d5317a3dd65101f17d588aadc1a95941e9a1f Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 17:02:44 -0700 Subject: [PATCH 010/129] Add conversation history replay and adopt join-before-leave switch - On connect and after every conversation switch, call dispatcher.getDisplayHistory() and replay entries through the same display channels live messages use (setUserRequest, setDisplay, appendDisplay, setDisplayInfo). - Wrap replay in historyStart/historyEnd messages so the webview can mark replayed bubbles with a 'history' class for muted styling. - Refactor joinSpecificSession to use the join-before-leave pattern (matching the shell's sessionManager); this also eliminates the channel-already-exists error so reconnectAndJoin is no longer needed. - Display 'conversation history' / 'now' separators around replayed content for clarity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 14 ++ .../typeagent-shell/src/agentServerBridge.ts | 169 +++++++++++------- .../typeagent-shell/src/webview/chatUI.ts | 49 ++++- .../typeagent-shell/src/webview/main.ts | 12 +- 4 files changed, 172 insertions(+), 72 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 09ef74f985..41ab2851cc 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -93,6 +93,20 @@ body { opacity: 0.8; } +/* Replayed history messages — muted/grayscale */ +.message.history { + opacity: 0.55; + filter: grayscale(0.4); +} + +.message.system.separator { + text-transform: uppercase; + letter-spacing: 1px; + font-size: 10px; + color: var(--vscode-descriptionForeground); + margin: 8px 0; +} + .message.system { align-self: center; font-size: 11px; diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 531f4ca003..79aeb4a09d 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -46,7 +46,9 @@ export type BridgeToWebviewMessage = | { type: "commandResult"; requestId: string; result: any } | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string } - | { type: "switching"; switching: boolean; targetName?: string }; + | { type: "switching"; switching: boolean; targetName?: string } + | { type: "historyStart" } + | { type: "historyEnd" }; /** * Messages from webview → extension host @@ -150,6 +152,9 @@ export class AgentServerBridge { sessionId: this.session.sessionId, sessionName: this.session.name, }); + + // Replay any existing history for this session + await this.replayHistory(this.session); } catch (e: any) { const msg = e?.message ?? String(e); this.broadcastToWebviews({ type: "error", message: msg }); @@ -354,6 +359,13 @@ export class AgentServerBridge { /** * Leave the current conversation and join a different one. */ + /** + * Switch to a different conversation using the join-before-leave pattern. + * - Phase 1: join the new session. If this fails, the old session is + * still active so we report failure cleanly. + * - Phase 2: leave the old session (best-effort). + * - Phase 3: replay the new session's display history. + */ private async joinSpecificSession( sessionId: string, targetName?: string, @@ -362,6 +374,10 @@ export class AgentServerBridge { return; } + if (this.session?.sessionId === sessionId) { + return; + } + this.isSwitching = true; this.broadcastToWebviews({ type: "switching", @@ -369,48 +385,47 @@ export class AgentServerBridge { targetName, }); try { - // Leave current session (cleans up its channels) - if (this.session) { - try { - await this.connection.leaveSession(this.session.sessionId); - } catch { - // Best effort — session may already be gone - } - this.session = undefined; - } - - // Join the new session + // Phase 1: join new session first + const clientIO = this.createClientIO(); + let newSession: SessionDispatcher; try { - const clientIO = this.createClientIO(); - this.session = await this.connection.joinSession(clientIO, { + newSession = await this.connection.joinSession(clientIO, { clientType: "extension", filter: true, sessionId, }); } catch (e: any) { - // Channel conflict — reconnect from scratch - const msg = e?.message ?? String(e); - if (msg.includes("already exists")) { - await this.reconnectAndJoin(sessionId); - } else { - throw e; - } + vscode.window.showErrorMessage( + `Failed to switch conversation: ${e?.message ?? String(e)}`, + ); + return; } - // Notify webviews - if (this.session) { - this.broadcastToWebviews({ - type: "sessionChanged", - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); - this.broadcastToWebviews({ - type: "status", - connected: true, - sessionId: this.session.sessionId, - sessionName: this.session.name, - }); + const oldSession = this.session; + this.session = newSession; + + // Phase 2: leave the old session (best-effort) + if (oldSession) { + try { + await this.connection.leaveSession(oldSession.sessionId); + } catch { + // Best effort + } } + + // Phase 3: clear UI and replay history + this.broadcastToWebviews({ + type: "sessionChanged", + sessionId: newSession.sessionId, + sessionName: newSession.name, + }); + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: newSession.sessionId, + sessionName: newSession.name, + }); + await this.replayHistory(newSession); } finally { this.isSwitching = false; this.broadcastToWebviews({ @@ -421,47 +436,63 @@ export class AgentServerBridge { } /** - * Full reconnect to resolve channel conflicts, then join a session. + * Replay the display history for the given session through the same + * channels live messages use, wrapped in historyStart/historyEnd + * markers so the webview can style replayed entries differently. */ - private async reconnectAndJoin(sessionId: string): Promise { - // Cancel any pending auto-reconnect - if (this.reconnectTimer) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = undefined; + private async replayHistory(session: SessionDispatcher): Promise { + let entries: Array; + try { + entries = await session.dispatcher.getDisplayHistory(); + } catch (e: any) { + // Best-effort: history is non-critical + return; } - if (this.connection) { - await this.connection.close(); - this.connection = undefined; - this.session = undefined; + if (entries.length === 0) { + return; } - const config = vscode.workspace.getConfiguration("typeagent"); - const serverUrl = config.get( - "serverUrl", - "ws://localhost:8999", - ); - - this.connection = await connectAgentServer(serverUrl, () => { - if (this.isSwitching) { - return; + this.broadcastToWebviews({ type: "historyStart" }); + for (const entry of entries) { + switch (entry.type) { + case "user-request": + this.broadcastToWebviews({ + type: "setUserRequest", + requestId: entry.requestId, + command: entry.command, + seq: entry.seq, + }); + break; + case "set-display": + this.broadcastToWebviews({ + type: "setDisplay", + message: entry.message, + seq: entry.seq, + }); + break; + case "append-display": + this.broadcastToWebviews({ + type: "appendDisplay", + message: entry.message, + mode: entry.mode, + seq: entry.seq, + }); + break; + case "set-display-info": + this.broadcastToWebviews({ + type: "setDisplayInfo", + requestId: entry.requestId, + source: entry.source, + actionIndex: entry.actionIndex, + action: entry.action, + seq: entry.seq, + }); + break; + // pending-interaction / interaction-* / notify entries skipped } - this.isConnected = false; - this.session = undefined; - this.updateStatusBar(false); - this.broadcastToWebviews({ type: "status", connected: false }); - this.scheduleReconnect(); - }); - - const clientIO = this.createClientIO(); - this.session = await this.connection.joinSession(clientIO, { - clientType: "extension", - filter: true, - sessionId, - }); - - this.isConnected = true; - this.updateStatusBar(true); + } + this.broadcastToWebviews({ type: "historyEnd" }); } private async handleWebviewMessage( diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 6a7ad8b973..4c835bfd4c 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -22,6 +22,9 @@ export class ChatUI { // Track switching state to keep input disabled private _isSwitching = false; + // Track history-replay mode + private _isHistoryMode = false; + constructor() { this._messagesEl = document.getElementById("messages")!; this._inputEl = document.getElementById( @@ -232,10 +235,52 @@ export class ChatUI { /** * Called when the user switches to a different conversation. + * Just clears the UI — history will be replayed via beginHistory/endHistory. */ - public onSessionChanged(sessionName: string): void { + public onSessionChanged(_sessionName: string): void { this.clearMessages(); - this.addSystemMessage(`Switched to conversation: ${sessionName}`); + } + + /** Whether replay-of-history is currently in progress. */ + public isHistoryMode(): boolean { + return this._isHistoryMode; + } + + /** + * Begin replaying server-side display history. Inserts a separator and + * sets a flag so the webview accepts setUserRequest events (which are + * normally ignored because live user messages are added on send). + */ + public beginHistory(): void { + this._isHistoryMode = true; + this._activeResponseEl = undefined; + this._lastAppendedContent = undefined; + const sep = document.createElement("div"); + sep.className = "message system separator history-start"; + sep.textContent = "─── conversation history ───"; + this._messagesEl.appendChild(sep); + } + + /** + * End history replay. Marks all replayed message bubbles with the + * `.history` class so they render in a muted style, then inserts a + * "now" separator and resets active-bubble tracking so live messages + * start a fresh bubble. + */ + public endHistory(): void { + // Mark every message currently in the DOM as historical + const messages = this._messagesEl.querySelectorAll(".message"); + messages.forEach((el) => el.classList.add("history")); + + const sep = document.createElement("div"); + sep.className = "message system separator history-end"; + sep.textContent = "─── now ───"; + this._messagesEl.appendChild(sep); + + this._isHistoryMode = false; + this._activeResponseEl = undefined; + this._lastAppendedContent = undefined; + this._scrollToBottom(); } public addSystemMessage(text: string): void { diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 638167c5a5..2c04e17e20 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -37,7 +37,11 @@ window.addEventListener("message", (event) => { ); break; case "setUserRequest": - // User message is shown immediately on send — skip server echo + // Live: user message shown immediately on send — skip echo. + // History replay: must show it (no live send happened). + if (chatUI.isHistoryMode()) { + chatUI.addUserMessage(msg.command); + } break; case "setDisplayInfo": chatUI.setDisplayInfo(msg.source, msg.action); @@ -61,6 +65,12 @@ window.addEventListener("message", (event) => { case "switching": chatUI.setSwitching(msg.switching, msg.targetName); break; + case "historyStart": + chatUI.beginHistory(); + break; + case "historyEnd": + chatUI.endHistory(); + break; } }); From 3edcd4b06e3b82ced0a640a72fc67ced77b9cadc Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Tue, 21 Apr 2026 17:25:38 -0700 Subject: [PATCH 011/129] Replace history separators with per-bubble timestamps - Remove '--- conversation history ---' and '--- now ---' separators - Each user/agent bubble now shows a small timestamp above its content - Format: HH:MM for today, 'Yesterday HH:MM' for yesterday, 'Mon DD HH:MM' otherwise - Full datetime shown on hover via title attribute - Bridge forwards the entry timestamp during history replay; live messages default to Date.now() - History bubbles still rendered muted via the existing .history class Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 19 ++-- .../typeagent-shell/src/agentServerBridge.ts | 7 +- .../typeagent-shell/src/webview/chatUI.ts | 90 ++++++++++++++----- .../typeagent-shell/src/webview/main.ts | 9 +- 4 files changed, 96 insertions(+), 29 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 41ab2851cc..ff133290ca 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -99,12 +99,21 @@ body { filter: grayscale(0.4); } -.message.system.separator { - text-transform: uppercase; - letter-spacing: 1px; +/* Per-bubble timestamp shown above the content */ +.message-timestamp { + display: block; font-size: 10px; - color: var(--vscode-descriptionForeground); - margin: 8px 0; + opacity: 0.6; + margin-bottom: 2px; + user-select: none; +} + +.message.user .message-timestamp { + text-align: right; +} + +.message-content { + display: block; } .message.system { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 79aeb4a09d..f2216a6deb 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -20,12 +20,13 @@ import type { SessionInfo } from "@typeagent/agent-server-protocol"; export type BridgeToWebviewMessage = | { type: "status"; connected: boolean; sessionId?: string; sessionName?: string } | { type: "sessionChanged"; sessionId: string; sessionName: string } - | { type: "setDisplay"; message: IAgentMessage; seq?: number } + | { type: "setDisplay"; message: IAgentMessage; seq?: number; timestamp?: number } | { type: "appendDisplay"; message: IAgentMessage; mode: DisplayAppendMode; seq?: number; + timestamp?: number; } | { type: "setDisplayInfo"; @@ -40,6 +41,7 @@ export type BridgeToWebviewMessage = requestId: RequestId; command: string; seq?: number; + timestamp?: number; } | { type: "clear"; requestId: RequestId } | { type: "notify"; event: string; data: any; source: string; seq?: number } @@ -462,6 +464,7 @@ export class AgentServerBridge { requestId: entry.requestId, command: entry.command, seq: entry.seq, + timestamp: entry.timestamp, }); break; case "set-display": @@ -469,6 +472,7 @@ export class AgentServerBridge { type: "setDisplay", message: entry.message, seq: entry.seq, + timestamp: entry.timestamp, }); break; case "append-display": @@ -477,6 +481,7 @@ export class AgentServerBridge { message: entry.message, mode: entry.mode, seq: entry.seq, + timestamp: entry.timestamp, }); break; case "set-display-info": diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 4c835bfd4c..a5dbe039c2 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -55,14 +55,19 @@ export class ChatUI { this._sendCallback = callback; } - public addUserMessage(text: string): void { + public addUserMessage(text: string, timestamp?: number): void { this._removeStatusIndicator(); this._removeTemporary(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; const el = document.createElement("div"); el.className = "message user"; - el.textContent = text; + const tsEl = this._createTimestampEl(timestamp); + el.appendChild(tsEl); + const textEl = document.createElement("span"); + textEl.className = "message-content"; + textEl.textContent = text; + el.appendChild(textEl); this._messagesEl.appendChild(el); this._scrollToBottom(); } @@ -70,11 +75,15 @@ export class ChatUI { /** * setDisplay: replace the content of the active agent bubble. */ - public setAgentDisplay(content: any, source?: string): void { + public setAgentDisplay( + content: any, + source?: string, + timestamp?: number, + ): void { this._removeStatusIndicator(); this._removeTemporary(); if (!this._activeResponseEl) { - this._activeResponseEl = this._createAgentBubble(source); + this._activeResponseEl = this._createAgentBubble(source, timestamp); } if (source) { const sourceEl = this._activeResponseEl.querySelector(".source-label"); @@ -99,6 +108,7 @@ export class ChatUI { content: any, source?: string, mode?: string, + timestamp?: number, ): void { this._removeStatusIndicator(); @@ -126,7 +136,7 @@ export class ChatUI { if (!this._activeResponseEl) { // Remove temporary when first real content arrives this._removeTemporary(); - this._activeResponseEl = this._createAgentBubble(source); + this._activeResponseEl = this._createAgentBubble(source, timestamp); } const contentEl = this._activeResponseEl.querySelector(".agent-content"); @@ -247,36 +257,26 @@ export class ChatUI { } /** - * Begin replaying server-side display history. Inserts a separator and - * sets a flag so the webview accepts setUserRequest events (which are - * normally ignored because live user messages are added on send). + * Begin replaying server-side display history. Sets a flag so the + * webview accepts setUserRequest events (which are normally ignored + * because live user messages are added on send). */ public beginHistory(): void { this._isHistoryMode = true; this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - const sep = document.createElement("div"); - sep.className = "message system separator history-start"; - sep.textContent = "─── conversation history ───"; - this._messagesEl.appendChild(sep); } /** * End history replay. Marks all replayed message bubbles with the - * `.history` class so they render in a muted style, then inserts a - * "now" separator and resets active-bubble tracking so live messages - * start a fresh bubble. + * `.history` class so they render in a muted style, then resets + * active-bubble tracking so live messages start a fresh bubble. */ public endHistory(): void { // Mark every message currently in the DOM as historical const messages = this._messagesEl.querySelectorAll(".message"); messages.forEach((el) => el.classList.add("history")); - const sep = document.createElement("div"); - sep.className = "message system separator history-end"; - sep.textContent = "─── now ───"; - this._messagesEl.appendChild(sep); - this._isHistoryMode = false; this._activeResponseEl = undefined; this._lastAppendedContent = undefined; @@ -301,9 +301,14 @@ export class ChatUI { this._sendCallback?.(text); } - private _createAgentBubble(source?: string): HTMLElement { + private _createAgentBubble( + source?: string, + timestamp?: number, + ): HTMLElement { const el = document.createElement("div"); el.className = "message agent"; + const tsEl = this._createTimestampEl(timestamp); + el.appendChild(tsEl); if (source) { const sourceEl = document.createElement("span"); sourceEl.className = "source-label"; @@ -317,6 +322,49 @@ export class ChatUI { return el; } + private _createTimestampEl(timestamp?: number): HTMLElement { + const ts = timestamp ?? Date.now(); + const el = document.createElement("span"); + el.className = "message-timestamp"; + el.textContent = this._formatTimestamp(ts); + // Tooltip with full date/time on hover + el.title = new Date(ts).toLocaleString(); + return el; + } + + /** + * Format timestamp as time-of-day for today, "Yesterday HH:MM" for + * yesterday, or short date+time for older messages. + */ + private _formatTimestamp(timestamp: number): string { + const d = new Date(timestamp); + const now = new Date(); + const time = d.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); + const sameDay = + d.getFullYear() === now.getFullYear() && + d.getMonth() === now.getMonth() && + d.getDate() === now.getDate(); + if (sameDay) { + return time; + } + const yesterday = new Date(now); + yesterday.setDate(now.getDate() - 1); + const isYesterday = + d.getFullYear() === yesterday.getFullYear() && + d.getMonth() === yesterday.getMonth() && + d.getDate() === yesterday.getDate(); + if (isYesterday) { + return `Yesterday ${time}`; + } + return `${d.toLocaleDateString([], { + month: "short", + day: "numeric", + })} ${time}`; + } + private _removeStatusIndicator(): void { if (this._statusIndicatorEl) { this._statusIndicatorEl.remove(); diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 2c04e17e20..57349efaa6 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -27,20 +27,25 @@ window.addEventListener("message", (event) => { chatUI.onSessionChanged(msg.sessionName); break; case "setDisplay": - chatUI.setAgentDisplay(msg.message.message, msg.message.source); + chatUI.setAgentDisplay( + msg.message.message, + msg.message.source, + msg.timestamp, + ); break; case "appendDisplay": chatUI.appendAgentDisplay( msg.message.message, msg.message.source, msg.mode, + msg.timestamp, ); break; case "setUserRequest": // Live: user message shown immediately on send — skip echo. // History replay: must show it (no live send happened). if (chatUI.isHistoryMode()) { - chatUI.addUserMessage(msg.command); + chatUI.addUserMessage(msg.command, msg.timestamp); } break; case "setDisplayInfo": From 22ae986713e57bd499292f7857bd45aafd5bf2bb Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 12:31:34 -0700 Subject: [PATCH 012/129] Batch history replay into single atomic message Previously replay sent N postMessages (one per history entry) and live events could be interleaved mid-replay, causing: - Long delay rendering history (slow per-entry postMessage round trips) - User's live messages getting absorbed into history replay flow and marked as .history (they appeared grayed out or didn't show correctly) Fix: send the entire history as a single 'historyReplay' message. The webview processes all entries synchronously, then marks just those bubbles with the .history class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 111 ++++++++++-------- .../typeagent-shell/src/webview/chatUI.ts | 69 +++++++---- .../typeagent-shell/src/webview/main.ts | 12 +- 3 files changed, 112 insertions(+), 80 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index f2216a6deb..8f438cd58d 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -49,8 +49,24 @@ export type BridgeToWebviewMessage = | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string } | { type: "switching"; switching: boolean; targetName?: string } - | { type: "historyStart" } - | { type: "historyEnd" }; + | { + type: "historyReplay"; + entries: Array<{ + type: string; + seq: number; + timestamp?: number; + // user-request + command?: string; + // set-display / append-display + message?: IAgentMessage; + mode?: DisplayAppendMode; + // set-display-info + source?: string; + action?: TypeAgentAction | string[]; + actionIndex?: number; + requestId?: RequestId; + }>; + }; /** * Messages from webview → extension host @@ -446,8 +462,7 @@ export class AgentServerBridge { let entries: Array; try { entries = await session.dispatcher.getDisplayHistory(); - } catch (e: any) { - // Best-effort: history is non-critical + } catch { return; } @@ -455,49 +470,51 @@ export class AgentServerBridge { return; } - this.broadcastToWebviews({ type: "historyStart" }); - for (const entry of entries) { - switch (entry.type) { - case "user-request": - this.broadcastToWebviews({ - type: "setUserRequest", - requestId: entry.requestId, - command: entry.command, - seq: entry.seq, - timestamp: entry.timestamp, - }); - break; - case "set-display": - this.broadcastToWebviews({ - type: "setDisplay", - message: entry.message, - seq: entry.seq, - timestamp: entry.timestamp, - }); - break; - case "append-display": - this.broadcastToWebviews({ - type: "appendDisplay", - message: entry.message, - mode: entry.mode, - seq: entry.seq, - timestamp: entry.timestamp, - }); - break; - case "set-display-info": - this.broadcastToWebviews({ - type: "setDisplayInfo", - requestId: entry.requestId, - source: entry.source, - actionIndex: entry.actionIndex, - action: entry.action, - seq: entry.seq, - }); - break; - // pending-interaction / interaction-* / notify entries skipped - } - } - this.broadcastToWebviews({ type: "historyEnd" }); + // Send the whole history as a single message — avoids slow per-entry + // postMessage round trips and prevents live events from being + // interleaved mid-replay. + this.broadcastToWebviews({ + type: "historyReplay", + entries: entries.map((e) => { + switch (e.type) { + case "user-request": + return { + type: "user-request", + seq: e.seq, + timestamp: e.timestamp, + requestId: e.requestId, + command: e.command, + }; + case "set-display": + return { + type: "set-display", + seq: e.seq, + timestamp: e.timestamp, + message: e.message, + }; + case "append-display": + return { + type: "append-display", + seq: e.seq, + timestamp: e.timestamp, + message: e.message, + mode: e.mode, + }; + case "set-display-info": + return { + type: "set-display-info", + seq: e.seq, + timestamp: e.timestamp, + requestId: e.requestId, + source: e.source, + actionIndex: e.actionIndex, + action: e.action, + }; + default: + return { type: "skip", seq: e.seq }; + } + }), + }); } private async handleWebviewMessage( diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index a5dbe039c2..270c890f2a 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -22,9 +22,6 @@ export class ChatUI { // Track switching state to keep input disabled private _isSwitching = false; - // Track history-replay mode - private _isHistoryMode = false; - constructor() { this._messagesEl = document.getElementById("messages")!; this._inputEl = document.getElementById( @@ -251,35 +248,59 @@ export class ChatUI { this.clearMessages(); } - /** Whether replay-of-history is currently in progress. */ - public isHistoryMode(): boolean { - return this._isHistoryMode; - } - /** - * Begin replaying server-side display history. Sets a flag so the - * webview accepts setUserRequest events (which are normally ignored - * because live user messages are added on send). + * Replay the given display history entries atomically. Processing is + * done synchronously so no live message can be interleaved mid-replay, + * and all replayed bubbles are marked with the `.history` class for + * muted styling. */ - public beginHistory(): void { - this._isHistoryMode = true; + public replayHistory(entries: Array): void { + if (!entries || entries.length === 0) { + return; + } + // Track where history begins so we can mark only those bubbles + const firstHistoryIdx = this._messagesEl.children.length; + this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - } - /** - * End history replay. Marks all replayed message bubbles with the - * `.history` class so they render in a muted style, then resets - * active-bubble tracking so live messages start a fresh bubble. - */ - public endHistory(): void { - // Mark every message currently in the DOM as historical - const messages = this._messagesEl.querySelectorAll(".message"); - messages.forEach((el) => el.classList.add("history")); + for (const e of entries) { + switch (e.type) { + case "user-request": + this.addUserMessage(e.command, e.timestamp); + break; + case "set-display": + this.setAgentDisplay( + e.message?.message, + e.message?.source, + e.timestamp, + ); + break; + case "append-display": + this.appendAgentDisplay( + e.message?.message, + e.message?.source, + e.mode, + e.timestamp, + ); + break; + case "set-display-info": + // Skip status indicators during replay — they'd pollute + // the rendered history with transient "processing..." text + break; + } + } - this._isHistoryMode = false; + // Mark everything we just appended as history + for (let i = firstHistoryIdx; i < this._messagesEl.children.length; i++) { + this._messagesEl.children[i].classList.add("history"); + } + + // Reset state so the next live message starts a fresh bubble this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + this._removeTemporary(); + this._removeStatusIndicator(); this._scrollToBottom(); } diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 57349efaa6..175d45a09e 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -43,10 +43,7 @@ window.addEventListener("message", (event) => { break; case "setUserRequest": // Live: user message shown immediately on send — skip echo. - // History replay: must show it (no live send happened). - if (chatUI.isHistoryMode()) { - chatUI.addUserMessage(msg.command, msg.timestamp); - } + // History now comes through historyReplay batch, not here. break; case "setDisplayInfo": chatUI.setDisplayInfo(msg.source, msg.action); @@ -70,11 +67,8 @@ window.addEventListener("message", (event) => { case "switching": chatUI.setSwitching(msg.switching, msg.targetName); break; - case "historyStart": - chatUI.beginHistory(); - break; - case "historyEnd": - chatUI.endHistory(); + case "historyReplay": + chatUI.replayHistory(msg.entries); break; } }); From c498301bb80c85fd6737af61e1ecc297288d6f2a Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 12:55:01 -0700 Subject: [PATCH 013/129] Don't replay history on websocket reconnect Replaying history on every reconnect (which can happen during long LLM calls) caused the live user message bubble and its incoming response to appear interleaved with muted duplicates from the replay, making it look like the original message disappeared. Track lastReplayedSessionId and only replay when we first join a session (or switch to a different one). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 8f438cd58d..89f86a1a39 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -90,6 +90,10 @@ export class AgentServerBridge { private reconnectTimer: NodeJS.Timeout | undefined; // Suppress disconnect handler during intentional reconnects private isSwitching = false; + // Track which session we've already replayed history for, so we + // don't replay again on simple websocket reconnects (which would + // create muted duplicates of live messages). + private lastReplayedSessionId: string | undefined; constructor() { this.statusBarItem = vscode.window.createStatusBarItem( @@ -171,8 +175,14 @@ export class AgentServerBridge { sessionName: this.session.name, }); - // Replay any existing history for this session - await this.replayHistory(this.session); + // Replay history only the first time we join this session. + // On simple reconnects we already have the bubbles in the DOM + // and re-replaying would create muted duplicates and (worse) + // race against any live messages still in flight. + if (this.lastReplayedSessionId !== this.session.sessionId) { + this.lastReplayedSessionId = this.session.sessionId; + await this.replayHistory(this.session); + } } catch (e: any) { const msg = e?.message ?? String(e); this.broadcastToWebviews({ type: "error", message: msg }); @@ -444,6 +454,7 @@ export class AgentServerBridge { sessionName: newSession.name, }); await this.replayHistory(newSession); + this.lastReplayedSessionId = newSession.sessionId; } finally { this.isSwitching = false; this.broadcastToWebviews({ From 9e0ff06e309dfefc0ead7c7ec0177a47b9e387aa Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 14:23:05 -0700 Subject: [PATCH 014/129] Render ANSI escape codes and markdown in chat output The agent dispatcher emits help text and other content with ANSI escape sequences (e.g. for bold/colored text). Previously those raw sequences were HTML-escaped and shown verbatim, producing output like '[0m[1m[4mSubcommands' instead of styled text. Bring rendering in line with the Electron shell: - ansi_up converts ANSI sequences to with classes - markdown-it renders markdown DisplayContent (lists, tables, code) - DOMPurify sanitises the resulting HTML - Added ANSI color CSS bound to VS Code terminal theme variables and basic markdown styles for code/lists/tables/paragraphs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 53 ++++++++ .../typeagent-shell/package-lock.json | 119 ++++++++++++++++++ ts/extensions/typeagent-shell/package.json | 18 ++- .../typeagent-shell/src/webview/chatUI.ts | 118 +++++++++++++---- 4 files changed, 278 insertions(+), 30 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index ff133290ca..bf402a7e18 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -225,3 +225,56 @@ body { #messages::-webkit-scrollbar-thumb:hover { background: var(--vscode-scrollbarSlider-hoverBackground); } + +/* ── ANSI color codes (from ansi_up with use_classes=true) ───── */ + +.message span.ansi-black-fg { color: var(--vscode-terminal-ansiBlack, #000); } +.message span.ansi-red-fg { color: var(--vscode-terminal-ansiRed, #c00); } +.message span.ansi-green-fg { color: var(--vscode-terminal-ansiGreen, #0c0); } +.message span.ansi-yellow-fg { color: var(--vscode-terminal-ansiYellow, #cc0); } +.message span.ansi-blue-fg { color: var(--vscode-terminal-ansiBlue, #00c); } +.message span.ansi-magenta-fg { color: var(--vscode-terminal-ansiMagenta, #c0c); } +.message span.ansi-cyan-fg { color: var(--vscode-terminal-ansiCyan, #0cc); } +.message span.ansi-white-fg { color: var(--vscode-terminal-ansiWhite, #ccc); } +.message span.ansi-bright-black-fg, +.message span.ansi-gray-fg { color: var(--vscode-terminal-ansiBrightBlack, #888); } +.message span.ansi-bright-red-fg { color: var(--vscode-terminal-ansiBrightRed, #f00); } +.message span.ansi-bright-green-fg { color: var(--vscode-terminal-ansiBrightGreen, #0f0); } +.message span.ansi-bright-yellow-fg { color: var(--vscode-terminal-ansiBrightYellow, #ff0); } +.message span.ansi-bright-blue-fg { color: var(--vscode-terminal-ansiBrightBlue, #00f); } +.message span.ansi-bright-magenta-fg { color: var(--vscode-terminal-ansiBrightMagenta, #f0f); } +.message span.ansi-bright-cyan-fg { color: var(--vscode-terminal-ansiBrightCyan, #0ff); } +.message span.ansi-bright-white-fg { color: var(--vscode-terminal-ansiBrightWhite, #fff); } + +.message span.ansi-bold { font-weight: bold; } +.message span.ansi-italic { font-style: italic; } +.message span.ansi-underline { text-decoration: underline; } + +/* ── Markdown content ────────────────────────────────────────── */ + +.message p { margin: 0.25em 0; } +.message pre { + background: var(--vscode-textCodeBlock-background); + padding: 6px 8px; + border-radius: 4px; + overflow-x: auto; + font-size: 0.9em; + margin: 0.4em 0; +} +.message code { + background: var(--vscode-textCodeBlock-background); + padding: 1px 4px; + border-radius: 3px; + font-size: 0.9em; +} +.message pre code { background: transparent; padding: 0; } +.message ul, .message ol { margin: 0.25em 0 0.25em 1.4em; padding: 0; } +.message table { + border-collapse: collapse; + margin: 0.4em 0; +} +.message th, .message td { + border: 1px solid var(--vscode-panel-border); + padding: 3px 6px; + font-size: 0.9em; +} diff --git a/ts/extensions/typeagent-shell/package-lock.json b/ts/extensions/typeagent-shell/package-lock.json index 9f1342ab37..19286daf50 100644 --- a/ts/extensions/typeagent-shell/package-lock.json +++ b/ts/extensions/typeagent-shell/package-lock.json @@ -15,12 +15,16 @@ "@typeagent/agent-server-protocol": "file:../../packages/agentServer/protocol", "@typeagent/dispatcher-rpc": "file:../../packages/dispatcher/rpc", "@typeagent/dispatcher-types": "file:../../packages/dispatcher/types", + "ansi_up": "^6.0.6", "debug": "^4.4.0", + "dompurify": "^3.4.1", "isomorphic-ws": "^5.0.0", + "markdown-it": "^14.1.1", "ws": "^8.18.0" }, "devDependencies": { "@types/debug": "^4.1.0", + "@types/markdown-it": "^14.1.2", "@types/vscode": "^1.90.0", "@types/ws": "^8.5.0", "esbuild": "^0.25.0" @@ -604,6 +608,31 @@ "@types/ms": "*" } }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -621,6 +650,13 @@ "undici-types": "~7.19.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/vscode": { "version": "1.116.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz", @@ -638,6 +674,21 @@ "@types/node": "*" } }, + "node_modules/ansi_up": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.6.tgz", + "integrity": "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -655,6 +706,27 @@ } } }, + "node_modules/dompurify": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz", + "integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -706,12 +778,59 @@ "ws": "*" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.19.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index e951623621..70d1d16ef2 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -16,7 +16,9 @@ "engines": { "vscode": "^1.90.0" }, - "categories": ["Other"], + "categories": [ + "Other" + ], "activationEvents": [], "main": "./dist/extension.js", "contributes": { @@ -97,20 +99,24 @@ "watch": "node esbuild.mjs --watch" }, "dependencies": { - "@typeagent/agent-server-client": "file:../../packages/agentServer/client", - "@typeagent/agent-server-protocol": "file:../../packages/agentServer/protocol", "@typeagent/agent-rpc": "file:../../packages/agentRpc", "@typeagent/agent-sdk": "file:../../packages/agentSdk", + "@typeagent/agent-server-client": "file:../../packages/agentServer/client", + "@typeagent/agent-server-protocol": "file:../../packages/agentServer/protocol", "@typeagent/dispatcher-rpc": "file:../../packages/dispatcher/rpc", "@typeagent/dispatcher-types": "file:../../packages/dispatcher/types", + "ansi_up": "^6.0.6", + "debug": "^4.4.0", + "dompurify": "^3.4.1", "isomorphic-ws": "^5.0.0", - "ws": "^8.18.0", - "debug": "^4.4.0" + "markdown-it": "^14.1.1", + "ws": "^8.18.0" }, "devDependencies": { + "@types/debug": "^4.1.0", + "@types/markdown-it": "^14.1.2", "@types/vscode": "^1.90.0", "@types/ws": "^8.5.0", - "@types/debug": "^4.1.0", "esbuild": "^0.25.0" } } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 270c890f2a..9b5d838bd1 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -1,6 +1,33 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import { AnsiUp } from "ansi_up"; +import MarkdownIt from "markdown-it"; +import DOMPurify from "dompurify"; + +const ansiText = new AnsiUp(); +ansiText.use_classes = true; +const ansiMarkdown = new AnsiUp(); +ansiMarkdown.use_classes = true; +ansiMarkdown.escape_html = false; + +const md = new MarkdownIt({ html: true, linkify: true, breaks: false }); +const defaultLinkOpen = + md.renderer.rules.link_open || + function (tokens, idx, options, _env, self) { + return self.renderToken(tokens, idx, options); + }; +md.renderer.rules.link_open = (tokens, idx, ...args) => { + tokens[idx].attrSet("target", "_blank"); + return defaultLinkOpen(tokens, idx, ...args); +}; + +const purifyConfig = { + ADD_ATTR: ["target"], + ALLOWED_URI_REGEXP: + /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, +}; + /** * Manages the chat UI elements in the webview. * Groups messages by requestId to match the shell's behavior. @@ -400,44 +427,87 @@ export class ChatUI { /** * Convert DisplayContent (string | string[] | string[][] | TypedDisplayContent) - * into an HTML string for rendering. + * into an HTML string for rendering. Handles ANSI escape codes and + * markdown the same way the Electron shell does. */ private _renderDisplayContent(content: any): string { if (content === null || content === undefined) { return ""; } + // Plain string: text type — convert ANSI to HTML, escape rest if (typeof content === "string") { - return this._escapeHtml(content); + return this._renderText(content); + } + // string[] / string[][] + if (Array.isArray(content)) { + if (content.length === 0) return ""; + if (Array.isArray(content[0])) { + // 2-D: render as a markdown table + return this._renderMarkdown(this._tableToMarkdown(content)); + } + return this._renderText((content as string[]).join("\n")); } // TypedDisplayContent: { type, content, alternates? } - if (typeof content === "object" && !Array.isArray(content)) { - if (content.alternates) { - const textAlt = content.alternates.find( - (a: any) => a.type === "text", + if (typeof content === "object") { + // Prefer html alternate when present (matches shell) + const htmlAlt = content.alternates?.find( + (a: any) => a.type === "html", + ); + if (htmlAlt && content.type !== "html") { + return this._sanitizeHtml( + this._stringifyMessage(htmlAlt.content), ); - if (textAlt) { - return this._renderDisplayContent(textAlt.content); - } } - if (content.content !== undefined) { - return this._renderDisplayContent(content.content); + const inner = content.content; + switch (content.type) { + case "html": + return this._sanitizeHtml(this._stringifyMessage(inner)); + case "markdown": + return this._renderMarkdown(this._stringifyMessage(inner)); + case "text": + default: + return this._renderDisplayContent(inner); } - return this._escapeHtml(JSON.stringify(content)); } - // string[] or string[][] - if (Array.isArray(content)) { - if (content.length === 0) return ""; - if (Array.isArray(content[0])) { - return content - .map((row: string[]) => row.join(" | ")) - .map((line: string) => this._escapeHtml(line)) - .join("
"); + return this._renderText(String(content)); + } + + private _stringifyMessage(message: any): string { + if (typeof message === "string") return message; + if (Array.isArray(message)) { + if (message.length === 0) return ""; + if (Array.isArray(message[0])) { + return this._tableToMarkdown(message as string[][]); } - return content - .map((line: string) => this._escapeHtml(String(line))) - .join("
"); + return (message as string[]).join("\n"); + } + return String(message); + } + + private _renderText(text: string): string { + const html = ansiText.ansi_to_html(text); + return html.replace(/\n/g, "
"); + } + + private _renderMarkdown(text: string): string { + const rendered = md.render(text); + const withAnsi = ansiMarkdown.ansi_to_html(rendered); + return this._sanitizeHtml(withAnsi); + } + + private _sanitizeHtml(html: string): string { + return DOMPurify.sanitize(html, purifyConfig); + } + + private _tableToMarkdown(table: string[][]): string { + if (table.length === 0) return ""; + const rows: string[] = []; + rows.push("| " + table[0].join(" | ") + " |"); + rows.push("| " + table[0].map(() => "---").join(" | ") + " |"); + for (let i = 1; i < table.length; i++) { + rows.push("| " + table[i].join(" | ") + " |"); } - return this._escapeHtml(String(content)); + return rows.join("\n"); } private _escapeHtml(text: string): string { From 919bd94796b63517de702c93a44a57a4bc912b06 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 14:31:49 -0700 Subject: [PATCH 015/129] Improve agent bubble + ANSI color contrast - Switch agent bubble background from inactiveSelectionBackground (very faint) to input-background with a subtle border for visibility - Replace VS Code terminal ANSI variables (designed for terminal background) with palette tuned to read against the chat bubble in both light and dark themes, scoped via body.vscode-light / vscode-dark classes - White / bright-white now map to foreground so they're never invisible Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 63 ++++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index bf402a7e18..66489d9cad 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -84,8 +84,9 @@ body { .message.agent { align-self: flex-start; - background: var(--vscode-editor-inactiveSelectionBackground); + background: var(--vscode-input-background); color: var(--vscode-foreground); + border: 1px solid var(--vscode-input-border, transparent); border-bottom-left-radius: 2px; } @@ -226,25 +227,49 @@ body { background: var(--vscode-scrollbarSlider-hoverBackground); } -/* ── ANSI color codes (from ansi_up with use_classes=true) ───── */ - -.message span.ansi-black-fg { color: var(--vscode-terminal-ansiBlack, #000); } -.message span.ansi-red-fg { color: var(--vscode-terminal-ansiRed, #c00); } -.message span.ansi-green-fg { color: var(--vscode-terminal-ansiGreen, #0c0); } -.message span.ansi-yellow-fg { color: var(--vscode-terminal-ansiYellow, #cc0); } -.message span.ansi-blue-fg { color: var(--vscode-terminal-ansiBlue, #00c); } -.message span.ansi-magenta-fg { color: var(--vscode-terminal-ansiMagenta, #c0c); } -.message span.ansi-cyan-fg { color: var(--vscode-terminal-ansiCyan, #0cc); } -.message span.ansi-white-fg { color: var(--vscode-terminal-ansiWhite, #ccc); } +/* ── ANSI color codes (from ansi_up with use_classes=true) ───── + * Tuned for contrast against the agent bubble background, using + * VS Code's body theme classes (vscode-light / vscode-dark / + * vscode-high-contrast) so colors stay readable in any theme. + */ + +/* Dark themes (default) — saturated mid-tones */ +.message span.ansi-black-fg { color: #6c6c6c; } +.message span.ansi-red-fg { color: #f48771; } +.message span.ansi-green-fg { color: #89d185; } +.message span.ansi-yellow-fg { color: #dcdcaa; } +.message span.ansi-blue-fg { color: #6796e6; } +.message span.ansi-magenta-fg { color: #d670d6; } +.message span.ansi-cyan-fg { color: #4ec9b0; } +.message span.ansi-white-fg, +.message span.ansi-bright-white-fg { color: var(--vscode-foreground); } .message span.ansi-bright-black-fg, -.message span.ansi-gray-fg { color: var(--vscode-terminal-ansiBrightBlack, #888); } -.message span.ansi-bright-red-fg { color: var(--vscode-terminal-ansiBrightRed, #f00); } -.message span.ansi-bright-green-fg { color: var(--vscode-terminal-ansiBrightGreen, #0f0); } -.message span.ansi-bright-yellow-fg { color: var(--vscode-terminal-ansiBrightYellow, #ff0); } -.message span.ansi-bright-blue-fg { color: var(--vscode-terminal-ansiBrightBlue, #00f); } -.message span.ansi-bright-magenta-fg { color: var(--vscode-terminal-ansiBrightMagenta, #f0f); } -.message span.ansi-bright-cyan-fg { color: var(--vscode-terminal-ansiBrightCyan, #0ff); } -.message span.ansi-bright-white-fg { color: var(--vscode-terminal-ansiBrightWhite, #fff); } +.message span.ansi-gray-fg { color: var(--vscode-descriptionForeground); } +.message span.ansi-bright-red-fg { color: #ff8a76; } +.message span.ansi-bright-green-fg { color: #b5cea8; } +.message span.ansi-bright-yellow-fg { color: #ffd700; } +.message span.ansi-bright-blue-fg { color: #569cd6; } +.message span.ansi-bright-magenta-fg { color: #d670d6; } +.message span.ansi-bright-cyan-fg { color: #4ec9b0; } + +/* Light theme — darker, more saturated */ +body.vscode-light .message span.ansi-red-fg, +body.vscode-light .message span.ansi-bright-red-fg { color: #a31515; } +body.vscode-light .message span.ansi-green-fg, +body.vscode-light .message span.ansi-bright-green-fg { color: #098658; } +body.vscode-light .message span.ansi-yellow-fg, +body.vscode-light .message span.ansi-bright-yellow-fg { color: #795e26; } +body.vscode-light .message span.ansi-blue-fg, +body.vscode-light .message span.ansi-bright-blue-fg { color: #0451a5; } +body.vscode-light .message span.ansi-magenta-fg, +body.vscode-light .message span.ansi-bright-magenta-fg { color: #af00db; } +body.vscode-light .message span.ansi-cyan-fg, +body.vscode-light .message span.ansi-bright-cyan-fg { color: #267f99; } +body.vscode-light .message span.ansi-black-fg, +body.vscode-light .message span.ansi-white-fg, +body.vscode-light .message span.ansi-bright-white-fg { color: var(--vscode-foreground); } +body.vscode-light .message span.ansi-bright-black-fg, +body.vscode-light .message span.ansi-gray-fg { color: var(--vscode-descriptionForeground); } .message span.ansi-bold { font-weight: bold; } .message span.ansi-italic { font-style: italic; } From 94c51ebd78311b6c33014e363117d592b91be905 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 14:35:40 -0700 Subject: [PATCH 016/129] Don't prefer HTML alternates with hardcoded light-theme colors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dispatcher emits @config (and similar) output as text+ANSI primary with an HTML alternate. The HTML alternate uses hardcoded slate colors (#1e293b, #64748b, #475569) designed for a light background — they're nearly invisible on VS Code's dark theme. Skip the HTML preference and use the text/markdown primary, which our ANSI/markdown renderer themes correctly via CSS variables. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/webview/chatUI.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 9b5d838bd1..bb10762e87 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -449,15 +449,10 @@ export class ChatUI { } // TypedDisplayContent: { type, content, alternates? } if (typeof content === "object") { - // Prefer html alternate when present (matches shell) - const htmlAlt = content.alternates?.find( - (a: any) => a.type === "html", - ); - if (htmlAlt && content.type !== "html") { - return this._sanitizeHtml( - this._stringifyMessage(htmlAlt.content), - ); - } + // Note: we deliberately do NOT prefer HTML alternates here. + // Many agents emit HTML alternates with hard-coded light-theme + // colors (e.g. #1e293b) that are unreadable on dark themes. + // The text/markdown primary is themed by our CSS instead. const inner = content.content; switch (content.type) { case "html": From 7ebeb1480d58858621eb7adc918f8f20d010a03d Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 14:38:20 -0700 Subject: [PATCH 017/129] Use HTML alternates but strip hard-coded inline colors Restore preference for HTML alternates so we get the richer rendering (emojis, layout, two-column tables) but post-process the HTML to drop hard-coded color/background/border-color from inline styles before sanitising. The remaining theme-variable-based CSS then applies, so content is readable on both light and dark themes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index bb10762e87..372efe8e14 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -449,10 +449,20 @@ export class ChatUI { } // TypedDisplayContent: { type, content, alternates? } if (typeof content === "object") { - // Note: we deliberately do NOT prefer HTML alternates here. - // Many agents emit HTML alternates with hard-coded light-theme - // colors (e.g. #1e293b) that are unreadable on dark themes. - // The text/markdown primary is themed by our CSS instead. + // Prefer html alternate when present (richer rendering with + // emojis, layout, etc). We strip hard-coded inline color + // styles so the content inherits theme-aware colors instead + // of light-theme defaults baked in by some agents. + const htmlAlt = content.alternates?.find( + (a: any) => a.type === "html", + ); + if (htmlAlt && content.type !== "html") { + return this._sanitizeHtml( + this._stripInlineColors( + this._stringifyMessage(htmlAlt.content), + ), + ); + } const inner = content.content; switch (content.type) { case "html": @@ -494,6 +504,40 @@ export class ChatUI { return DOMPurify.sanitize(html, purifyConfig); } + /** + * Remove hard-coded color / background / border-color from inline + * style attributes so theme variables can take over. Many agents + * emit HTML with light-theme baked colors which are unreadable on + * a dark theme; this neutralizes those without losing other style + * properties (padding, font-weight, alignment, etc). + */ + private _stripInlineColors(html: string): string { + return html.replace( + /style\s*=\s*"([^"]*)"/gi, + (_match, body: string) => { + const cleaned = body + .split(";") + .map((decl) => decl.trim()) + .filter((decl) => { + if (!decl) return false; + const prop = decl.split(":")[0]?.trim().toLowerCase(); + return ( + prop !== "color" && + prop !== "background" && + prop !== "background-color" && + prop !== "border-color" && + prop !== "border-bottom-color" && + prop !== "border-top-color" && + prop !== "border-left-color" && + prop !== "border-right-color" + ); + }) + .join("; "); + return cleaned ? `style="${cleaned}"` : ""; + }, + ); + } + private _tableToMarkdown(table: string[][]): string { if (table.length === 0) return ""; const rows: string[] = []; From 9943898af259dd347c56d8c36beb8c8190add040 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 14:41:59 -0700 Subject: [PATCH 018/129] Restyle chat to match shell layout: avatars + header above bubble MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructured the message DOM so each message is a row with: - Avatar (circular, 28px) on the outside (right for user, left for agent) - Header above the bubble showing source · timestamp in dim text - Bubble itself contains only the message content Agent avatars use a per-source emoji (browser→🌐, calendar→📅, etc.) falling back to the source's first letter. User avatar shows 'T' for now (will become a real username later). Other tweaks: - Bubbles get larger border-radius (12px) with a small flat corner on the side closest to the avatar, like the shell - Timestamp format now includes seconds (matches shell '2:14:24 PM') - System/error messages override the new flex layout to keep their centered block style Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 123 +++++++++++++----- .../typeagent-shell/src/webview/chatUI.ts | 113 +++++++++++++--- 2 files changed, 187 insertions(+), 49 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 66489d9cad..e36d0c878c 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -67,27 +67,110 @@ body { } .message { + display: flex; + align-items: flex-start; + gap: 8px; + max-width: 100%; + line-height: 1.4; +} + +.message.user { + flex-direction: row-reverse; +} + +.message.agent { + flex-direction: row; +} + +.message-body { + display: flex; + flex-direction: column; + min-width: 0; + max-width: calc(100% - 36px); +} + +.message.user .message-body { + align-items: flex-end; +} + +.message.agent .message-body { + align-items: flex-start; +} + +.message-header { + display: flex; + gap: 6px; + align-items: baseline; + font-size: 10.5px; + color: var(--vscode-descriptionForeground); + margin-bottom: 2px; + padding: 0 4px; + user-select: none; +} + +.message.user .message-header { + flex-direction: row-reverse; +} + +.source-label { + font-weight: 600; +} + +.message-timestamp { + opacity: 0.7; +} + +.message.user .message-timestamp::before, +.message.agent .message-timestamp::before { + content: "· "; + opacity: 0.5; +} + +/* The actual chat bubble */ +.bubble { padding: 8px 12px; - border-radius: 8px; - max-width: 85%; + border-radius: 12px; word-wrap: break-word; white-space: pre-wrap; - line-height: 1.4; + max-width: 100%; } -.message.user { - align-self: flex-end; +.message.user .bubble { background: var(--vscode-button-background); color: var(--vscode-button-foreground); - border-bottom-right-radius: 2px; + border-top-right-radius: 4px; } -.message.agent { - align-self: flex-start; +.message.agent .bubble { background: var(--vscode-input-background); color: var(--vscode-foreground); border: 1px solid var(--vscode-input-border, transparent); - border-bottom-left-radius: 2px; + border-top-left-radius: 4px; +} + +/* Avatars */ +.avatar { + flex: 0 0 auto; + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 600; + user-select: none; + margin-top: 18px; /* aligns with bubble, accounting for header */ +} + +.avatar-user { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); +} + +.avatar-agent { + background: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); } .message.agent.partial { @@ -100,19 +183,6 @@ body { filter: grayscale(0.4); } -/* Per-bubble timestamp shown above the content */ -.message-timestamp { - display: block; - font-size: 10px; - opacity: 0.6; - margin-bottom: 2px; - user-select: none; -} - -.message.user .message-timestamp { - text-align: right; -} - .message-content { display: block; } @@ -124,6 +194,7 @@ body { font-style: italic; background: none; padding: 4px 8px; + display: block; } .message.error { @@ -133,15 +204,7 @@ body { background: var(--vscode-inputValidation-errorBackground, rgba(255, 0, 0, 0.1)); border: 1px solid var(--vscode-inputValidation-errorBorder, var(--vscode-errorForeground)); padding: 6px 10px; -} - -.source-label { display: block; - font-size: 10px; - font-weight: 600; - color: var(--vscode-descriptionForeground); - margin-bottom: 2px; - text-transform: uppercase; } /* ── Input area ──────────────────────────────────────────────── */ diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 372efe8e14..827383a73e 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -84,15 +84,28 @@ export class ChatUI { this._removeTemporary(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - const el = document.createElement("div"); - el.className = "message user"; - const tsEl = this._createTimestampEl(timestamp); - el.appendChild(tsEl); + + const row = document.createElement("div"); + row.className = "message user"; + + const body = document.createElement("div"); + body.className = "message-body"; + + const header = this._createHeader("you", timestamp); + body.appendChild(header); + + const bubble = document.createElement("div"); + bubble.className = "bubble"; const textEl = document.createElement("span"); textEl.className = "message-content"; textEl.textContent = text; - el.appendChild(textEl); - this._messagesEl.appendChild(el); + bubble.appendChild(textEl); + body.appendChild(bubble); + + row.appendChild(body); + row.appendChild(this._createAvatar("user")); + + this._messagesEl.appendChild(row); this._scrollToBottom(); } @@ -353,23 +366,84 @@ export class ChatUI { source?: string, timestamp?: number, ): HTMLElement { - const el = document.createElement("div"); - el.className = "message agent"; - const tsEl = this._createTimestampEl(timestamp); - el.appendChild(tsEl); - if (source) { - const sourceEl = document.createElement("span"); - sourceEl.className = "source-label"; - sourceEl.textContent = source; - el.appendChild(sourceEl); - } + const row = document.createElement("div"); + row.className = "message agent"; + + row.appendChild(this._createAvatar("agent", source)); + + const body = document.createElement("div"); + body.className = "message-body"; + + const header = this._createHeader(source ?? "", timestamp); + body.appendChild(header); + + const bubble = document.createElement("div"); + bubble.className = "bubble"; const contentEl = document.createElement("span"); contentEl.className = "agent-content"; - el.appendChild(contentEl); - this._messagesEl.appendChild(el); + bubble.appendChild(contentEl); + body.appendChild(bubble); + + row.appendChild(body); + this._messagesEl.appendChild(row); + return row; + } + + private _createHeader( + label: string, + timestamp?: number, + ): HTMLElement { + const header = document.createElement("div"); + header.className = "message-header"; + if (label) { + const labelEl = document.createElement("span"); + labelEl.className = "source-label"; + labelEl.textContent = label; + header.appendChild(labelEl); + } + const tsEl = this._createTimestampEl(timestamp); + header.appendChild(tsEl); + return header; + } + + private _createAvatar( + kind: "user" | "agent", + source?: string, + ): HTMLElement { + const el = document.createElement("div"); + el.className = `avatar avatar-${kind}`; + if (kind === "user") { + el.textContent = "T"; + } else { + el.textContent = this._avatarForSource(source); + } return el; } + private _avatarForSource(source?: string): string { + if (!source) return "✦"; + const root = source.split(".")[0].toLowerCase(); + const map: Record = { + browser: "🌐", + calendar: "📅", + chat: "💬", + code: "💻", + desktop: "🖥", + email: "✉", + list: "📋", + markdown: "📝", + montage: "🖼", + music: "🎵", + photo: "📷", + spelunker: "⛏", + system: "⚙", + turtle: "🐢", + weather: "☀", + word: "📄", + }; + return map[root] ?? root.charAt(0).toUpperCase(); + } + private _createTimestampEl(timestamp?: number): HTMLElement { const ts = timestamp ?? Date.now(); const el = document.createElement("span"); @@ -388,8 +462,9 @@ export class ChatUI { const d = new Date(timestamp); const now = new Date(); const time = d.toLocaleTimeString([], { - hour: "2-digit", + hour: "numeric", minute: "2-digit", + second: "2-digit", }); const sameDay = d.getFullYear() === now.getFullYear() && From 7701a2b56844e86c79f16e7bb449a7cc1c02c879 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 15:02:44 -0700 Subject: [PATCH 019/129] Add user status icons, real username, and per-agent emojis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - User bubbles now show a 🪶 (sent) icon when posted and flip to ✅ (done) when the matching commandComplete arrives. Replayed history user messages render with ✅ since they already finished. - Bridge sends a 'userInfo' message (from os.userInfo().username) on webview registration; ChatUI uses it for the user avatar initial and the bubble header label instead of the hard-coded 'T'/'you'. - Expanded the agent emoji map to mirror the values declared in each agent's manifest (browser 🌐, calendar 📅, code ⚛, github-cli 🐙, weather ⛅, etc.), so agent avatars match what the Electron shell shows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 10 +++ .../typeagent-shell/src/agentServerBridge.ts | 12 +++ .../typeagent-shell/src/webview/chatUI.ts | 88 ++++++++++++++++--- .../typeagent-shell/src/webview/main.ts | 3 + 4 files changed, 99 insertions(+), 14 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index e36d0c878c..6a2396a6f1 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -187,6 +187,16 @@ body { display: block; } +/* Status icon inside user bubble (sent / done) */ +.status-icon { + display: inline-block; + margin-left: 6px; + font-size: 11px; + opacity: 0.8; + vertical-align: baseline; + user-select: none; +} + .message.system { align-self: center; font-size: 11px; diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 89f86a1a39..6a36bd812d 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as vscode from "vscode"; +import * as os from "os"; import { connectAgentServer } from "@typeagent/agent-server-client"; import type { AgentServerConnection, @@ -49,6 +50,7 @@ export type BridgeToWebviewMessage = | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string } | { type: "switching"; switching: boolean; targetName?: string } + | { type: "userInfo"; name: string } | { type: "historyReplay"; entries: Array<{ @@ -116,6 +118,16 @@ export class AgentServerBridge { this.handleWebviewMessage(msg, webview), ); + // Send local user info so bubbles can show a real name/initial + try { + const name = os.userInfo().username; + if (name) { + this.postToWebview(webview, { type: "userInfo", name }); + } + } catch { + // os.userInfo() can throw on some configurations; ignore + } + // Send current status this.postToWebview(webview, { type: "status", diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 827383a73e..3c34bface5 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -49,6 +49,13 @@ export class ChatUI { // Track switching state to keep input disabled private _isSwitching = false; + // Display name + initial for the local user (set via setUserInfo) + private _userName = "you"; + private _userInitial = "T"; + + // Queue of live user bubbles awaiting a commandComplete to mark done + private _pendingUserBubbles: HTMLElement[] = []; + constructor() { this._messagesEl = document.getElementById("messages")!; this._inputEl = document.getElementById( @@ -79,7 +86,21 @@ export class ChatUI { this._sendCallback = callback; } - public addUserMessage(text: string, timestamp?: number): void { + /** + * Set the local user's display name and avatar initial. Called once + * from the host with the OS username; safe to call again to update. + */ + public setUserInfo(name: string): void { + if (!name) return; + this._userName = name; + this._userInitial = name.trim().charAt(0).toUpperCase() || "?"; + } + + public addUserMessage( + text: string, + timestamp?: number, + status: "pending" | "done" = "done", + ): void { this._removeStatusIndicator(); this._removeTemporary(); this._activeResponseEl = undefined; @@ -91,7 +112,7 @@ export class ChatUI { const body = document.createElement("div"); body.className = "message-body"; - const header = this._createHeader("you", timestamp); + const header = this._createHeader(this._userName, timestamp); body.appendChild(header); const bubble = document.createElement("div"); @@ -100,12 +121,22 @@ export class ChatUI { textEl.className = "message-content"; textEl.textContent = text; bubble.appendChild(textEl); + + const statusEl = document.createElement("span"); + statusEl.className = "status-icon"; + statusEl.textContent = status === "pending" ? "🪶" : "✅"; + statusEl.title = status === "pending" ? "Sent" : "Done"; + bubble.appendChild(statusEl); + body.appendChild(bubble); row.appendChild(body); row.appendChild(this._createAvatar("user")); this._messagesEl.appendChild(row); + if (status === "pending") { + this._pendingUserBubbles.push(row); + } this._scrollToBottom(); } @@ -204,6 +235,7 @@ export class ChatUI { this._messagesEl.innerHTML = ""; this._activeResponseEl = undefined; this._statusIndicatorEl = undefined; + this._pendingUserBubbles = []; } /** @@ -215,6 +247,15 @@ export class ChatUI { this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + // Mark the oldest pending user bubble as done + const pending = this._pendingUserBubbles.shift(); + if (pending) { + const icon = pending.querySelector(".status-icon"); + if (icon) { + icon.textContent = "✅"; + (icon as HTMLElement).title = "Done"; + } + } } public addNotification(event: string, _data: any, source: string): void { @@ -356,7 +397,7 @@ export class ChatUI { const text = this._inputEl.value.trim(); if (!text) return; // Show user message immediately (don't wait for server echo) - this.addUserMessage(text); + this.addUserMessage(text, undefined, "pending"); this._inputEl.value = ""; this._inputEl.style.height = "auto"; this._sendCallback?.(text); @@ -413,9 +454,11 @@ export class ChatUI { const el = document.createElement("div"); el.className = `avatar avatar-${kind}`; if (kind === "user") { - el.textContent = "T"; + el.textContent = this._userInitial; + el.title = this._userName; } else { el.textContent = this._avatarForSource(source); + if (source) el.title = source; } return el; } @@ -423,23 +466,40 @@ export class ChatUI { private _avatarForSource(source?: string): string { if (!source) return "✦"; const root = source.split(".")[0].toLowerCase(); + // Emoji per agent, sourced from the manifest emojiChar values + // (ts/packages/agents/*/src/*Manifest.json). const map: Record = { + androidmobile: "📱", browser: "🌐", calendar: "📅", chat: "💬", - code: "💻", - desktop: "🖥", - email: "✉", - list: "📋", - markdown: "📝", - montage: "🖼", - music: "🎵", + code: "⚛️", + desktop: "🪟", + email: "📩", + "github-cli": "🐙", + greeting: "🖐️", + image: "🖼️", + list: "📝", + markdown: "🗎", + montage: "🎞", + onboarding: "🛠️", photo: "📷", - spelunker: "⛏", - system: "⚙", + player: "🎧", + localplayer: "🎵", + music: "🎵", + scriptflow: "🔁", + settings: "⚙️", + taskflow: "📜", + test: "➕", turtle: "🐢", - weather: "☀", + utility: "🔧", + video: "📹", + weather: "⛅", word: "📄", + spelunker: "⛏", + system: "⚙", + shell: "🐚", + dispatcher: "🤖", }; return map[root] ?? root.charAt(0).toUpperCase(); } diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 175d45a09e..a50d2de218 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -23,6 +23,9 @@ window.addEventListener("message", (event) => { case "status": chatUI.setStatus(msg.connected, msg.sessionId, msg.sessionName); break; + case "userInfo": + chatUI.setUserInfo(msg.name); + break; case "sessionChanged": chatUI.onSessionChanged(msg.sessionName); break; From 027bc464648c325c054aff7cfe2ebe4b651c1636 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 15:05:57 -0700 Subject: [PATCH 020/129] Thread requestId through send/complete to mark correct user bubble MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The webview now generates a unique requestId for each send, tags the pending user bubble with it (data-request-id), and passes it to the extension host. The host forwards it to dispatcher.processCommand and echoes it back in commandComplete. The webview maps requestId → bubble in a Map and flips the matching bubble's icon to ✅ on completion, so concurrent or out-of-order completions are handled correctly. Falls back to FIFO if a requestId is missing. Also send commandComplete on the error path so the bubble doesn't stay stuck on 🪶 if dispatch throws. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 20 ++++++-- .../typeagent-shell/src/webview/chatUI.ts | 48 +++++++++++++------ .../typeagent-shell/src/webview/main.ts | 6 +-- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 6a36bd812d..544ee7b6f4 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -546,7 +546,7 @@ export class AgentServerBridge { ): Promise { switch (msg.type) { case "sendCommand": - await this.sendCommand(msg.command); + await this.sendCommand(msg.command, msg.requestId); break; case "connect": await this.connect(); @@ -564,7 +564,10 @@ export class AgentServerBridge { } } - private async sendCommand(command: string): Promise { + private async sendCommand( + command: string, + requestId?: string, + ): Promise { if (!this.session) { this.broadcastToWebviews({ type: "error", @@ -574,15 +577,22 @@ export class AgentServerBridge { } try { - const result = - await this.session.dispatcher.processCommand(command); + const result = await this.session.dispatcher.processCommand( + command, + requestId, + ); // Command finished — tell webview to clean up temporary status this.broadcastToWebviews({ type: "commandComplete", - requestId: "", + requestId: requestId ?? "", result: result ?? null, }); } catch (e: any) { + this.broadcastToWebviews({ + type: "commandComplete", + requestId: requestId ?? "", + result: null, + }); this.broadcastToWebviews({ type: "error", message: e?.message ?? String(e), diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 3c34bface5..a12e0013c6 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -37,7 +37,7 @@ export class ChatUI { private _inputEl: HTMLTextAreaElement; private _sendBtn: HTMLButtonElement; private _statusEl: HTMLElement; - private _sendCallback?: (text: string) => void; + private _sendCallback?: (text: string, requestId: string) => void; // Track the active response bubble for setDisplay/appendDisplay private _activeResponseEl?: HTMLElement; @@ -53,8 +53,11 @@ export class ChatUI { private _userName = "you"; private _userInitial = "T"; - // Queue of live user bubbles awaiting a commandComplete to mark done - private _pendingUserBubbles: HTMLElement[] = []; + // Map from requestId → user bubble element awaiting commandComplete + private _pendingUserBubbles = new Map(); + + // Counter for generating unique request IDs + private _nextRequestId = 1; constructor() { this._messagesEl = document.getElementById("messages")!; @@ -82,7 +85,7 @@ export class ChatUI { }); } - public onSend(callback: (text: string) => void): void { + public onSend(callback: (text: string, requestId: string) => void): void { this._sendCallback = callback; } @@ -100,6 +103,7 @@ export class ChatUI { text: string, timestamp?: number, status: "pending" | "done" = "done", + requestId?: string, ): void { this._removeStatusIndicator(); this._removeTemporary(); @@ -108,6 +112,9 @@ export class ChatUI { const row = document.createElement("div"); row.className = "message user"; + if (requestId) { + row.dataset.requestId = requestId; + } const body = document.createElement("div"); body.className = "message-body"; @@ -134,8 +141,8 @@ export class ChatUI { row.appendChild(this._createAvatar("user")); this._messagesEl.appendChild(row); - if (status === "pending") { - this._pendingUserBubbles.push(row); + if (status === "pending" && requestId) { + this._pendingUserBubbles.set(requestId, row); } this._scrollToBottom(); } @@ -235,22 +242,34 @@ export class ChatUI { this._messagesEl.innerHTML = ""; this._activeResponseEl = undefined; this._statusIndicatorEl = undefined; - this._pendingUserBubbles = []; + this._pendingUserBubbles.clear(); } /** * Called when a command finishes processing. * Cleans up temporary status messages. */ - public onCommandComplete(): void { + public onCommandComplete(requestId?: string): void { this._removeTemporary(); this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - // Mark the oldest pending user bubble as done - const pending = this._pendingUserBubbles.shift(); - if (pending) { - const icon = pending.querySelector(".status-icon"); + + // Mark the matching pending user bubble as done. + let bubble: HTMLElement | undefined; + if (requestId && this._pendingUserBubbles.has(requestId)) { + bubble = this._pendingUserBubbles.get(requestId); + this._pendingUserBubbles.delete(requestId); + } else if (!requestId && this._pendingUserBubbles.size > 0) { + // Fallback for missing requestId — pop the oldest pending one + const firstKey = this._pendingUserBubbles.keys().next().value; + if (firstKey !== undefined) { + bubble = this._pendingUserBubbles.get(firstKey); + this._pendingUserBubbles.delete(firstKey); + } + } + if (bubble) { + const icon = bubble.querySelector(".status-icon"); if (icon) { icon.textContent = "✅"; (icon as HTMLElement).title = "Done"; @@ -396,11 +415,12 @@ export class ChatUI { private _handleSend(): void { const text = this._inputEl.value.trim(); if (!text) return; + const requestId = `webview-${this._nextRequestId++}-${Date.now()}`; // Show user message immediately (don't wait for server echo) - this.addUserMessage(text, undefined, "pending"); + this.addUserMessage(text, undefined, "pending", requestId); this._inputEl.value = ""; this._inputEl.style.height = "auto"; - this._sendCallback?.(text); + this._sendCallback?.(text, requestId); } private _createAgentBubble( diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index a50d2de218..d62aa73487 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -65,7 +65,7 @@ window.addEventListener("message", (event) => { break; case "commandComplete": // Command finished — clean up any remaining temporary status - chatUI.onCommandComplete(); + chatUI.onCommandComplete(msg.requestId); break; case "switching": chatUI.setSwitching(msg.switching, msg.targetName); @@ -77,8 +77,8 @@ window.addEventListener("message", (event) => { }); // Wire up the send button and input -chatUI.onSend((text) => { - vscode.postMessage({ type: "sendCommand", command: text }); +chatUI.onSend((text, requestId) => { + vscode.postMessage({ type: "sendCommand", command: text, requestId }); }); // Ask the extension host to connect From e86793aaa430658d21519d57f8de3951bdcd8480 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Wed, 22 Apr 2026 15:13:23 -0700 Subject: [PATCH 021/129] Replace user-bubble checkmark with shell-style roadrunner icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Electron shell shows a roadrunner SVG inside the user bubble that reflects translation/cache state (not request completion). Match that: - Embed the same roadrunner SVG from the shell's icon.ts and render it hidden inside each new live user bubble. - Forward 'explained' notify events through the bridge (now carrying requestId) and surface them in the webview: green if served from a named cache, gold if translated by the model, light blue if there was nothing to cache. The tooltip mirrors the shell's 'Translated by X. Explained at Y' phrasing. - 'grammarRule' notify with success=false recolors the icon to cornflowerblue and appends the reason to the tooltip. - onCommandComplete no longer flips an icon — completion is unrelated to translation state — but still cleans up the pending-bubble map. - onNotify returns whether the event was consumed so we don't also surface it as a noisy system message. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 17 ++- .../typeagent-shell/src/agentServerBridge.ts | 5 +- .../typeagent-shell/src/webview/chatUI.ts | 114 ++++++++++++++---- .../typeagent-shell/src/webview/main.ts | 7 +- 4 files changed, 113 insertions(+), 30 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 6a2396a6f1..348212543e 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -187,14 +187,23 @@ body { display: block; } -/* Status icon inside user bubble (sent / done) */ +/* Status icon inside user bubble (roadrunner: translation/cache state) */ .status-icon { - display: inline-block; + display: inline-flex; + align-items: center; margin-left: 6px; - font-size: 11px; - opacity: 0.8; vertical-align: baseline; user-select: none; + color: var(--vscode-descriptionForeground); + cursor: help; +} +.status-icon.hidden { + visibility: hidden; +} +.status-icon svg { + width: 14px; + height: 14px; + display: block; } .message.system { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 544ee7b6f4..ab8d6033d5 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -45,7 +45,7 @@ export type BridgeToWebviewMessage = timestamp?: number; } | { type: "clear"; requestId: RequestId } - | { type: "notify"; event: string; data: any; source: string; seq?: number } + | { type: "notify"; event: string; data: any; source: string; seq?: number; requestId?: RequestId } | { type: "commandResult"; requestId: string; result: any } | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string } @@ -689,7 +689,7 @@ export class AgentServerBridge { appendDiagnosticData: () => {}, setDynamicDisplay: () => {}, notify: ( - _notificationId: string | RequestId | undefined, + notificationId: string | RequestId | undefined, event: string, data: any, source: string, @@ -701,6 +701,7 @@ export class AgentServerBridge { data, source, seq, + requestId: notificationId as RequestId | undefined, }); }, requestChoice: () => {}, diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index a12e0013c6..f795e5f789 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -28,6 +28,11 @@ const purifyConfig = { /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, }; +// Roadrunner icon (translation/cache state indicator), copied from the +// Electron shell's icon.ts so the extension matches its visual language. +// The fill is set via `currentColor` so we can recolor it via CSS class. +const ROADRUNNER_SVG = ``; + /** * Manages the chat UI elements in the webview. * Groups messages by requestId to match the shell's behavior. @@ -129,11 +134,17 @@ export class ChatUI { textEl.textContent = text; bubble.appendChild(textEl); - const statusEl = document.createElement("span"); - statusEl.className = "status-icon"; - statusEl.textContent = status === "pending" ? "🪶" : "✅"; - statusEl.title = status === "pending" ? "Sent" : "Done"; - bubble.appendChild(statusEl); + // Roadrunner: hidden until an "explained" notify arrives. Mirrors + // the Electron shell's translation/cache indicator. For replayed + // messages we just leave it hidden — we don't replay notify events. + const icon = document.createElement("span"); + icon.className = "status-icon roadrunner"; + icon.innerHTML = ROADRUNNER_SVG; + if (status !== "pending") { + icon.classList.add("hidden"); + } + icon.title = "Waiting for translation…"; + bubble.appendChild(icon); body.appendChild(bubble); @@ -254,26 +265,83 @@ export class ChatUI { this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - - // Mark the matching pending user bubble as done. - let bubble: HTMLElement | undefined; - if (requestId && this._pendingUserBubbles.has(requestId)) { - bubble = this._pendingUserBubbles.get(requestId); + // Note: we deliberately don't flip the roadrunner here. The + // roadrunner reflects translation/cache state, which is signaled + // separately via the "explained" notify event (see onNotify). + if (requestId) { this._pendingUserBubbles.delete(requestId); - } else if (!requestId && this._pendingUserBubbles.size > 0) { - // Fallback for missing requestId — pop the oldest pending one - const firstKey = this._pendingUserBubbles.keys().next().value; - if (firstKey !== undefined) { - bubble = this._pendingUserBubbles.get(firstKey); - this._pendingUserBubbles.delete(firstKey); - } } - if (bubble) { - const icon = bubble.querySelector(".status-icon"); - if (icon) { - icon.textContent = "✅"; - (icon as HTMLElement).title = "Done"; - } + } + + /** + * Handle a clientIO notify event. Returns true if the event was + * consumed (and so should NOT be shown as a system message). + */ + public onNotify( + event: string, + data: any, + _source: string, + requestId?: string, + ): boolean { + if (event === "explained") { + this._applyExplained(requestId, data); + return true; + } + if (event === "grammarRule") { + this._applyGrammarRule(requestId, data); + return true; + } + return false; + } + + private _findUserBubble(requestId?: string): HTMLElement | undefined { + if (!requestId) return undefined; + if (this._pendingUserBubbles.has(requestId)) { + return this._pendingUserBubbles.get(requestId); + } + const sel = `.message.user[data-request-id="${CSS.escape(requestId)}"]`; + return (this._messagesEl.querySelector(sel) as HTMLElement) ?? undefined; + } + + private _applyExplained(requestId: string | undefined, data: any): void { + const bubble = this._findUserBubble(requestId); + if (!bubble) return; + const icon = bubble.querySelector(".roadrunner") as HTMLElement | null; + if (!icon) return; + + const fromCache: string | undefined = data?.fromCache; + const time: string | undefined = data?.time; + const error: string | undefined = data?.error; + + const cachePart = fromCache + ? `Translated by ${fromCache}` + : "Translated by model"; + let tooltip: string; + let colorVar: string; + if (error === undefined) { + tooltip = `${cachePart}. Explained at ${time ?? "now"}`; + // green = cache hit, gold = model translation + colorVar = fromCache ? "#00c000" : "#c0c000"; + } else { + tooltip = `${cachePart}. Nothing to put in cache: ${error}`; + colorVar = "lightblue"; + } + + icon.classList.remove("hidden"); + icon.style.color = colorVar; + icon.title = tooltip; + } + + private _applyGrammarRule(requestId: string | undefined, data: any): void { + const bubble = this._findUserBubble(requestId); + if (!bubble) return; + const icon = bubble.querySelector(".roadrunner") as HTMLElement | null; + if (!icon) return; + if (data?.success === false) { + icon.style.color = "cornflowerblue"; + const existing = icon.title || ""; + const reason = data?.message ? ` (${data.message})` : ""; + icon.title = `${existing}. No fast-path cached${reason}`; } } diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index d62aa73487..d6f08f1b9c 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -55,7 +55,12 @@ window.addEventListener("message", (event) => { chatUI.clearMessages(); break; case "notify": - chatUI.addNotification(msg.event, msg.data, msg.source); + // Let the chat UI consume status notifications (explained, + // grammarRule). Anything it doesn't handle becomes a visible + // system message. + if (!chatUI.onNotify(msg.event, msg.data, msg.source, msg.requestId)) { + chatUI.addNotification(msg.event, msg.data, msg.source); + } break; case "error": chatUI.addErrorMessage(msg.message); From 8e96891bfa98ac631919ab5ef32873c28073b31c Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:07:18 -0700 Subject: [PATCH 022/129] Fix roadrunner: extract clientRequestId from RequestId object Two bugs were keeping the roadrunner stuck on the default grey: 1. RequestId is actually an object {requestId, clientRequestId}, not a string. The notify forwarder was passing the whole object as the bridge message's requestId, so the webview's string lookup never matched any bubble. Extract clientRequestId in the bridge before broadcasting so the webview can match it against the data-request-id it set on the bubble. 2. The icon sat below the bubble text on its own line. Position it absolutely in the top-right corner of the bubble (matching the Electron shell's '.chat-message-explained-icon' style), reserve right padding so it doesn't overlap the message text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 12 +++++++++--- .../typeagent-shell/src/agentServerBridge.ts | 13 +++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 348212543e..c1d7a47eba 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -128,6 +128,7 @@ body { /* The actual chat bubble */ .bubble { + position: relative; padding: 8px 12px; border-radius: 12px; word-wrap: break-word; @@ -139,6 +140,8 @@ body { background: var(--vscode-button-background); color: var(--vscode-button-foreground); border-top-right-radius: 4px; + /* Reserve space in the top-right corner for the roadrunner icon */ + padding-right: 24px; } .message.agent .bubble { @@ -187,15 +190,18 @@ body { display: block; } -/* Status icon inside user bubble (roadrunner: translation/cache state) */ +/* Status icon inside user bubble (roadrunner: translation/cache state). + Positioned in the top-right corner of the bubble like the Electron shell. */ .status-icon { + position: absolute; + top: 4px; + right: 6px; display: inline-flex; align-items: center; - margin-left: 6px; - vertical-align: baseline; user-select: none; color: var(--vscode-descriptionForeground); cursor: help; + line-height: 0; } .status-icon.hidden { visibility: hidden; diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index ab8d6033d5..75915fe8ec 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -45,7 +45,7 @@ export type BridgeToWebviewMessage = timestamp?: number; } | { type: "clear"; requestId: RequestId } - | { type: "notify"; event: string; data: any; source: string; seq?: number; requestId?: RequestId } + | { type: "notify"; event: string; data: any; source: string; seq?: number; requestId?: string } | { type: "commandResult"; requestId: string; result: any } | { type: "commandComplete"; requestId: string; result: any } | { type: "error"; message: string } @@ -695,13 +695,22 @@ export class AgentServerBridge { source: string, seq?: number, ) => { + // RequestId is an object {requestId, clientRequestId}; + // we tag bubbles by the clientRequestId we generated. + let clientRequestId: string | undefined; + if (typeof notificationId === "string") { + clientRequestId = notificationId; + } else if (notificationId && typeof notificationId === "object") { + clientRequestId = (notificationId as any) + .clientRequestId as string | undefined; + } this.broadcastToWebviews({ type: "notify", event, data, source, seq, - requestId: notificationId as RequestId | undefined, + requestId: clientRequestId, }); }, requestChoice: () => {}, From e41e09771d6f050fed96efcc23f1c9e647308865 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:11:00 -0700 Subject: [PATCH 023/129] Hide roadrunner until 'explained' notify arrives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roadrunner is only meaningful for action translations: pure chat responses go through the chat agent and never emit an 'explained' event, so the icon was sitting permanently grey on those bubbles showing the misleading default 'Waiting for translation…' tooltip. Start hidden and only reveal it when _applyExplained sets a real color and tooltip. Bubbles for chat-only requests now show no icon at all, matching the actual state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/webview/chatUI.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index f795e5f789..ae409a53c5 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -134,16 +134,13 @@ export class ChatUI { textEl.textContent = text; bubble.appendChild(textEl); - // Roadrunner: hidden until an "explained" notify arrives. Mirrors - // the Electron shell's translation/cache indicator. For replayed - // messages we just leave it hidden — we don't replay notify events. + // Roadrunner: hidden until an "explained" notify arrives (which + // only fires for action translations — pure chat responses never + // emit it, so the icon stays hidden for those). Mirrors the + // Electron shell's translation/cache indicator. const icon = document.createElement("span"); - icon.className = "status-icon roadrunner"; + icon.className = "status-icon roadrunner hidden"; icon.innerHTML = ROADRUNNER_SVG; - if (status !== "pending") { - icon.classList.add("hidden"); - } - icon.title = "Waiting for translation…"; bubble.appendChild(icon); body.appendChild(bubble); From 9ea1ba906fba28dc42f436998ffffd5a05284053 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:20:50 -0700 Subject: [PATCH 024/129] Always show roadrunner; update tooltip when no translation occurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the roadrunner visible on every user bubble (so the layout is consistent), but use a richer tooltip lifecycle: - New live bubble: tooltip 'Translating…' (waiting for explained) - explained event arrives: color green/gold/blue + 'Translated by X. Explained at HH:MM:SS' (sets data-explained=true) - commandComplete arrives without explained (chat agent, system command, etc.): tooltip becomes 'No translation (handled directly)' and the icon stays muted Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index ae409a53c5..9891418879 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -134,13 +134,15 @@ export class ChatUI { textEl.textContent = text; bubble.appendChild(textEl); - // Roadrunner: hidden until an "explained" notify arrives (which - // only fires for action translations — pure chat responses never - // emit it, so the icon stays hidden for those). Mirrors the - // Electron shell's translation/cache indicator. + // Roadrunner: starts grey with a "translating…" tooltip, gets + // recolored + retooltipped when an "explained" notify arrives + // (for action translations) and falls back to a sensible + // "no translation" tooltip on commandComplete for pure chat + // responses that never emit "explained". const icon = document.createElement("span"); - icon.className = "status-icon roadrunner hidden"; + icon.className = "status-icon roadrunner"; icon.innerHTML = ROADRUNNER_SVG; + icon.title = "Translating…"; bubble.appendChild(icon); body.appendChild(bubble); @@ -262,10 +264,19 @@ export class ChatUI { this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - // Note: we deliberately don't flip the roadrunner here. The - // roadrunner reflects translation/cache state, which is signaled - // separately via the "explained" notify event (see onNotify). + + // If the bubble's roadrunner never received an "explained" event + // (e.g. a pure chat-agent response, or a system command like + // @config), update its tooltip to reflect that translation + // wasn't applicable. Color stays at the default muted color. if (requestId) { + const bubble = this._findUserBubble(requestId); + const icon = bubble?.querySelector( + ".roadrunner", + ) as HTMLElement | null; + if (icon && icon.dataset.explained !== "true") { + icon.title = "No translation (handled directly)"; + } this._pendingUserBubbles.delete(requestId); } } @@ -324,7 +335,7 @@ export class ChatUI { colorVar = "lightblue"; } - icon.classList.remove("hidden"); + icon.dataset.explained = "true"; icon.style.color = colorVar; icon.title = tooltip; } From b739bc9a6a2d2ad25290583abb9d7c77cbd88ce4 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:23:09 -0700 Subject: [PATCH 025/129] Revert "Always show roadrunner; update tooltip when no translation occurs" This reverts commit 9ea1ba906fba28dc42f436998ffffd5a05284053. --- .../typeagent-shell/src/webview/chatUI.ts | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 9891418879..ae409a53c5 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -134,15 +134,13 @@ export class ChatUI { textEl.textContent = text; bubble.appendChild(textEl); - // Roadrunner: starts grey with a "translating…" tooltip, gets - // recolored + retooltipped when an "explained" notify arrives - // (for action translations) and falls back to a sensible - // "no translation" tooltip on commandComplete for pure chat - // responses that never emit "explained". + // Roadrunner: hidden until an "explained" notify arrives (which + // only fires for action translations — pure chat responses never + // emit it, so the icon stays hidden for those). Mirrors the + // Electron shell's translation/cache indicator. const icon = document.createElement("span"); - icon.className = "status-icon roadrunner"; + icon.className = "status-icon roadrunner hidden"; icon.innerHTML = ROADRUNNER_SVG; - icon.title = "Translating…"; bubble.appendChild(icon); body.appendChild(bubble); @@ -264,19 +262,10 @@ export class ChatUI { this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - - // If the bubble's roadrunner never received an "explained" event - // (e.g. a pure chat-agent response, or a system command like - // @config), update its tooltip to reflect that translation - // wasn't applicable. Color stays at the default muted color. + // Note: we deliberately don't flip the roadrunner here. The + // roadrunner reflects translation/cache state, which is signaled + // separately via the "explained" notify event (see onNotify). if (requestId) { - const bubble = this._findUserBubble(requestId); - const icon = bubble?.querySelector( - ".roadrunner", - ) as HTMLElement | null; - if (icon && icon.dataset.explained !== "true") { - icon.title = "No translation (handled directly)"; - } this._pendingUserBubbles.delete(requestId); } } @@ -335,7 +324,7 @@ export class ChatUI { colorVar = "lightblue"; } - icon.dataset.explained = "true"; + icon.classList.remove("hidden"); icon.style.color = colorVar; icon.title = tooltip; } From 48fe0f9d78babc7ecf783798c7813822059c4b53 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:29:59 -0700 Subject: [PATCH 026/129] shell-chat: hover timing on bubbles + clickable action JSON - Forward clientRequestId on display events; tag agent bubbles by request id so setDisplayInfo and metrics target the right bubble. - setDisplayInfo now updates the agent bubble's source label to `schemaName.actionName` and makes it clickable to expand a pretty-printed JSON panel of the action payload (mirrors shell swapContent pattern). - onCommandComplete reads result.metrics (RequestMetrics) and applies a multi-line tooltip (Total / Parse / Command / per-action durations) to the user and agent bubbles for the request, viewable on hover. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 25 +++ .../typeagent-shell/src/webview/chatUI.ts | 168 ++++++++++++++---- .../typeagent-shell/src/webview/main.ts | 17 +- 3 files changed, 174 insertions(+), 36 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index c1d7a47eba..9decd51801 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -116,6 +116,31 @@ body { font-weight: 600; } +.source-label.clickable { + cursor: pointer; + text-decoration: underline dotted; + text-underline-offset: 2px; +} + +.source-label.clickable:hover { + color: var(--vscode-textLink-activeForeground); +} + +.action-json { + margin: 4px 0 0 0; + padding: 8px 10px; + background: var(--vscode-textCodeBlock-background, rgba(127, 127, 127, 0.15)); + border: 1px solid var(--vscode-widget-border, rgba(127, 127, 127, 0.3)); + border-radius: 6px; + font-family: var(--vscode-editor-font-family, monospace); + font-size: 11px; + line-height: 1.4; + color: var(--vscode-foreground); + overflow-x: auto; + white-space: pre; + max-height: 320px; +} + .message-timestamp { opacity: 0.7; } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index ae409a53c5..c8e20b1d0b 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -61,6 +61,10 @@ export class ChatUI { // Map from requestId → user bubble element awaiting commandComplete private _pendingUserBubbles = new Map(); + // Map from clientRequestId → the agent bubble for that request, so + // setDisplayInfo and commandComplete metrics can find their target. + private _agentBubblesByRequestId = new Map(); + // Counter for generating unique request IDs private _nextRequestId = 1; @@ -162,20 +166,18 @@ export class ChatUI { content: any, source?: string, timestamp?: number, + requestId?: string, ): void { this._removeStatusIndicator(); this._removeTemporary(); - if (!this._activeResponseEl) { - this._activeResponseEl = this._createAgentBubble(source, timestamp); - } + const bubble = this._getOrCreateAgentBubble(source, timestamp, requestId); if (source) { - const sourceEl = this._activeResponseEl.querySelector(".source-label"); - if (sourceEl) { + const sourceEl = bubble.querySelector(".source-label"); + if (sourceEl && !sourceEl.classList.contains("has-action")) { sourceEl.textContent = source; } } - const contentEl = - this._activeResponseEl.querySelector(".agent-content"); + const contentEl = bubble.querySelector(".agent-content"); if (contentEl) { contentEl.innerHTML = this._renderDisplayContent(content); } @@ -192,6 +194,7 @@ export class ChatUI { source?: string, mode?: string, timestamp?: number, + requestId?: string, ): void { this._removeStatusIndicator(); @@ -216,33 +219,92 @@ export class ChatUI { } this._lastAppendedContent = rendered; - if (!this._activeResponseEl) { - // Remove temporary when first real content arrives + // Remove temporary when first real content arrives + const hadBubble = + (requestId && this._agentBubblesByRequestId.has(requestId)) || + !!this._activeResponseEl; + if (!hadBubble) { this._removeTemporary(); - this._activeResponseEl = this._createAgentBubble(source, timestamp); } - const contentEl = - this._activeResponseEl.querySelector(".agent-content"); + const bubble = this._getOrCreateAgentBubble(source, timestamp, requestId); + const contentEl = bubble.querySelector(".agent-content"); if (contentEl) { contentEl.innerHTML += rendered; } this._scrollToBottom(); } + private _getOrCreateAgentBubble( + source?: string, + timestamp?: number, + requestId?: string, + ): HTMLElement { + if (requestId && this._agentBubblesByRequestId.has(requestId)) { + return this._agentBubblesByRequestId.get(requestId)!; + } + if (!requestId && this._activeResponseEl) { + return this._activeResponseEl; + } + const bubble = this._createAgentBubble(source, timestamp, requestId); + this._activeResponseEl = bubble; + if (requestId) { + this._agentBubblesByRequestId.set(requestId, bubble); + } + return bubble; + } + /** - * setDisplayInfo: show which agent is processing (status indicator). - * Replaces previous status indicator rather than accumulating. + * setDisplayInfo: update the agent bubble for this request to show + * the action that's being executed. The header label becomes a + * clickable "schemaName.actionName" — clicking it expands a JSON + * panel with the action payload. */ - public setDisplayInfo(source: string, action?: any): void { - if (!source) return; - - if (!this._statusIndicatorEl) { - this._statusIndicatorEl = document.createElement("div"); - this._statusIndicatorEl.className = "message system status-indicator"; - this._messagesEl.appendChild(this._statusIndicatorEl); + public setDisplayInfo( + source: string, + action?: any, + requestId?: string, + ): void { + if (!source && !action) return; + + const bubble = this._getOrCreateAgentBubble(source, undefined, requestId); + const label = bubble.querySelector(".source-label") as HTMLElement | null; + if (!label) return; + + let actionLabel: string | undefined; + if (Array.isArray(action)) { + actionLabel = `${source} ${action.join(" ")}`; + } else if (action && typeof action === "object" && action.actionName) { + actionLabel = action.schemaName + ? `${action.schemaName}.${action.actionName}` + : action.actionName; + } + + if (actionLabel) { + label.textContent = actionLabel; + label.classList.add("has-action", "clickable"); + label.title = "Click to view action JSON"; + + // Toggle a JSON panel below the bubble's content + const handler = () => { + const body = bubble.querySelector(".message-body"); + if (!body) return; + let pre = body.querySelector(".action-json") as HTMLElement | null; + if (pre) { + pre.remove(); + } else { + pre = document.createElement("pre"); + pre.className = "action-json"; + pre.textContent = JSON.stringify(action, null, 2); + body.appendChild(pre); + } + this._scrollToBottom(); + }; + // Replace any prior listener by cloning is overkill — store + // the handler on the element so we can detach if needed + label.onclick = handler; + } else if (source) { + label.textContent = source; } - const label = action ? `[${source}] processing...` : `[${source}] processing...`; - this._statusIndicatorEl.textContent = label; this._scrollToBottom(); } @@ -251,25 +313,63 @@ export class ChatUI { this._activeResponseEl = undefined; this._statusIndicatorEl = undefined; this._pendingUserBubbles.clear(); + this._agentBubblesByRequestId.clear(); } /** * Called when a command finishes processing. - * Cleans up temporary status messages. + * Cleans up temporary status messages and applies timing metrics + * (visible as a hover tooltip on the bubbles for this request). */ - public onCommandComplete(requestId?: string): void { + public onCommandComplete(requestId?: string, result?: any): void { this._removeTemporary(); this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; - // Note: we deliberately don't flip the roadrunner here. The - // roadrunner reflects translation/cache state, which is signaled - // separately via the "explained" notify event (see onNotify). + if (requestId) { + const tooltip = this._formatMetricsTooltip(result?.metrics); + if (tooltip) { + const userBubble = this._pendingUserBubbles.get(requestId); + if (userBubble) { + const b = userBubble.querySelector(".bubble") as HTMLElement | null; + if (b) b.title = tooltip; + } + const agentBubble = this._agentBubblesByRequestId.get(requestId); + if (agentBubble) { + const b = agentBubble.querySelector(".bubble") as HTMLElement | null; + if (b) b.title = tooltip; + } + } this._pendingUserBubbles.delete(requestId); + this._agentBubblesByRequestId.delete(requestId); } } + private _formatMetricsTooltip(metrics: any): string | undefined { + if (!metrics || typeof metrics !== "object") return undefined; + const fmt = (ms?: number) => + typeof ms === "number" ? `${ms.toFixed(0)}ms` : "—"; + const lines: string[] = []; + if (typeof metrics.duration === "number") { + lines.push(`Total: ${fmt(metrics.duration)}`); + } + if (metrics.parse?.duration != null) { + lines.push(` Parse: ${fmt(metrics.parse.duration)}`); + } + if (metrics.command?.duration != null) { + lines.push(` Command: ${fmt(metrics.command.duration)}`); + } + if (Array.isArray(metrics.actions) && metrics.actions.length > 0) { + metrics.actions.forEach((a: any, i: number) => { + if (a?.duration != null) { + lines.push(` Action ${i + 1}: ${fmt(a.duration)}`); + } + }); + } + return lines.length > 0 ? lines.join("\n") : undefined; + } + /** * Handle a clientIO notify event. Returns true if the event was * consumed (and so should NOT be shown as a system message). @@ -491,9 +591,11 @@ export class ChatUI { private _createAgentBubble( source?: string, timestamp?: number, + requestId?: string, ): HTMLElement { const row = document.createElement("div"); row.className = "message agent"; + if (requestId) row.dataset.requestId = requestId; row.appendChild(this._createAvatar("agent", source)); @@ -521,12 +623,10 @@ export class ChatUI { ): HTMLElement { const header = document.createElement("div"); header.className = "message-header"; - if (label) { - const labelEl = document.createElement("span"); - labelEl.className = "source-label"; - labelEl.textContent = label; - header.appendChild(labelEl); - } + const labelEl = document.createElement("span"); + labelEl.className = "source-label"; + if (label) labelEl.textContent = label; + header.appendChild(labelEl); const tsEl = this._createTimestampEl(timestamp); header.appendChild(tsEl); return header; diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index d6f08f1b9c..5d371f4627 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -17,6 +17,13 @@ const vscode = acquireVsCodeApi(); const chatUI = new ChatUI(); // Listen for messages from the extension host (bridged from agent server) +// Helper: pull clientRequestId out of a RequestId object/string. +function clientIdOf(requestId: any): string | undefined { + if (!requestId) return undefined; + if (typeof requestId === "string") return requestId; + return requestId.clientRequestId as string | undefined; +} + window.addEventListener("message", (event) => { const msg = event.data; switch (msg.type) { @@ -34,6 +41,7 @@ window.addEventListener("message", (event) => { msg.message.message, msg.message.source, msg.timestamp, + clientIdOf(msg.message.requestId), ); break; case "appendDisplay": @@ -42,6 +50,7 @@ window.addEventListener("message", (event) => { msg.message.source, msg.mode, msg.timestamp, + clientIdOf(msg.message.requestId), ); break; case "setUserRequest": @@ -49,7 +58,11 @@ window.addEventListener("message", (event) => { // History now comes through historyReplay batch, not here. break; case "setDisplayInfo": - chatUI.setDisplayInfo(msg.source, msg.action); + chatUI.setDisplayInfo( + msg.source, + msg.action, + clientIdOf(msg.requestId), + ); break; case "clear": chatUI.clearMessages(); @@ -70,7 +83,7 @@ window.addEventListener("message", (event) => { break; case "commandComplete": // Command finished — clean up any remaining temporary status - chatUI.onCommandComplete(msg.requestId); + chatUI.onCommandComplete(msg.requestId, msg.result); break; case "switching": chatUI.setSwitching(msg.switching, msg.targetName); From 75cbf464a8de5fe9c531b01f5d1a2b5cc9ccd9f7 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:39:35 -0700 Subject: [PATCH 027/129] shell-chat: inline shell-style metrics row + JSON syntax highlighting Replace the hover-tooltip metrics with a two-column row inside the agent bubble that mirrors the Electron shell: left: First Message: right: Action Elapsed Time: / Total Elapsed Time: Times are formatted reader-friendly (ms / s) like the shell. Add lightweight JSON syntax highlighting in the action JSON expand panel, using VS Code's symbolIcon token color variables (with sensible fallbacks) so keys, strings, numbers, booleans and null are color-coded just like in the editor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 47 ++++++ .../typeagent-shell/src/webview/chatUI.ts | 136 +++++++++++++----- 2 files changed, 151 insertions(+), 32 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 9decd51801..7557b88772 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -141,6 +141,53 @@ body { max-height: 320px; } +/* JSON syntax highlighting (uses VS Code token colors with fallbacks). */ +.action-json .json-key { + color: var(--vscode-symbolIcon-propertyForeground, #9cdcfe); +} +.action-json .json-string { + color: var(--vscode-symbolIcon-stringForeground, #ce9178); +} +.action-json .json-number { + color: var(--vscode-symbolIcon-numberForeground, #b5cea8); +} +.action-json .json-bool { + color: var(--vscode-symbolIcon-booleanForeground, #569cd6); + font-weight: 600; +} +.action-json .json-null { + color: var(--vscode-symbolIcon-nullForeground, #569cd6); + font-style: italic; +} +.action-json .json-punct { + color: var(--vscode-foreground); + opacity: 0.7; +} + +/* Inline metrics row at the bottom of an agent bubble. */ +.bubble-metrics { + display: flex; + justify-content: space-between; + gap: 16px; + margin-top: 4px; + padding: 0 4px; + font-size: 11px; + opacity: 0.75; +} + +.bubble-metrics .metrics-left { + text-align: left; +} + +.bubble-metrics .metrics-right { + text-align: right; +} + +.bubble-metrics b { + font-weight: 600; + opacity: 1; +} + .message-timestamp { opacity: 0.7; } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index c8e20b1d0b..e62c54a6e3 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -294,8 +294,16 @@ export class ChatUI { } else { pre = document.createElement("pre"); pre.className = "action-json"; - pre.textContent = JSON.stringify(action, null, 2); - body.appendChild(pre); + pre.innerHTML = ChatUI._highlightJson( + JSON.stringify(action, null, 2), + ); + // Insert before .bubble-metrics if present, else append + const metricsRow = body.querySelector(".bubble-metrics"); + if (metricsRow) { + body.insertBefore(pre, metricsRow); + } else { + body.appendChild(pre); + } } this._scrollToBottom(); }; @@ -328,46 +336,71 @@ export class ChatUI { this._lastAppendedContent = undefined; if (requestId) { - const tooltip = this._formatMetricsTooltip(result?.metrics); - if (tooltip) { - const userBubble = this._pendingUserBubbles.get(requestId); - if (userBubble) { - const b = userBubble.querySelector(".bubble") as HTMLElement | null; - if (b) b.title = tooltip; - } - const agentBubble = this._agentBubblesByRequestId.get(requestId); - if (agentBubble) { - const b = agentBubble.querySelector(".bubble") as HTMLElement | null; - if (b) b.title = tooltip; - } + const agentBubble = this._agentBubblesByRequestId.get(requestId); + if (agentBubble) { + this._renderMetrics(agentBubble, result?.metrics); } this._pendingUserBubbles.delete(requestId); this._agentBubblesByRequestId.delete(requestId); } } - private _formatMetricsTooltip(metrics: any): string | undefined { - if (!metrics || typeof metrics !== "object") return undefined; - const fmt = (ms?: number) => - typeof ms === "number" ? `${ms.toFixed(0)}ms` : "—"; - const lines: string[] = []; - if (typeof metrics.duration === "number") { - lines.push(`Total: ${fmt(metrics.duration)}`); - } + private _renderMetrics(bubbleRow: HTMLElement, metrics: any): void { + if (!metrics || typeof metrics !== "object") return; + + const body = bubbleRow.querySelector(".message-body"); + if (!body) return; + + // Remove any prior metrics row (idempotent) + body.querySelector(".bubble-metrics")?.remove(); + + const fmt = (ms?: number) => { + if (typeof ms !== "number") return undefined; + if (ms < 1) return `${ms.toFixed(3)}ms`; + if (ms > 1000) return `${(ms / 1000).toFixed(1)}s`; + return `${ms.toFixed(1)}ms`; + }; + const item = (label: string, value?: string) => + value ? `${label}: ${value}` : ""; + + const left: string[] = []; + const right: string[] = []; + if (metrics.parse?.duration != null) { - lines.push(` Parse: ${fmt(metrics.parse.duration)}`); - } - if (metrics.command?.duration != null) { - lines.push(` Command: ${fmt(metrics.command.duration)}`); + left.push(item("First Message", fmt(metrics.parse.duration))!); } + if (Array.isArray(metrics.actions) && metrics.actions.length > 0) { - metrics.actions.forEach((a: any, i: number) => { - if (a?.duration != null) { - lines.push(` Action ${i + 1}: ${fmt(a.duration)}`); - } - }); + const actionTotal = metrics.actions.reduce( + (acc: number, a: any) => + acc + (typeof a?.duration === "number" ? a.duration : 0), + 0, + ); + if (actionTotal > 0) { + right.push(item("Action Elapsed Time", fmt(actionTotal))!); + } + } else if (metrics.command?.duration != null) { + right.push( + item("Command Elapsed Time", fmt(metrics.command.duration))!, + ); + } + if (metrics.duration != null) { + right.push(item("Total Elapsed Time", fmt(metrics.duration))!); } - return lines.length > 0 ? lines.join("\n") : undefined; + + if (left.length === 0 && right.length === 0) return; + + const row = document.createElement("div"); + row.className = "bubble-metrics"; + const leftEl = document.createElement("div"); + leftEl.className = "metrics-left"; + leftEl.innerHTML = left.filter(Boolean).join("
"); + const rightEl = document.createElement("div"); + rightEl.className = "metrics-right"; + rightEl.innerHTML = right.filter(Boolean).join("
"); + row.append(leftEl, rightEl); + body.appendChild(row); + this._scrollToBottom(); } /** @@ -648,6 +681,45 @@ export class ChatUI { return el; } + private static _escapeHtml(s: string): string { + return s + .replace(/&/g, "&") + .replace(//g, ">"); + } + + /** + * Tokenize a JSON.stringify(obj, null, 2) string and wrap tokens in + * spans with classes (json-key, json-string, json-number, json-bool, + * json-null, json-punct) for CSS styling. + */ + private static _highlightJson(json: string): string { + const escaped = ChatUI._escapeHtml(json); + // Order matters: match strings (and following ":" => key) before numbers. + const re = + /("(?:\\.|[^"\\])*")(\s*:)?|\b(true|false)\b|\bnull\b|(-?\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?)/g; + return escaped.replace( + re, + ( + _m, + strLit?: string, + colon?: string, + bool?: string, + num?: string, + ) => { + if (strLit) { + if (colon) { + return `${strLit}${colon}`; + } + return `${strLit}`; + } + if (bool) return `${bool}`; + if (num) return `${num}`; + return `null`; + }, + ); + } + private _avatarForSource(source?: string): string { if (!source) return "✦"; const root = source.split(".")[0].toLowerCase(); From 325cba4b988ce272c08e728b130deb2e32a1d746 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:43:20 -0700 Subject: [PATCH 028/129] shell-chat: only show timing on bubble hover; brighter JSON tokens - .bubble-metrics is now hidden by default (opacity 0, max-height 0) and fades in only when the .message row is hovered, matching the user's expectation of a hover-revealed timing display. - Switch JSON token colors to the more reliably-defined VS Code --vscode-debugTokenExpression-* variables for higher visibility across themes (still with hardcoded dark fallbacks). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 29 ++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 7557b88772..4c8e0789e3 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -143,20 +143,20 @@ body { /* JSON syntax highlighting (uses VS Code token colors with fallbacks). */ .action-json .json-key { - color: var(--vscode-symbolIcon-propertyForeground, #9cdcfe); + color: var(--vscode-debugTokenExpression-name, #9cdcfe); } .action-json .json-string { - color: var(--vscode-symbolIcon-stringForeground, #ce9178); + color: var(--vscode-debugTokenExpression-string, #ce9178); } .action-json .json-number { - color: var(--vscode-symbolIcon-numberForeground, #b5cea8); + color: var(--vscode-debugTokenExpression-number, #b5cea8); } .action-json .json-bool { - color: var(--vscode-symbolIcon-booleanForeground, #569cd6); + color: var(--vscode-debugTokenExpression-boolean, #569cd6); font-weight: 600; } .action-json .json-null { - color: var(--vscode-symbolIcon-nullForeground, #569cd6); + color: var(--vscode-debugTokenExpression-value, #569cd6); font-style: italic; } .action-json .json-punct { @@ -164,7 +164,8 @@ body { opacity: 0.7; } -/* Inline metrics row at the bottom of an agent bubble. */ +/* Inline metrics row at the bottom of an agent bubble. + Hidden by default; revealed when the user hovers the bubble. */ .bubble-metrics { display: flex; justify-content: space-between; @@ -172,7 +173,20 @@ body { margin-top: 4px; padding: 0 4px; font-size: 11px; - opacity: 0.75; + opacity: 0; + max-height: 0; + overflow: hidden; + transition: + opacity 120ms ease-in, + max-height 120ms ease-in; + pointer-events: none; +} + +.message.agent:hover .bubble-metrics, +.message.user:hover .bubble-metrics { + opacity: 0.8; + max-height: 60px; + pointer-events: auto; } .bubble-metrics .metrics-left { @@ -185,7 +199,6 @@ body { .bubble-metrics b { font-weight: 600; - opacity: 1; } .message-timestamp { From 4bfdc36da3d9193db31593437abeb4317dfb5047 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:48:55 -0700 Subject: [PATCH 029/129] shell-chat: shell-style metrics footer with separator + tinted bg Render the timing row inside the bubble (instead of below it) and style it as an attached footer: - background tinted with --vscode-editorWidget-background - top border separator - bottom corners rounded to match the bubble - left/right columns truly aligned via flex (text-align + flex 0 0) Reveal the footer on bubble hover with combined opacity, max-height and padding transitions so it visually slides in. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 28 +++++++++++++++---- .../typeagent-shell/src/webview/chatUI.ts | 16 ++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 4c8e0789e3..4b3025df85 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -164,41 +164,57 @@ body { opacity: 0.7; } -/* Inline metrics row at the bottom of an agent bubble. +/* Inline metrics row — rendered inside the bubble at the bottom. Hidden by default; revealed when the user hovers the bubble. */ .bubble-metrics { display: flex; justify-content: space-between; + align-items: flex-start; gap: 16px; - margin-top: 4px; - padding: 0 4px; + margin: 8px -12px -8px -12px; /* extend to bubble edges (negate .bubble padding) */ + padding: 6px 12px; + border-top: 1px solid var(--vscode-widget-border, rgba(127, 127, 127, 0.25)); + background: var(--vscode-editorWidget-background, rgba(127, 127, 127, 0.08)); + border-bottom-left-radius: 12px; + border-bottom-right-radius: 12px; font-size: 11px; + color: var(--vscode-descriptionForeground); opacity: 0; max-height: 0; overflow: hidden; transition: opacity 120ms ease-in, - max-height 120ms ease-in; + max-height 120ms ease-in, + padding 120ms ease-in; pointer-events: none; + padding-top: 0; + padding-bottom: 0; + border-top-width: 0; } .message.agent:hover .bubble-metrics, .message.user:hover .bubble-metrics { - opacity: 0.8; - max-height: 60px; + opacity: 1; + max-height: 80px; + padding-top: 6px; + padding-bottom: 6px; + border-top-width: 1px; pointer-events: auto; } .bubble-metrics .metrics-left { text-align: left; + flex: 0 0 auto; } .bubble-metrics .metrics-right { text-align: right; + flex: 0 0 auto; } .bubble-metrics b { font-weight: 600; + color: var(--vscode-foreground); } .message-timestamp { diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index e62c54a6e3..82779ae30b 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -297,13 +297,7 @@ export class ChatUI { pre.innerHTML = ChatUI._highlightJson( JSON.stringify(action, null, 2), ); - // Insert before .bubble-metrics if present, else append - const metricsRow = body.querySelector(".bubble-metrics"); - if (metricsRow) { - body.insertBefore(pre, metricsRow); - } else { - body.appendChild(pre); - } + body.appendChild(pre); } this._scrollToBottom(); }; @@ -348,11 +342,11 @@ export class ChatUI { private _renderMetrics(bubbleRow: HTMLElement, metrics: any): void { if (!metrics || typeof metrics !== "object") return; - const body = bubbleRow.querySelector(".message-body"); - if (!body) return; + const bubble = bubbleRow.querySelector(".bubble") as HTMLElement | null; + if (!bubble) return; // Remove any prior metrics row (idempotent) - body.querySelector(".bubble-metrics")?.remove(); + bubble.querySelector(".bubble-metrics")?.remove(); const fmt = (ms?: number) => { if (typeof ms !== "number") return undefined; @@ -399,7 +393,7 @@ export class ChatUI { rightEl.className = "metrics-right"; rightEl.innerHTML = right.filter(Boolean).join("
"); row.append(leftEl, rightEl); - body.appendChild(row); + bubble.appendChild(row); this._scrollToBottom(); } From 331a53a53a40b070732880888d32e7f655e8176f Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 15:52:16 -0700 Subject: [PATCH 030/129] shell-chat: replay set-display-info + thread requestId; metrics on user bubble too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit History replay now: - threads clientRequestId from each entry through addUserMessage, setAgentDisplay, appendAgentDisplay so replayed bubbles are tagged the same way live bubbles are - processes set-display-info entries (previously skipped) so the agent bubble's source label becomes the clickable schemaName.actionName with the JSON expand panel, matching live behaviour - resets the active-response between turns so each user-request gets its own agent bubble - clears the pending/agent maps after replay so historical request ids don't intercept future live commandComplete events Live commandComplete now also renders the timing footer on the user bubble (in addition to the agent bubble), so timing is visible from either side of the conversation on hover. History entries do not carry metrics (DisplayLogEntry has no command-result type), so historical bubbles will not show timing — only clickable action labels. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 82779ae30b..43f277b108 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -334,6 +334,10 @@ export class ChatUI { if (agentBubble) { this._renderMetrics(agentBubble, result?.metrics); } + const userBubble = this._pendingUserBubbles.get(requestId); + if (userBubble) { + this._renderMetrics(userBubble, result?.metrics); + } this._pendingUserBubbles.delete(requestId); this._agentBubblesByRequestId.delete(requestId); } @@ -556,16 +560,29 @@ export class ChatUI { this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + const clientId = (rid: any): string | undefined => { + if (!rid) return undefined; + if (typeof rid === "string") return rid; + return rid.clientRequestId as string | undefined; + }; + for (const e of entries) { switch (e.type) { - case "user-request": - this.addUserMessage(e.command, e.timestamp); + case "user-request": { + const rid = clientId(e.requestId); + this.addUserMessage(e.command, e.timestamp, "pending", rid); + // Reset active agent bubble between turns so a new one + // is created for each user request during replay. + this._activeResponseEl = undefined; + this._lastAppendedContent = undefined; break; + } case "set-display": this.setAgentDisplay( e.message?.message, e.message?.source, e.timestamp, + clientId(e.message?.requestId), ); break; case "append-display": @@ -574,11 +591,17 @@ export class ChatUI { e.message?.source, e.mode, e.timestamp, + clientId(e.message?.requestId), ); break; case "set-display-info": - // Skip status indicators during replay — they'd pollute - // the rendered history with transient "processing..." text + // Apply during replay so historical action labels are + // also clickable + reveal their JSON. + this.setDisplayInfo( + e.source, + e.action, + clientId(e.requestId), + ); break; } } @@ -591,6 +614,10 @@ export class ChatUI { // Reset state so the next live message starts a fresh bubble this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + // Drop tracking maps — history entries shouldn't intercept future + // live commandComplete events. + this._pendingUserBubbles.clear(); + this._agentBubblesByRequestId.clear(); this._removeTemporary(); this._removeStatusIndicator(); this._scrollToBottom(); From 76076c50c8c29a6cd4083dec1918713f1d99d5a4 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 16:04:24 -0700 Subject: [PATCH 031/129] dispatcher: persist command-result + setDisplayInfo entries in DisplayLog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new CommandResultEntry type to DisplayLogEntry and a corresponding DisplayLog.logCommandResult() that records per-request RequestMetrics. This brings the structured display log to feature-parity with the Electron shell's HTML-snapshot history for the purpose of re-rendering timing data and clickable action labels on history replay. In agentServer/sharedDispatcher.ts: - Patch the shared clientIO.setDisplayInfo so set-display-info entries are also mirrored into the log (previously only setUserRequest / setDisplay / appendDisplay were). - Wrap each per-connection dispatcher's processCommand to log a command-result entry with result.metrics on completion. The log entry carries clientRequestId for correlation (the server-side requestId UUID is generated inside processCommand and not exposed here). In typeagent-shell extension: - Forward command-result entries through historyReplay with their metrics payload. - chatUI.replayHistory now matches the entry's clientRequestId against the just-replayed agent/user bubble and applies _renderMetrics — so hovering a historical bubble shows First Message / Action Elapsed / Total Elapsed times exactly like a live one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 10 ++++ .../typeagent-shell/src/webview/chatUI.ts | 13 +++++ .../server/src/sharedDispatcher.ts | 57 +++++++++++++++++++ .../dispatcher/dispatcher/src/displayLog.ts | 34 +++++++++++ .../dispatcher/types/src/displayLogEntry.ts | 19 ++++++- 5 files changed, 131 insertions(+), 2 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 75915fe8ec..219a5399a6 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -67,6 +67,8 @@ export type BridgeToWebviewMessage = action?: TypeAgentAction | string[]; actionIndex?: number; requestId?: RequestId; + // command-result + metrics?: any; }>; }; @@ -533,6 +535,14 @@ export class AgentServerBridge { actionIndex: e.actionIndex, action: e.action, }; + case "command-result": + return { + type: "command-result", + seq: e.seq, + timestamp: e.timestamp, + requestId: e.requestId, + metrics: e.metrics, + }; default: return { type: "skip", seq: e.seq }; } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 43f277b108..917f02b0c9 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -603,6 +603,19 @@ export class ChatUI { clientId(e.requestId), ); break; + case "command-result": { + // Render timing footer for this historical request on + // both its agent bubble and user bubble (if known). + const rid = clientId(e.requestId); + if (rid) { + const agent = + this._agentBubblesByRequestId.get(rid); + if (agent) this._renderMetrics(agent, e.metrics); + const user = this._pendingUserBubbles.get(rid); + if (user) this._renderMetrics(user, e.metrics); + } + break; + } } } diff --git a/ts/packages/agentServer/server/src/sharedDispatcher.ts b/ts/packages/agentServer/server/src/sharedDispatcher.ts index e30a707353..3a80ae0a8e 100644 --- a/ts/packages/agentServer/server/src/sharedDispatcher.ts +++ b/ts/packages/agentServer/server/src/sharedDispatcher.ts @@ -283,6 +283,25 @@ export async function createSharedDispatcher( log.logAppendDisplay(message, mode); log.saveQueued(); }; + + const origSetDisplayInfo = orig.setDisplayInfo.bind(orig); + orig.setDisplayInfo = ( + requestId, + source, + actionIndex, + action, + ...rest + ) => { + origSetDisplayInfo( + requestId, + source, + actionIndex, + action, + ...rest, + ); + log.logSetDisplayInfo(requestId, source, actionIndex, action); + log.saveQueued(); + }; } const dispatchers = new Map(); @@ -348,6 +367,44 @@ export async function createSharedDispatcher( shared.cancelInteraction(interactionId); }; + // Wrap processCommand so each completed request logs a + // command-result entry into the DisplayLog (carrying its + // metrics). This lets history replay re-render timing data + // exactly the way live commandComplete does. + const origProcessCommand = + dispatcher.processCommand.bind(dispatcher); + dispatcher.processCommand = async ( + command: any, + clientRequestId?: any, + attachments?: any, + processOptions?: any, + ) => { + const result = await origProcessCommand( + command, + clientRequestId, + attachments, + processOptions, + ); + try { + context.displayLog.logCommandResult( + { + connectionId, + // The actual server-side requestId UUID is + // generated inside processCommand and not + // exposed to the wrapper, so leave it empty; + // consumers correlate via clientRequestId. + requestId: "", + clientRequestId, + }, + result?.metrics, + ); + context.displayLog.saveQueued(); + } catch { + // best effort + } + return result; + }; + return dispatcher; }, respondToInteraction(response: PendingInteractionResponse): void { diff --git a/ts/packages/dispatcher/dispatcher/src/displayLog.ts b/ts/packages/dispatcher/dispatcher/src/displayLog.ts index 61f323920a..7651cb7cbf 100644 --- a/ts/packages/dispatcher/dispatcher/src/displayLog.ts +++ b/ts/packages/dispatcher/dispatcher/src/displayLog.ts @@ -8,6 +8,7 @@ import type { SetDisplayInfoEntry, IAgentMessage, RequestId, + RequestMetrics, PendingInteractionRequest, } from "@typeagent/dispatcher-types"; @@ -267,6 +268,39 @@ export class DisplayLog { return seq; } + /** + * Append a command-result entry. Carries the per-request metrics so + * consumers can re-render timing info during history replay. + * @returns the assigned sequence number + */ + logCommandResult( + requestId: RequestId, + metrics?: RequestMetrics, + ): number { + const seq = this.nextSeq++; + let safeMetrics: RequestMetrics | undefined; + if (metrics !== undefined) { + try { + safeMetrics = JSON.parse(JSON.stringify(metrics)); + } catch { + safeMetrics = undefined; + } + } + const entry: import("@typeagent/dispatcher-types").CommandResultEntry = + { + type: "command-result", + seq, + timestamp: Date.now(), + requestId, + }; + if (safeMetrics !== undefined) { + entry.metrics = safeMetrics; + } + this.entries.push(entry); + this.dirty = true; + return seq; + } + /** * Schedule a save at the end of the current event-loop tick. * Multiple calls within the same tick are coalesced into one write. diff --git a/ts/packages/dispatcher/types/src/displayLogEntry.ts b/ts/packages/dispatcher/types/src/displayLogEntry.ts index 8fefb62d54..9ab99c3462 100644 --- a/ts/packages/dispatcher/types/src/displayLogEntry.ts +++ b/ts/packages/dispatcher/types/src/displayLogEntry.ts @@ -7,7 +7,7 @@ import type { NotifyExplainedData, TemplateEditConfig, } from "./clientIO.js"; -import type { RequestId } from "./dispatcher.js"; +import type { RequestId, RequestMetrics } from "./dispatcher.js"; import type { PendingInteractionType } from "./pendingInteraction.js"; export type SetDisplayEntry = { @@ -84,6 +84,20 @@ export type InteractionCancelledEntry = { interactionId: string; }; +/** + * Logged when a command completes. Carries the full RequestMetrics so + * that consumers replaying history can re-render timing information + * (e.g. hover tooltip on the agent bubble) just like they would for a + * live command. + */ +export type CommandResultEntry = { + type: "command-result"; + seq: number; + timestamp: number; + requestId: RequestId; + metrics?: RequestMetrics; +}; + export type DisplayLogEntry = | SetDisplayEntry | AppendDisplayEntry @@ -92,4 +106,5 @@ export type DisplayLogEntry = | UserRequestEntry | PendingInteractionEntry | InteractionResolvedEntry - | InteractionCancelledEntry; + | InteractionCancelledEntry + | CommandResultEntry; From db8abe906406a3ff6e068369dda7861922962bf3 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 16:09:33 -0700 Subject: [PATCH 032/129] shell-chat: split user vs agent bubble timing to match shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell distinguishes which timing applies to which bubble: user bubble → Translation: agent bubble → Action Elapsed Time: Total Elapsed Time: Update _renderMetrics to take a 'kind' parameter and render only the fields appropriate for that bubble side. Both live (onCommandComplete) and replay (command-result entry) call sites now pass the right kind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 62 ++++++++++++------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 917f02b0c9..edd7d0a3b0 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -332,18 +332,22 @@ export class ChatUI { if (requestId) { const agentBubble = this._agentBubblesByRequestId.get(requestId); if (agentBubble) { - this._renderMetrics(agentBubble, result?.metrics); + this._renderMetrics(agentBubble, result?.metrics, "agent"); } const userBubble = this._pendingUserBubbles.get(requestId); if (userBubble) { - this._renderMetrics(userBubble, result?.metrics); + this._renderMetrics(userBubble, result?.metrics, "user"); } this._pendingUserBubbles.delete(requestId); this._agentBubblesByRequestId.delete(requestId); } } - private _renderMetrics(bubbleRow: HTMLElement, metrics: any): void { + private _renderMetrics( + bubbleRow: HTMLElement, + metrics: any, + kind: "user" | "agent", + ): void { if (!metrics || typeof metrics !== "object") return; const bubble = bubbleRow.querySelector(".bubble") as HTMLElement | null; @@ -364,26 +368,34 @@ export class ChatUI { const left: string[] = []; const right: string[] = []; - if (metrics.parse?.duration != null) { - left.push(item("First Message", fmt(metrics.parse.duration))!); - } - - if (Array.isArray(metrics.actions) && metrics.actions.length > 0) { - const actionTotal = metrics.actions.reduce( - (acc: number, a: any) => - acc + (typeof a?.duration === "number" ? a.duration : 0), - 0, - ); - if (actionTotal > 0) { - right.push(item("Action Elapsed Time", fmt(actionTotal))!); + if (kind === "user") { + // Mirror the shell's user-bubble metrics: Translation phase. + if (metrics.parse?.duration != null) { + left.push(item("Translation", fmt(metrics.parse.duration))!); + } + } else { + // Agent bubble: Action time (sum of per-action durations or + // command phase) + Total elapsed time. + let actionTotal: number | undefined; + if ( + Array.isArray(metrics.actions) && + metrics.actions.length > 0 + ) { + actionTotal = metrics.actions.reduce( + (acc: number, a: any) => + acc + + (typeof a?.duration === "number" ? a.duration : 0), + 0, + ); + } else if (metrics.command?.duration != null) { + actionTotal = metrics.command.duration; + } + if (actionTotal != null && actionTotal > 0) { + left.push(item("Action Elapsed Time", fmt(actionTotal))!); + } + if (metrics.duration != null) { + right.push(item("Total Elapsed Time", fmt(metrics.duration))!); } - } else if (metrics.command?.duration != null) { - right.push( - item("Command Elapsed Time", fmt(metrics.command.duration))!, - ); - } - if (metrics.duration != null) { - right.push(item("Total Elapsed Time", fmt(metrics.duration))!); } if (left.length === 0 && right.length === 0) return; @@ -610,9 +622,11 @@ export class ChatUI { if (rid) { const agent = this._agentBubblesByRequestId.get(rid); - if (agent) this._renderMetrics(agent, e.metrics); + if (agent) + this._renderMetrics(agent, e.metrics, "agent"); const user = this._pendingUserBubbles.get(rid); - if (user) this._renderMetrics(user, e.metrics); + if (user) + this._renderMetrics(user, e.metrics, "user"); } break; } From e46526ea36d5791fb6ba6804390e0ea64f12c37a Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 18:05:44 -0700 Subject: [PATCH 033/129] feat(typeagent-shell): hide empty agent bubble + multi-chat side panels Hide empty agent bubble - Add .empty class on creation; remove only when first non-empty content arrives via setDisplay/appendDisplay. - Avoids the flash of an empty bubble while status / setDisplayInfo events fire before the response is ready. Multi-chat side panels - Each editor panel now gets its own AgentServerBridge instance with its own connection and its own ephemeral session (named cli-ephemeral-vscode- so the server's startup sweep cleans up orphans). - Sidebar continues to use the primary bridge on the default/persistent session. - Panel bridges delete their ephemeral session on dispose (best-effort). - AgentServerBridge gains constructor opts {ownsStatusBar, autoCreateSessionPrefix}; only the primary bridge owns the status bar item, panels skip it to avoid duplication. - chatViewProvider exposes wireWebview(webview, bridge) so panels can be wired with a fresh bridge; sidebar uses the primary one. - New command typeagent-shell.newChatPanel; openChat repurposed to also open a fresh ephemeral panel. - Conversation management commands (switch/new/rename/delete) continue to operate on the primary (sidebar) bridge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 6 + ts/extensions/typeagent-shell/package.json | 5 + .../typeagent-shell/src/agentServerBridge.ts | 82 ++++++++++++-- .../typeagent-shell/src/chatViewProvider.ts | 33 +++--- .../typeagent-shell/src/extension.ts | 104 ++++++++++++------ .../typeagent-shell/src/webview/chatUI.ts | 11 +- 6 files changed, 178 insertions(+), 63 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 4b3025df85..63136975a5 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -82,6 +82,12 @@ body { flex-direction: row; } +/* Hide agent bubble until it has real content (avoids flash of empty + bubble while status / setDisplayInfo events fire before content). */ +.message.agent.empty { + display: none; +} + .message-body { display: flex; flex-direction: column; diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 70d1d16ef2..c4236e46b9 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -46,6 +46,11 @@ "title": "Open Chat in Editor", "category": "TypeAgent" }, + { + "command": "typeagent-shell.newChatPanel", + "title": "New Chat (Side Panel)", + "category": "TypeAgent" + }, { "command": "typeagent-shell.focusChat", "title": "Focus Chat", diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 219a5399a6..2102566649 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -99,14 +99,32 @@ export class AgentServerBridge { // create muted duplicates of live messages). private lastReplayedSessionId: string | undefined; - constructor() { - this.statusBarItem = vscode.window.createStatusBarItem( - vscode.StatusBarAlignment.Left, - 100, - ); - this.statusBarItem.command = "typeagent-shell.focusChat"; - this.updateStatusBar(false); - this.statusBarItem.show(); + // Configuration + private readonly ownsStatusBar: boolean; + private readonly autoCreateSessionPrefix: string | undefined; + // Track ephemeral session we created so we can delete on dispose + private ephemeralSessionId: string | undefined; + + constructor(opts?: { + ownsStatusBar?: boolean; + autoCreateSessionPrefix?: string; + }) { + this.ownsStatusBar = opts?.ownsStatusBar ?? true; + this.autoCreateSessionPrefix = opts?.autoCreateSessionPrefix; + if (this.ownsStatusBar) { + this.statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + 100, + ); + this.statusBarItem.command = "typeagent-shell.focusChat"; + this.updateStatusBar(false); + this.statusBarItem.show(); + } else { + // Dummy stand-in so existing code paths don't crash + this.statusBarItem = { + dispose: () => {}, + } as unknown as vscode.StatusBarItem; + } } /** @@ -175,10 +193,38 @@ export class AgentServerBridge { // Join the default session with our ClientIO implementation // filter: true ensures we only see responses to our own requests const clientIO = this.createClientIO(); - this.session = await this.connection.joinSession(clientIO, { + + // If configured for an ephemeral panel session, create one first + // and join it; otherwise join the default session. + let joinOpts: any = { clientType: "extension", filter: true, - }); + }; + if ( + this.autoCreateSessionPrefix && + this.ephemeralSessionId === undefined + ) { + const ts = new Date() + .toISOString() + .replace(/[:.]/g, "-") + .replace("T", "_") + .substring(0, 19); + const name = `${this.autoCreateSessionPrefix}${ts}`; + try { + const info = await this.connection.createSession(name); + this.ephemeralSessionId = info.sessionId; + joinOpts.sessionId = info.sessionId; + } catch (e) { + // Fall back to default join if creation fails + } + } else if (this.ephemeralSessionId) { + joinOpts.sessionId = this.ephemeralSessionId; + } + + this.session = await this.connection.joinSession( + clientIO, + joinOpts, + ); this.isConnected = true; this.updateStatusBar(true); @@ -224,7 +270,20 @@ export class AgentServerBridge { } dispose(): void { - this.disconnect(); + // Best-effort delete the ephemeral session we created for this panel + const toDelete = this.ephemeralSessionId; + const conn = this.connection; + if (toDelete && conn) { + // Fire-and-forget — don't block dispose + conn + .deleteSession(toDelete) + .catch(() => {}) + .finally(() => { + this.disconnect(); + }); + } else { + this.disconnect(); + } this.statusBarItem.dispose(); } @@ -745,6 +804,7 @@ export class AgentServerBridge { } private updateStatusBar(connected: boolean): void { + if (!this.ownsStatusBar) return; if (connected) { this.statusBarItem.text = "$(plug) TypeAgent: Connected"; this.statusBarItem.backgroundColor = undefined; diff --git a/ts/extensions/typeagent-shell/src/chatViewProvider.ts b/ts/extensions/typeagent-shell/src/chatViewProvider.ts index 06f44117d3..971b76ed83 100644 --- a/ts/extensions/typeagent-shell/src/chatViewProvider.ts +++ b/ts/extensions/typeagent-shell/src/chatViewProvider.ts @@ -5,7 +5,8 @@ import * as vscode from "vscode"; import { AgentServerBridge } from "./agentServerBridge"; /** - * Provides the chat webview for both the sidebar panel and editor tabs. + * Provides the chat webview for the sidebar (uses primary bridge) and + * helper for editor panels (each gets its own bridge). */ export class ChatViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "typeagent-shell.chatView"; @@ -14,7 +15,7 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { constructor( private readonly _extensionUri: vscode.Uri, - private readonly _bridge: AgentServerBridge, + private readonly _primaryBridge: AgentServerBridge, ) {} public resolveWebviewView( @@ -23,13 +24,21 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { _token: vscode.CancellationToken, ): void { this._sidebarView = webviewView; - this.resolveWebviewPanel(webviewView.webview); + this.wireWebview(webviewView.webview, this._primaryBridge); + + webviewView.onDidDispose(() => { + this._sidebarView = undefined; + }); } /** - * Configure a webview (sidebar or editor panel) with the chat UI. + * Configure a webview (panel) with the chat UI and bind it to a bridge. + * Used both by the sidebar (primary bridge) and per-panel bridges. */ - public resolveWebviewPanel(webview: vscode.Webview): void { + public wireWebview( + webview: vscode.Webview, + bridge: AgentServerBridge, + ): vscode.Disposable { webview.options = { enableScripts: true, localResourceRoots: [ @@ -40,18 +49,10 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { webview.html = this._getHtmlForWebview(webview); - // Register webview with the bridge — this connects it to the RPC stream - const bridgeDisposable = this._bridge.registerWebview(webview); - + const bridgeDisposable = bridge.registerWebview(webview); // Auto-connect when webview opens - this._bridge.connect(); - - // Clean up on dispose - if (this._sidebarView) { - this._sidebarView.onDidDispose(() => { - bridgeDisposable.dispose(); - }); - } + bridge.connect(); + return bridgeDisposable; } private _getHtmlForWebview(webview: vscode.Webview): string { diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index bd1fdea293..27d7095cd5 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -5,12 +5,15 @@ import * as vscode from "vscode"; import { ChatViewProvider } from "./chatViewProvider"; import { AgentServerBridge } from "./agentServerBridge"; -let bridge: AgentServerBridge | undefined; +let primaryBridge: AgentServerBridge | undefined; +// Track per-panel bridges so we can dispose them on extension deactivate +const panelBridges = new Set(); +let panelCounter = 0; export function activate(context: vscode.ExtensionContext): void { - bridge = new AgentServerBridge(); + primaryBridge = new AgentServerBridge({ ownsStatusBar: true }); - const provider = new ChatViewProvider(context.extensionUri, bridge); + const provider = new ChatViewProvider(context.extensionUri, primaryBridge); // Sidebar webview provider context.subscriptions.push( @@ -21,24 +24,23 @@ export function activate(context: vscode.ExtensionContext): void { ), ); - // Command: open chat as an editor tab + // Command: open a NEW chat in an editor tab beside the current view. + // Each invocation creates a fresh ephemeral session + bridge so panels + // are independent of each other and of the sidebar. context.subscriptions.push( vscode.commands.registerCommand("typeagent-shell.openChat", () => { - const panel = vscode.window.createWebviewPanel( - "typeagent-shell.chatPanel", - "TypeAgent Chat", - vscode.ViewColumn.Beside, - getWebviewOptions(context.extensionUri), - ); - panel.iconPath = vscode.Uri.joinPath( - context.extensionUri, - "media", - "typeagent-icon.svg", - ); - provider.resolveWebviewPanel(panel.webview); + openNewChatPanel(context, provider); }), ); + // Alias for clarity + context.subscriptions.push( + vscode.commands.registerCommand( + "typeagent-shell.newChatPanel", + () => openNewChatPanel(context, provider), + ), + ); + // Command: focus the sidebar chat context.subscriptions.push( vscode.commands.registerCommand("typeagent-shell.focusChat", () => { @@ -48,40 +50,74 @@ export function activate(context: vscode.ExtensionContext): void { }), ); - // Conversation management commands + // Conversation management commands — operate on the primary (sidebar) + // bridge. Per-panel chats are intentionally ephemeral. context.subscriptions.push( vscode.commands.registerCommand( "typeagent-shell.switchSession", - () => bridge?.switchSession(), + () => primaryBridge?.switchSession(), ), vscode.commands.registerCommand( "typeagent-shell.newSession", - () => bridge?.newSession(), + () => primaryBridge?.newSession(), ), vscode.commands.registerCommand( "typeagent-shell.renameSession", - () => bridge?.renameCurrentSession(), + () => primaryBridge?.renameCurrentSession(), ), vscode.commands.registerCommand( "typeagent-shell.deleteSession", - () => bridge?.deleteSession(), + () => primaryBridge?.deleteSession(), ), ); } -export function deactivate(): void { - bridge?.dispose(); +function openNewChatPanel( + context: vscode.ExtensionContext, + provider: ChatViewProvider, +): void { + panelCounter += 1; + const title = `TypeAgent Chat ${panelCounter}`; + const panel = vscode.window.createWebviewPanel( + "typeagent-shell.chatPanel", + title, + vscode.ViewColumn.Beside, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.joinPath(context.extensionUri, "dist"), + vscode.Uri.joinPath(context.extensionUri, "media"), + ], + }, + ); + panel.iconPath = vscode.Uri.joinPath( + context.extensionUri, + "media", + "typeagent-icon.svg", + ); + + // Each panel gets its own bridge, its own connection, and its own + // ephemeral session that's deleted when the panel closes. + const bridge = new AgentServerBridge({ + ownsStatusBar: false, + autoCreateSessionPrefix: "cli-ephemeral-vscode-", + }); + panelBridges.add(bridge); + + const bridgeDisposable = provider.wireWebview(panel.webview, bridge); + + panel.onDidDispose(() => { + bridgeDisposable.dispose(); + bridge.dispose(); + panelBridges.delete(bridge); + }); } -function getWebviewOptions( - extensionUri: vscode.Uri, -): vscode.WebviewOptions & vscode.WebviewPanelOptions { - return { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [ - vscode.Uri.joinPath(extensionUri, "dist"), - vscode.Uri.joinPath(extensionUri, "media"), - ], - }; +export function deactivate(): void { + primaryBridge?.dispose(); + for (const b of panelBridges) { + b.dispose(); + } + panelBridges.clear(); } diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index edd7d0a3b0..b58a7461a2 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -179,7 +179,11 @@ export class ChatUI { } const contentEl = bubble.querySelector(".agent-content"); if (contentEl) { - contentEl.innerHTML = this._renderDisplayContent(content); + const html = this._renderDisplayContent(content); + contentEl.innerHTML = html; + if (html && html.trim().length > 0) { + bubble.classList.remove("empty"); + } } this._scrollToBottom(); } @@ -230,6 +234,9 @@ export class ChatUI { const contentEl = bubble.querySelector(".agent-content"); if (contentEl) { contentEl.innerHTML += rendered; + if (rendered && rendered.trim().length > 0) { + bubble.classList.remove("empty"); + } } this._scrollToBottom(); } @@ -675,7 +682,7 @@ export class ChatUI { requestId?: string, ): HTMLElement { const row = document.createElement("div"); - row.className = "message agent"; + row.className = "message agent empty"; if (requestId) row.dataset.requestId = requestId; row.appendChild(this._createAvatar("agent", source)); From 5384dcdbfe507e57c4eb0e69763f8fce87c1e5de Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 18:20:13 -0700 Subject: [PATCH 034/129] feat(typeagent-shell): active-chat tracking + friendlier panel names Chats are now peers (no 'main chat'): - Active chat = last-focused (sidebar visibility or panel onDidChangeViewState). - Conversation commands (switch/new/rename/delete) target the active chat. - Single status bar item is owned at extension level and shows the active chat's display name + connection state, so it's always clear which chat commands will hit. Friendlier panel names: - AgentServerBridge takes a displayName and (for ephemeral panels) ephemeralSessionName. - Panel server-side name keeps the cli-ephemeral-vscode- prefix so the server's startup sweep cleans up orphans. - Webview/status-bar see 'Chat N' (not the prefixed name). - Sidebar bridge displays the actual session name (default/renamed). Bridge changes: - New methods: getDisplayName(), isConnectedNow(), onStatusChange(). - nameOverride captured on rename so display reflects new name without round-trip. - Bridge no longer manages the status bar; emits change events extension subscribes to. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 66 +++++--- .../typeagent-shell/src/chatViewProvider.ts | 8 + .../typeagent-shell/src/extension.ts | 153 ++++++++++++++---- 3 files changed, 177 insertions(+), 50 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 2102566649..97f4648b80 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -101,16 +101,23 @@ export class AgentServerBridge { // Configuration private readonly ownsStatusBar: boolean; - private readonly autoCreateSessionPrefix: string | undefined; + private readonly ephemeralSessionName: string | undefined; + private displayName: string; // Track ephemeral session we created so we can delete on dispose private ephemeralSessionId: string | undefined; + private nameOverride: string | undefined; + // Notify when this bridge's status/session changes — used by extension + // to update the shared status bar when this bridge is active. + private onStatusChanged?: () => void; constructor(opts?: { ownsStatusBar?: boolean; - autoCreateSessionPrefix?: string; + ephemeralSessionName?: string; + displayName?: string; }) { this.ownsStatusBar = opts?.ownsStatusBar ?? true; - this.autoCreateSessionPrefix = opts?.autoCreateSessionPrefix; + this.ephemeralSessionName = opts?.ephemeralSessionName; + this.displayName = opts?.displayName ?? "TypeAgent"; if (this.ownsStatusBar) { this.statusBarItem = vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, @@ -120,13 +127,30 @@ export class AgentServerBridge { this.updateStatusBar(false); this.statusBarItem.show(); } else { - // Dummy stand-in so existing code paths don't crash this.statusBarItem = { dispose: () => {}, } as unknown as vscode.StatusBarItem; } } + /** Display name used in webview status bar / for routing UX. */ + getDisplayName(): string { + if (this.ephemeralSessionName) { + return this.displayName; + } + return this.nameOverride ?? this.session?.name ?? this.displayName; + } + + isConnectedNow(): boolean { + return this.isConnected; + } + + /** Subscribe to connection / session-name changes. */ + onStatusChange(cb: () => void): vscode.Disposable { + this.onStatusChanged = cb; + return { dispose: () => { this.onStatusChanged = undefined; } }; + } + /** * Register a webview to receive messages from the server. */ @@ -187,34 +211,28 @@ export class AgentServerBridge { this.session = undefined; this.updateStatusBar(false); this.broadcastToWebviews({ type: "status", connected: false }); + this.onStatusChanged?.(); this.scheduleReconnect(); }); - // Join the default session with our ClientIO implementation - // filter: true ensures we only see responses to our own requests + // Join the session with our ClientIO implementation const clientIO = this.createClientIO(); - // If configured for an ephemeral panel session, create one first - // and join it; otherwise join the default session. let joinOpts: any = { clientType: "extension", filter: true, }; if ( - this.autoCreateSessionPrefix && + this.ephemeralSessionName && this.ephemeralSessionId === undefined ) { - const ts = new Date() - .toISOString() - .replace(/[:.]/g, "-") - .replace("T", "_") - .substring(0, 19); - const name = `${this.autoCreateSessionPrefix}${ts}`; try { - const info = await this.connection.createSession(name); + const info = await this.connection.createSession( + this.ephemeralSessionName, + ); this.ephemeralSessionId = info.sessionId; joinOpts.sessionId = info.sessionId; - } catch (e) { + } catch { // Fall back to default join if creation fails } } else if (this.ephemeralSessionId) { @@ -232,8 +250,9 @@ export class AgentServerBridge { type: "status", connected: true, sessionId: this.session.sessionId, - sessionName: this.session.name, + sessionName: this.getDisplayName(), }); + this.onStatusChanged?.(); // Replay history only the first time we join this session. // On simple reconnects we already have the bubbles in the DOM @@ -266,6 +285,7 @@ export class AgentServerBridge { this.isConnected = false; this.updateStatusBar(false); this.broadcastToWebviews({ type: "status", connected: false }); + this.onStatusChanged?.(); } } @@ -397,12 +417,14 @@ export class AgentServerBridge { this.session.sessionId, newName.trim(), ); + this.nameOverride = newName.trim(); this.broadcastToWebviews({ type: "status", connected: true, sessionId: this.session.sessionId, - sessionName: newName.trim(), + sessionName: this.getDisplayName(), }); + this.onStatusChanged?.(); vscode.window.showInformationMessage( `Renamed conversation to "${newName.trim()}"`, ); @@ -504,6 +526,7 @@ export class AgentServerBridge { const oldSession = this.session; this.session = newSession; + this.nameOverride = undefined; // Phase 2: leave the old session (best-effort) if (oldSession) { @@ -518,14 +541,15 @@ export class AgentServerBridge { this.broadcastToWebviews({ type: "sessionChanged", sessionId: newSession.sessionId, - sessionName: newSession.name, + sessionName: this.getDisplayName(), }); this.broadcastToWebviews({ type: "status", connected: true, sessionId: newSession.sessionId, - sessionName: newSession.name, + sessionName: this.getDisplayName(), }); + this.onStatusChanged?.(); await this.replayHistory(newSession); this.lastReplayedSessionId = newSession.sessionId; } finally { diff --git a/ts/extensions/typeagent-shell/src/chatViewProvider.ts b/ts/extensions/typeagent-shell/src/chatViewProvider.ts index 971b76ed83..50f558c42d 100644 --- a/ts/extensions/typeagent-shell/src/chatViewProvider.ts +++ b/ts/extensions/typeagent-shell/src/chatViewProvider.ts @@ -12,12 +12,18 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "typeagent-shell.chatView"; private _sidebarView?: vscode.WebviewView; + private _onSidebarResolved?: (view: vscode.WebviewView) => void; constructor( private readonly _extensionUri: vscode.Uri, private readonly _primaryBridge: AgentServerBridge, ) {} + public onSidebarResolved(cb: (view: vscode.WebviewView) => void): void { + this._onSidebarResolved = cb; + if (this._sidebarView) cb(this._sidebarView); + } + public resolveWebviewView( webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, @@ -29,6 +35,8 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { webviewView.onDidDispose(() => { this._sidebarView = undefined; }); + + this._onSidebarResolved?.(webviewView); } /** diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index 27d7095cd5..7c99c2b3e7 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -5,15 +5,34 @@ import * as vscode from "vscode"; import { ChatViewProvider } from "./chatViewProvider"; import { AgentServerBridge } from "./agentServerBridge"; -let primaryBridge: AgentServerBridge | undefined; -// Track per-panel bridges so we can dispose them on extension deactivate -const panelBridges = new Set(); +interface ChatEntry { + bridge: AgentServerBridge; + /** UI handle for promoting to active. undefined means sidebar. */ + panel?: vscode.WebviewPanel; + sidebarView?: vscode.WebviewView; + statusDisposable: vscode.Disposable; +} + +let sidebarBridge: AgentServerBridge | undefined; +const chats = new Set(); +let activeChat: ChatEntry | undefined; let panelCounter = 0; +let statusBarItem: vscode.StatusBarItem; export function activate(context: vscode.ExtensionContext): void { - primaryBridge = new AgentServerBridge({ ownsStatusBar: true }); + statusBarItem = vscode.window.createStatusBarItem( + vscode.StatusBarAlignment.Left, + 100, + ); + statusBarItem.command = "typeagent-shell.focusChat"; + context.subscriptions.push(statusBarItem); - const provider = new ChatViewProvider(context.extensionUri, primaryBridge); + sidebarBridge = new AgentServerBridge({ + ownsStatusBar: false, + displayName: "Sidebar", + }); + + const provider = new ChatViewProvider(context.extensionUri, sidebarBridge); // Sidebar webview provider context.subscriptions.push( @@ -24,16 +43,37 @@ export function activate(context: vscode.ExtensionContext): void { ), ); - // Command: open a NEW chat in an editor tab beside the current view. - // Each invocation creates a fresh ephemeral session + bridge so panels - // are independent of each other and of the sidebar. + // Track sidebar as a chat entry once it resolves + provider.onSidebarResolved((view) => { + const entry: ChatEntry = { + bridge: sidebarBridge!, + sidebarView: view, + statusDisposable: sidebarBridge!.onStatusChange(() => + refreshStatusBar(), + ), + }; + chats.add(entry); + setActive(entry); + view.onDidChangeVisibility(() => { + if (view.visible) setActive(entry); + }); + view.onDidDispose(() => { + chats.delete(entry); + entry.statusDisposable.dispose(); + if (activeChat === entry) { + activeChat = chats.values().next().value; + refreshStatusBar(); + } + }); + refreshStatusBar(); + }); + + // Command: open NEW chat in editor tab beside context.subscriptions.push( vscode.commands.registerCommand("typeagent-shell.openChat", () => { openNewChatPanel(context, provider); }), ); - - // Alias for clarity context.subscriptions.push( vscode.commands.registerCommand( "typeagent-shell.newChatPanel", @@ -44,40 +84,73 @@ export function activate(context: vscode.ExtensionContext): void { // Command: focus the sidebar chat context.subscriptions.push( vscode.commands.registerCommand("typeagent-shell.focusChat", () => { - vscode.commands.executeCommand( - "typeagent-shell.chatView.focus", - ); + // If active chat is a panel, reveal it; otherwise focus sidebar + if (activeChat?.panel) { + activeChat.panel.reveal(); + } else { + vscode.commands.executeCommand( + "typeagent-shell.chatView.focus", + ); + } }), ); - // Conversation management commands — operate on the primary (sidebar) - // bridge. Per-panel chats are intentionally ephemeral. + // Conversation management commands — operate on the ACTIVE chat context.subscriptions.push( vscode.commands.registerCommand( "typeagent-shell.switchSession", - () => primaryBridge?.switchSession(), + () => activeChat?.bridge.switchSession(), ), vscode.commands.registerCommand( "typeagent-shell.newSession", - () => primaryBridge?.newSession(), + () => activeChat?.bridge.newSession(), ), vscode.commands.registerCommand( "typeagent-shell.renameSession", - () => primaryBridge?.renameCurrentSession(), + () => activeChat?.bridge.renameCurrentSession(), ), vscode.commands.registerCommand( "typeagent-shell.deleteSession", - () => primaryBridge?.deleteSession(), + () => activeChat?.bridge.deleteSession(), ), ); } +function setActive(entry: ChatEntry): void { + activeChat = entry; + refreshStatusBar(); +} + +function refreshStatusBar(): void { + if (!activeChat) { + statusBarItem.text = "$(debug-disconnect) TypeAgent"; + statusBarItem.tooltip = "TypeAgent — no active chat"; + statusBarItem.show(); + return; + } + const bridge = activeChat.bridge; + const name = bridge.getDisplayName(); + if (bridge.isConnectedNow()) { + statusBarItem.text = `$(plug) TypeAgent: ${name}`; + statusBarItem.backgroundColor = undefined; + } else { + statusBarItem.text = `$(debug-disconnect) TypeAgent: ${name}`; + statusBarItem.backgroundColor = new vscode.ThemeColor( + "statusBarItem.warningBackground", + ); + } + statusBarItem.tooltip = `Active chat: ${name}\nClick to focus`; + statusBarItem.show(); +} + function openNewChatPanel( context: vscode.ExtensionContext, provider: ChatViewProvider, ): void { panelCounter += 1; - const title = `TypeAgent Chat ${panelCounter}`; + const n = panelCounter; + const friendly = `Chat ${n}`; + const title = `TypeAgent ${friendly}`; const panel = vscode.window.createWebviewPanel( "typeagent-shell.chatPanel", title, @@ -97,27 +170,49 @@ function openNewChatPanel( "typeagent-icon.svg", ); - // Each panel gets its own bridge, its own connection, and its own - // ephemeral session that's deleted when the panel closes. + // Each panel: own bridge + ephemeral session deleted on close. + // Server name uses the cli-ephemeral- prefix so the startup sweep + // catches orphans from crashes; the user-visible name is "Chat N". const bridge = new AgentServerBridge({ ownsStatusBar: false, - autoCreateSessionPrefix: "cli-ephemeral-vscode-", + ephemeralSessionName: `cli-ephemeral-vscode-${n}-${Date.now()}`, + displayName: friendly, }); - panelBridges.add(bridge); + + const entry: ChatEntry = { + bridge, + panel, + statusDisposable: bridge.onStatusChange(() => refreshStatusBar()), + }; + chats.add(entry); + setActive(entry); const bridgeDisposable = provider.wireWebview(panel.webview, bridge); + panel.onDidChangeViewState((e) => { + if (e.webviewPanel.active) { + setActive(entry); + } + }); + panel.onDidDispose(() => { bridgeDisposable.dispose(); bridge.dispose(); - panelBridges.delete(bridge); + entry.statusDisposable.dispose(); + chats.delete(entry); + if (activeChat === entry) { + // Fall back to any remaining chat (sidebar, if open) + activeChat = chats.values().next().value; + refreshStatusBar(); + } }); } export function deactivate(): void { - primaryBridge?.dispose(); - for (const b of panelBridges) { - b.dispose(); + sidebarBridge?.dispose(); + for (const e of chats) { + e.bridge.dispose(); } - panelBridges.clear(); + chats.clear(); + statusBarItem?.dispose(); } From 585c38645faff3632a9eaabcc61c089a1a7a2bb3 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 18:36:46 -0700 Subject: [PATCH 035/129] fix(typeagent-shell): track session name through switch/rename + drop session name from server join/leave broadcasts - getDisplayName now follows session.name (with override) and only falls back to the friendly 'Chat N' label for the auto-generated cli-ephemeral-vscode- prefix. Switching/renaming the panel's session updates the displayed name. - Panel title is refreshed from the bridge's display name whenever status changes. - When a panel switches away from its initial ephemeral session, that session is now deleted (best-effort) so it doesn't pile up server-side. - agentServer: 'A new client has joined' / 'A client has left' broadcasts no longer include the session name (the joining client's UI already shows it; other clients don't need the technical prefix). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 24 ++++++++++++++++--- .../typeagent-shell/src/extension.ts | 9 +++++++ .../agentServer/server/src/sessionManager.ts | 4 ++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 97f4648b80..0acd747395 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -135,10 +135,13 @@ export class AgentServerBridge { /** Display name used in webview status bar / for routing UX. */ getDisplayName(): string { - if (this.ephemeralSessionName) { + if (this.nameOverride) return this.nameOverride; + const sn = this.session?.name; + // Auto-generated ephemeral names are ugly — show the friendly label + if (sn && sn.startsWith("cli-ephemeral-vscode-")) { return this.displayName; } - return this.nameOverride ?? this.session?.name ?? this.displayName; + return sn ?? this.displayName; } isConnectedNow(): boolean { @@ -528,13 +531,28 @@ export class AgentServerBridge { this.session = newSession; this.nameOverride = undefined; - // Phase 2: leave the old session (best-effort) + // Phase 2: leave the old session (best-effort). + // If we were on an ephemeral session and we're moving away from + // it, also delete it so it doesn't pile up on the server. if (oldSession) { try { await this.connection.leaveSession(oldSession.sessionId); } catch { // Best effort } + if ( + this.ephemeralSessionId && + oldSession.sessionId === this.ephemeralSessionId && + newSession.sessionId !== this.ephemeralSessionId + ) { + const epId = this.ephemeralSessionId; + this.ephemeralSessionId = undefined; + try { + await this.connection.deleteSession(epId); + } catch { + // Best effort + } + } } // Phase 3: clear UI and replay history diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index 7c99c2b3e7..df53e4add1 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -122,6 +122,15 @@ function setActive(entry: ChatEntry): void { } function refreshStatusBar(): void { + // Keep panel titles in sync with their bridge's display name + for (const e of chats) { + if (e.panel) { + const desired = `TypeAgent ${e.bridge.getDisplayName()}`; + if (e.panel.title !== desired) { + e.panel.title = desired; + } + } + } if (!activeChat) { statusBarItem.text = "$(debug-disconnect) TypeAgent"; statusBarItem.tooltip = "TypeAgent — no active chat"; diff --git a/ts/packages/agentServer/server/src/sessionManager.ts b/ts/packages/agentServer/server/src/sessionManager.ts index eff5c4114e..fbd4b31b46 100644 --- a/ts/packages/agentServer/server/src/sessionManager.ts +++ b/ts/packages/agentServer/server/src/sessionManager.ts @@ -379,7 +379,7 @@ export async function createSessionManager( // Notify existing clients that a new client has joined if (sharedDispatcher.clientCount > 1 && dispatcher.connectionId) { sharedDispatcher.broadcastSystemMessage( - `[A new client has joined this conversation. You are connected to '${record.name}'.]`, + `[A new client has joined this conversation.]`, dispatcher.connectionId, ); } @@ -413,7 +413,7 @@ export async function createSessionManager( // Notify remaining clients before this client leaves if (record.sharedDispatcher.clientCount > 1) { record.sharedDispatcher.broadcastSystemMessage( - `[A client has left this conversation. You remain connected to '${record.name}'.]`, + `[A client has left this conversation.]`, connectionId, ); } From 903c5e5c26f906b442ac7b55054e2d12be3870fb Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 19:06:37 -0700 Subject: [PATCH 036/129] fix(webSocketChannelServer): drop sends to non-open sockets to prevent uncaughtException ws.send() on a CLOSING/CLOSED socket queues the failure via process.nextTick, which bypasses the caller's synchronous try/catch and surfaces as a process-level uncaughtException (e.g. broadcastSystemMessage in agentServer iterating clients while one is mid-disconnect). Check readyState first; if not OPEN, drop the message and synchronously invoke the callback with an error so the failure is visible to the caller's existing try/catch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../utils/webSocketChannelServer/src/server.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ts/packages/utils/webSocketChannelServer/src/server.ts b/ts/packages/utils/webSocketChannelServer/src/server.ts index 03ad3aa077..7ef5ae2a75 100644 --- a/ts/packages/utils/webSocketChannelServer/src/server.ts +++ b/ts/packages/utils/webSocketChannelServer/src/server.ts @@ -36,6 +36,24 @@ export async function createWebSocketChannelServer( (message, cb) => { const data = JSON.stringify(message); debug(`sending message: ${data}`); + // Skip sends to a socket that is closing/closed. ws.send() + // would otherwise queue the failure on process.nextTick, + // bypassing any synchronous try/catch around the caller and + // becoming an uncaughtException. Best-effort: just drop the + // message and report via the callback (if any). + if (ws.readyState !== WebSocket.OPEN) { + debugError( + `dropping send: ws not open (readyState=${ws.readyState})`, + ); + if (cb) { + cb( + new Error( + `WebSocket is not open: readyState ${ws.readyState}`, + ), + ); + } + return; + } ws.send( data, cb From cbfde20d1ac878bc9fe2f8e8ca98f24c0468aa54 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 19:58:11 -0700 Subject: [PATCH 037/129] feat(typeagent-shell): persist ephemeral panel chats once user engages When a side-panel chat starts, its session is created with a cli-ephemeral-vscode-* name so that orphans from crashes get cleaned up by the server's startup sweep, and so that closing an unused panel doesn't leave behind an empty session. As soon as the user submits the first command in such a panel, rename the underlying session to a persistent name ('Chat N ', deduped if needed) and clear the ephemeralSessionId so it is no longer deleted on dispose or switch. This way meaningful conversations survive panel close and server restart, while truly empty panels are still tidied up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 0acd747395..26b350daee 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -675,6 +675,48 @@ export class AgentServerBridge { } } + /** + * If the current session is the bridge's ephemeral session and the user + * has just produced activity, give it a real (persistent) name so it is + * not deleted on panel close or by the server's startup sweep. + */ + private async promoteEphemeralIfNeeded(): Promise { + if (!this.connection || !this.session) return; + if (!this.ephemeralSessionId) return; + if (this.session.sessionId !== this.ephemeralSessionId) return; + + const stamp = new Date() + .toISOString() + .replace("T", " ") + .slice(0, 16); + const base = `${this.displayName} ${stamp}`; + let name = base; + try { + const sessions = await this.connection.listSessions(); + const taken = new Set(sessions.map((s) => s.name)); + let n = 2; + while (taken.has(name)) { + name = `${base} (${n++})`; + } + await this.connection.renameSession( + this.session.sessionId, + name, + ); + this.nameOverride = name; + this.ephemeralSessionId = undefined; + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: this.getDisplayName(), + }); + this.onStatusChanged?.(); + } catch { + // Best effort — if rename fails (e.g. server rejects), the + // session stays ephemeral and will still be cleaned up. + } + } + private async sendCommand( command: string, requestId?: string, @@ -687,6 +729,11 @@ export class AgentServerBridge { return; } + // Once the user actually engages with an ephemeral panel session, + // promote it to a persistent named session so it survives panel + // close and the server's startup sweep of cli-ephemeral-* sessions. + await this.promoteEphemeralIfNeeded(); + try { const result = await this.session.dispatcher.processCommand( command, From 5677f8288e63a9929c681ce90e1767e5fb38079c Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:08:16 -0700 Subject: [PATCH 038/129] feat(typeagent-shell): add Clear Chat command + default keybindings New command typeagent-shell.clearChat clears the visible messages in the active chat (server-side history is preserved; reload to replay). Default keybindings (Ctrl on Win/Linux, Cmd on macOS): Alt+T Focus Chat Alt+Shift+T New Chat (Side Panel) Alt+N New Conversation (when chat focused) Alt+S Switch Conversation (when chat focused) Alt+L Clear Chat View (when chat focused) Keybindings that mutate the active conversation are gated on the chat sidebar view or chat panel having focus to avoid surprising the user when working elsewhere. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 35 +++++++++++++++++++ .../typeagent-shell/src/agentServerBridge.ts | 11 ++++++ .../typeagent-shell/src/extension.ts | 4 +++ 3 files changed, 50 insertions(+) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index c4236e46b9..180b456104 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -75,6 +75,41 @@ "command": "typeagent-shell.deleteSession", "title": "Delete Conversation", "category": "TypeAgent" + }, + { + "command": "typeagent-shell.clearChat", + "title": "Clear Chat View", + "category": "TypeAgent" + } + ], + "keybindings": [ + { + "command": "typeagent-shell.focusChat", + "key": "ctrl+alt+t", + "mac": "cmd+alt+t" + }, + { + "command": "typeagent-shell.newChatPanel", + "key": "ctrl+alt+shift+t", + "mac": "cmd+alt+shift+t" + }, + { + "command": "typeagent-shell.newSession", + "key": "ctrl+alt+n", + "mac": "cmd+alt+n", + "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + }, + { + "command": "typeagent-shell.switchSession", + "key": "ctrl+alt+s", + "mac": "cmd+alt+s", + "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + }, + { + "command": "typeagent-shell.clearChat", + "key": "ctrl+alt+l", + "mac": "cmd+alt+l", + "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" } ], "configuration": { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 26b350daee..748868b54d 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -310,6 +310,17 @@ export class AgentServerBridge { this.statusBarItem.dispose(); } + /** + * Clear the visible chat UI for this bridge's webviews. + * Server-side history is left untouched; reload to replay. + */ + clearChatUI(): void { + this.broadcastToWebviews({ + type: "clear", + requestId: "user-clear" as RequestId, + }); + } + // ── Conversation management ───────────────────────────────── /** diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index df53e4add1..4e637147c0 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -113,6 +113,10 @@ export function activate(context: vscode.ExtensionContext): void { "typeagent-shell.deleteSession", () => activeChat?.bridge.deleteSession(), ), + vscode.commands.registerCommand( + "typeagent-shell.clearChat", + () => activeChat?.bridge.clearChatUI(), + ), ); } From 5b3c36dfa200ac9dcbb29805942de8b75a50c61b Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:23:23 -0700 Subject: [PATCH 039/129] fix(typeagent-shell): use chord keybindings gated on chat focus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous Ctrl+Alt+* bindings clashed with AltGr layouts and (on Linux) with the GNOME 'Open Terminal' system shortcut. Switched to Ctrl+K Ctrl+* chords (Cmd+K Cmd+* on macOS) and gated all of them on the chat sidebar view or chat panel having focus, so when the user is editing code VS Code's defaults (color theme, keyboard shortcuts, toggle fold, etc.) still work normally. Dropped the default keybinding for focusChat — the activity bar icon and command palette already provide good entry points, and any global key here would override something useful. VS Code displays keybindings next to commands in the command palette automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 180b456104..faedf8dcbb 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -83,32 +83,28 @@ } ], "keybindings": [ - { - "command": "typeagent-shell.focusChat", - "key": "ctrl+alt+t", - "mac": "cmd+alt+t" - }, { "command": "typeagent-shell.newChatPanel", - "key": "ctrl+alt+shift+t", - "mac": "cmd+alt+shift+t" + "key": "ctrl+k ctrl+shift+t", + "mac": "cmd+k cmd+shift+t", + "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" }, { "command": "typeagent-shell.newSession", - "key": "ctrl+alt+n", - "mac": "cmd+alt+n", + "key": "ctrl+k ctrl+n", + "mac": "cmd+k cmd+n", "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" }, { "command": "typeagent-shell.switchSession", - "key": "ctrl+alt+s", - "mac": "cmd+alt+s", + "key": "ctrl+k ctrl+s", + "mac": "cmd+k cmd+s", "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" }, { "command": "typeagent-shell.clearChat", - "key": "ctrl+alt+l", - "mac": "cmd+alt+l", + "key": "ctrl+k ctrl+l", + "mac": "cmd+k cmd+l", "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" } ], From 63415d3bfdc8669c6790ff045e29905fca948afe Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:24:46 -0700 Subject: [PATCH 040/129] chore(typeagent-shell): use Ctrl+K Ctrl+T for New Chat (Side Panel) Now that the binding is gated on chat focus, taking over the Color Theme chord only inside the chat view is acceptable, and the T mnemonic matches 'tab/chat'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index faedf8dcbb..320007f438 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -85,8 +85,8 @@ "keybindings": [ { "command": "typeagent-shell.newChatPanel", - "key": "ctrl+k ctrl+shift+t", - "mac": "cmd+k cmd+shift+t", + "key": "ctrl+k ctrl+t", + "mac": "cmd+k cmd+t", "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" }, { From 1ed51f874191163407dac2a4d99d95d044caaa1b Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:35:22 -0700 Subject: [PATCH 041/129] fix(typeagent-shell): drive keybinding when-clauses from a real focus context VS Code's focusedView / activeWebviewPanelId context keys do not track focus inside a webview's iframe, so the previous when-clauses evaluated false even when the chat input had focus. Pressing Ctrl+K Ctrl+S/T/L therefore fell through to VS Code's defaults (Keyboard Shortcuts, Color Theme, Fold). Fix: the webview now reports focusin/focusout (plus window focus/blur and document.hasFocus on load) to the bridge. The bridge surfaces these via onWebviewFocus, and the extension aggregates focus across all open chats into a custom context key 'typeagent-shell.chatFocused'. All chat keybindings now gate on that key. The context key is also cleared on view/panel disposal and when the sidebar becomes hidden, so we don't leave it stuck on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 8 ++--- .../typeagent-shell/src/agentServerBridge.ts | 12 ++++++- .../typeagent-shell/src/extension.ts | 33 +++++++++++++++++++ .../typeagent-shell/src/webview/main.ts | 13 ++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 320007f438..9ff8a359a8 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -87,25 +87,25 @@ "command": "typeagent-shell.newChatPanel", "key": "ctrl+k ctrl+t", "mac": "cmd+k cmd+t", - "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + "when": "typeagent-shell.chatFocused" }, { "command": "typeagent-shell.newSession", "key": "ctrl+k ctrl+n", "mac": "cmd+k cmd+n", - "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + "when": "typeagent-shell.chatFocused" }, { "command": "typeagent-shell.switchSession", "key": "ctrl+k ctrl+s", "mac": "cmd+k cmd+s", - "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + "when": "typeagent-shell.chatFocused" }, { "command": "typeagent-shell.clearChat", "key": "ctrl+k ctrl+l", "mac": "cmd+k cmd+l", - "when": "focusedView == typeagent-shell.chatView || activeWebviewPanelId == typeagent-shell.chatPanel" + "when": "typeagent-shell.chatFocused" } ], "configuration": { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 748868b54d..815c5398a6 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -79,7 +79,8 @@ export type BridgeFromWebviewMessage = | { type: "sendCommand"; command: string; requestId?: string } | { type: "connect" } | { type: "disconnect" } - | { type: "getStatus" }; + | { type: "getStatus" } + | { type: "focus"; focused: boolean }; /** * Manages the RPC connection to the agent server from the extension host @@ -109,6 +110,7 @@ export class AgentServerBridge { // Notify when this bridge's status/session changes — used by extension // to update the shared status bar when this bridge is active. private onStatusChanged?: () => void; + private onWebviewFocusChanged?: (focused: boolean) => void; constructor(opts?: { ownsStatusBar?: boolean; @@ -154,6 +156,11 @@ export class AgentServerBridge { return { dispose: () => { this.onStatusChanged = undefined; } }; } + onWebviewFocus(cb: (focused: boolean) => void): vscode.Disposable { + this.onWebviewFocusChanged = cb; + return { dispose: () => { this.onWebviewFocusChanged = undefined; } }; + } + /** * Register a webview to receive messages from the server. */ @@ -683,6 +690,9 @@ export class AgentServerBridge { sessionId: this.session?.sessionId, }); break; + case "focus": + this.onWebviewFocusChanged?.(msg.focused); + break; } } diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index 4e637147c0..e06b8cf288 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -11,6 +11,8 @@ interface ChatEntry { panel?: vscode.WebviewPanel; sidebarView?: vscode.WebviewView; statusDisposable: vscode.Disposable; + focusDisposable: vscode.Disposable; + focused: boolean; } let sidebarBridge: AgentServerBridge | undefined; @@ -51,19 +53,26 @@ export function activate(context: vscode.ExtensionContext): void { statusDisposable: sidebarBridge!.onStatusChange(() => refreshStatusBar(), ), + focusDisposable: sidebarBridge!.onWebviewFocus((f) => { + setEntryFocused(entry, f); + }), + focused: false, }; chats.add(entry); setActive(entry); view.onDidChangeVisibility(() => { if (view.visible) setActive(entry); + else setEntryFocused(entry, false); }); view.onDidDispose(() => { chats.delete(entry); entry.statusDisposable.dispose(); + entry.focusDisposable.dispose(); if (activeChat === entry) { activeChat = chats.values().next().value; refreshStatusBar(); } + refreshFocusContext(); }); refreshStatusBar(); }); @@ -125,6 +134,22 @@ function setActive(entry: ChatEntry): void { refreshStatusBar(); } +function setEntryFocused(entry: ChatEntry, focused: boolean): void { + if (entry.focused === focused) return; + entry.focused = focused; + if (focused) setActive(entry); + refreshFocusContext(); +} + +function refreshFocusContext(): void { + const anyFocused = [...chats].some((c) => c.focused); + vscode.commands.executeCommand( + "setContext", + "typeagent-shell.chatFocused", + anyFocused, + ); +} + function refreshStatusBar(): void { // Keep panel titles in sync with their bridge's display name for (const e of chats) { @@ -196,6 +221,10 @@ function openNewChatPanel( bridge, panel, statusDisposable: bridge.onStatusChange(() => refreshStatusBar()), + focusDisposable: bridge.onWebviewFocus((f) => { + setEntryFocused(entry, f); + }), + focused: false, }; chats.add(entry); setActive(entry); @@ -205,6 +234,8 @@ function openNewChatPanel( panel.onDidChangeViewState((e) => { if (e.webviewPanel.active) { setActive(entry); + } else { + setEntryFocused(entry, false); } }); @@ -212,12 +243,14 @@ function openNewChatPanel( bridgeDisposable.dispose(); bridge.dispose(); entry.statusDisposable.dispose(); + entry.focusDisposable.dispose(); chats.delete(entry); if (activeChat === entry) { // Fall back to any remaining chat (sidebar, if open) activeChat = chats.values().next().value; refreshStatusBar(); } + refreshFocusContext(); }); } diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 5d371f4627..b0c1c892fa 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -101,3 +101,16 @@ chatUI.onSend((text, requestId) => { // Ask the extension host to connect vscode.postMessage({ type: "connect" }); + +// Report focus changes so the extension can drive a context key for keybindings. +const reportFocus = (focused: boolean) => { + vscode.postMessage({ type: "focus", focused }); +}; +window.addEventListener("focus", () => reportFocus(true)); +window.addEventListener("blur", () => reportFocus(false)); +document.addEventListener("focusin", () => reportFocus(true)); +document.addEventListener("focusout", (e: FocusEvent) => { + // Only report blur if focus left the document entirely + if (!document.hasFocus()) reportFocus(false); +}); +if (document.hasFocus()) reportFocus(true); From 08bc719cd8f2d70f410b6f3fc47abb6e600a956e Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:40:44 -0700 Subject: [PATCH 042/129] feat(typeagent-shell): mute connection ribbon for inactive chats Makes it visually obvious which chat global commands and the status bar refer to. The extension broadcasts an active/inactive flag to each bridge whenever the active chat changes; the webview toggles a body.chat-inactive class that overrides the connected/disconnected/switching ribbon colors with a muted grey at reduced opacity. Also routes the dispose-time fallback (when the active chat closes) through setActive so the new active chat lights back up automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/media/chat.css | 11 ++++++++++ .../typeagent-shell/src/agentServerBridge.ts | 9 ++++++++ .../typeagent-shell/src/extension.ts | 21 +++++++++++++++---- .../typeagent-shell/src/webview/main.ts | 3 +++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/ts/extensions/typeagent-shell/media/chat.css b/ts/extensions/typeagent-shell/media/chat.css index 63136975a5..6509cef977 100644 --- a/ts/extensions/typeagent-shell/media/chat.css +++ b/ts/extensions/typeagent-shell/media/chat.css @@ -55,6 +55,17 @@ body { font-style: italic; } +/* When this chat is not the active one, mute the status ribbon so it's + visually clear which chat the global commands / status bar refer to. */ +body.chat-inactive .status.connected, +body.chat-inactive .status.connecting, +body.chat-inactive .status.disconnected, +body.chat-inactive .status.switching { + background: var(--vscode-disabledForeground, #888); + color: var(--vscode-editor-background); + opacity: 0.7; +} + /* ── Messages area ───────────────────────────────────────────── */ #messages { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 815c5398a6..3d3de072aa 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -51,6 +51,7 @@ export type BridgeToWebviewMessage = | { type: "error"; message: string } | { type: "switching"; switching: boolean; targetName?: string } | { type: "userInfo"; name: string } + | { type: "setActive"; active: boolean } | { type: "historyReplay"; entries: Array<{ @@ -317,6 +318,14 @@ export class AgentServerBridge { this.statusBarItem.dispose(); } + /** + * Tell webviews bound to this bridge whether they are the active chat. + * Used to visually mute non-active chats. + */ + setActive(active: boolean): void { + this.broadcastToWebviews({ type: "setActive", active }); + } + /** * Clear the visible chat UI for this bridge's webviews. * Server-side history is left untouched; reload to replay. diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index e06b8cf288..da86ff30bf 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -69,8 +69,10 @@ export function activate(context: vscode.ExtensionContext): void { entry.statusDisposable.dispose(); entry.focusDisposable.dispose(); if (activeChat === entry) { - activeChat = chats.values().next().value; - refreshStatusBar(); + activeChat = undefined; + const next = chats.values().next().value; + if (next) setActive(next); + else refreshStatusBar(); } refreshFocusContext(); }); @@ -130,7 +132,16 @@ export function activate(context: vscode.ExtensionContext): void { } function setActive(entry: ChatEntry): void { + if (activeChat === entry) { + // Even if no change, ensure webview state matches (handles initial) + entry.bridge.setActive(true); + refreshStatusBar(); + return; + } + const prev = activeChat; activeChat = entry; + prev?.bridge.setActive(false); + entry.bridge.setActive(true); refreshStatusBar(); } @@ -247,8 +258,10 @@ function openNewChatPanel( chats.delete(entry); if (activeChat === entry) { // Fall back to any remaining chat (sidebar, if open) - activeChat = chats.values().next().value; - refreshStatusBar(); + activeChat = undefined; + const next = chats.values().next().value; + if (next) setActive(next); + else refreshStatusBar(); } refreshFocusContext(); }); diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index b0c1c892fa..bb4609b751 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -91,6 +91,9 @@ window.addEventListener("message", (event) => { case "historyReplay": chatUI.replayHistory(msg.entries); break; + case "setActive": + document.body.classList.toggle("chat-inactive", !msg.active); + break; } }); From 427f8fd375b2fc31f4eaacf250d6a490383a1a07 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Thu, 23 Apr 2026 20:44:48 -0700 Subject: [PATCH 043/129] feat(typeagent-shell): persist chat panels across VS Code reload Registers a WebviewPanelSerializer for typeagent-shell.chatPanel. Each panel webview now calls vscode.setState({sessionId, sessionName}) on every status update, so VS Code remembers which session each panel was bound to. On window restore VS Code re-instantiates the panel and calls deserializeWebviewPanel; we read the saved sessionId, build a fresh AgentServerBridge with restoreSessionId set, and the bridge rejoins that exact session (history replay then redraws the conversation). If the saved session no longer exists on the server, we fall back to creating a new ephemeral session so the panel still works. Added onWebviewPanel:typeagent-shell.chatPanel to activationEvents so the extension wakes up to restore panels even when nothing else has activated it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 4 +- .../typeagent-shell/src/agentServerBridge.ts | 26 ++++++++- .../typeagent-shell/src/extension.ts | 58 +++++++++++++++++-- .../typeagent-shell/src/webview/main.ts | 6 ++ 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index 9ff8a359a8..cfe78c380b 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -19,7 +19,9 @@ "categories": [ "Other" ], - "activationEvents": [], + "activationEvents": [ + "onWebviewPanel:typeagent-shell.chatPanel" + ], "main": "./dist/extension.js", "contributes": { "viewsContainers": { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 3d3de072aa..f28ba90c32 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -112,15 +112,19 @@ export class AgentServerBridge { // to update the shared status bar when this bridge is active. private onStatusChanged?: () => void; private onWebviewFocusChanged?: (focused: boolean) => void; + /** If set, connect() will join this existing session instead of creating one. */ + private restoreSessionId: string | undefined; constructor(opts?: { ownsStatusBar?: boolean; ephemeralSessionName?: string; displayName?: string; + restoreSessionId?: string; }) { this.ownsStatusBar = opts?.ownsStatusBar ?? true; this.ephemeralSessionName = opts?.ephemeralSessionName; this.displayName = opts?.displayName ?? "TypeAgent"; + this.restoreSessionId = opts?.restoreSessionId; if (this.ownsStatusBar) { this.statusBarItem = vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, @@ -233,7 +237,24 @@ export class AgentServerBridge { clientType: "extension", filter: true, }; + if (this.restoreSessionId) { + // Try to rejoin a session restored from a saved panel. + // If it no longer exists on the server, fall through to the + // ephemeral / default behavior so we still have a chat. + try { + const sessions = await this.connection.listSessions(); + if (sessions.some((s) => s.sessionId === this.restoreSessionId)) { + joinOpts.sessionId = this.restoreSessionId; + } else { + this.restoreSessionId = undefined; + } + } catch { + // listSessions failed — try to join anyway + joinOpts.sessionId = this.restoreSessionId; + } + } if ( + joinOpts.sessionId === undefined && this.ephemeralSessionName && this.ephemeralSessionId === undefined ) { @@ -246,7 +267,10 @@ export class AgentServerBridge { } catch { // Fall back to default join if creation fails } - } else if (this.ephemeralSessionId) { + } else if ( + joinOpts.sessionId === undefined && + this.ephemeralSessionId + ) { joinOpts.sessionId = this.ephemeralSessionId; } diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index da86ff30bf..26ead8f92b 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -92,6 +92,39 @@ export function activate(context: vscode.ExtensionContext): void { ), ); + // Serializer: restore previously-open chat panels on window reopen. + context.subscriptions.push( + vscode.window.registerWebviewPanelSerializer( + "typeagent-shell.chatPanel", + { + async deserializeWebviewPanel( + panel: vscode.WebviewPanel, + state: any, + ): Promise { + panelCounter += 1; + const n = panelCounter; + const sessionId = + state && typeof state === "object" + ? (state.sessionId as string | undefined) + : undefined; + const sessionName = + state && typeof state === "object" + ? (state.sessionName as string | undefined) + : undefined; + const friendly = sessionName ?? `Chat ${n}`; + panel.title = `TypeAgent ${friendly}`; + attachChatPanel(context, provider, panel, { + displayName: friendly, + ephemeralSessionName: sessionId + ? undefined + : `cli-ephemeral-vscode-${n}-${Date.now()}`, + restoreSessionId: sessionId, + }); + }, + }, + ), + ); + // Command: focus the sidebar chat context.subscriptions.push( vscode.commands.registerCommand("typeagent-shell.focusChat", () => { @@ -213,19 +246,33 @@ function openNewChatPanel( ], }, ); + attachChatPanel(context, provider, panel, { + displayName: friendly, + ephemeralSessionName: `cli-ephemeral-vscode-${n}-${Date.now()}`, + }); +} + +function attachChatPanel( + context: vscode.ExtensionContext, + provider: ChatViewProvider, + panel: vscode.WebviewPanel, + opts: { + displayName: string; + ephemeralSessionName?: string; + restoreSessionId?: string; + }, +): void { panel.iconPath = vscode.Uri.joinPath( context.extensionUri, "media", "typeagent-icon.svg", ); - // Each panel: own bridge + ephemeral session deleted on close. - // Server name uses the cli-ephemeral- prefix so the startup sweep - // catches orphans from crashes; the user-visible name is "Chat N". const bridge = new AgentServerBridge({ ownsStatusBar: false, - ephemeralSessionName: `cli-ephemeral-vscode-${n}-${Date.now()}`, - displayName: friendly, + ephemeralSessionName: opts.ephemeralSessionName, + displayName: opts.displayName, + restoreSessionId: opts.restoreSessionId, }); const entry: ChatEntry = { @@ -257,7 +304,6 @@ function openNewChatPanel( entry.focusDisposable.dispose(); chats.delete(entry); if (activeChat === entry) { - // Fall back to any remaining chat (sidebar, if open) activeChat = undefined; const next = chats.values().next().value; if (next) setActive(next); diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index bb4609b751..eb4450485e 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -29,6 +29,12 @@ window.addEventListener("message", (event) => { switch (msg.type) { case "status": chatUI.setStatus(msg.connected, msg.sessionId, msg.sessionName); + if (msg.connected && msg.sessionId) { + vscode.setState({ + sessionId: msg.sessionId, + sessionName: msg.sessionName, + }); + } break; case "userInfo": chatUI.setUserInfo(msg.name); From aeeededa9cb92ea102d355e992acaaf184ecdb79 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 11:22:57 -0700 Subject: [PATCH 044/129] feat(typeagent-shell): loading-history input lock, global shortcuts, rename binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bridge wraps replayHistory in a historyLoading begin/end broadcast. Webview disables input + shows 'Loading history…' placeholder during the replay so the user can't fire a command into a half-restored conversation. - Remove the chatFocused when-clauses on all chord keybindings so they work globally. This intentionally takes over Ctrl+K Ctrl+T (Color Theme), Ctrl+K Ctrl+S (Keyboard Shortcuts), Ctrl+K Ctrl+L (Toggle Fold), and Ctrl+K Ctrl+R (Open Keyboard Shortcuts Reference). - Add Ctrl+K Ctrl+R (Cmd+K Cmd+R on macOS) for Rename Conversation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/package.json | 17 +++++++++-------- .../typeagent-shell/src/agentServerBridge.ts | 15 +++++++++++++++ .../typeagent-shell/src/webview/chatUI.ts | 16 ++++++++++++++++ .../typeagent-shell/src/webview/main.ts | 3 +++ 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/ts/extensions/typeagent-shell/package.json b/ts/extensions/typeagent-shell/package.json index cfe78c380b..41eee420c9 100644 --- a/ts/extensions/typeagent-shell/package.json +++ b/ts/extensions/typeagent-shell/package.json @@ -88,26 +88,27 @@ { "command": "typeagent-shell.newChatPanel", "key": "ctrl+k ctrl+t", - "mac": "cmd+k cmd+t", - "when": "typeagent-shell.chatFocused" + "mac": "cmd+k cmd+t" }, { "command": "typeagent-shell.newSession", "key": "ctrl+k ctrl+n", - "mac": "cmd+k cmd+n", - "when": "typeagent-shell.chatFocused" + "mac": "cmd+k cmd+n" }, { "command": "typeagent-shell.switchSession", "key": "ctrl+k ctrl+s", - "mac": "cmd+k cmd+s", - "when": "typeagent-shell.chatFocused" + "mac": "cmd+k cmd+s" + }, + { + "command": "typeagent-shell.renameSession", + "key": "ctrl+k ctrl+r", + "mac": "cmd+k cmd+r" }, { "command": "typeagent-shell.clearChat", "key": "ctrl+k ctrl+l", - "mac": "cmd+k cmd+l", - "when": "typeagent-shell.chatFocused" + "mac": "cmd+k cmd+l" } ], "configuration": { diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index f28ba90c32..17c95a55f0 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -52,6 +52,7 @@ export type BridgeToWebviewMessage = | { type: "switching"; switching: boolean; targetName?: string } | { type: "userInfo"; name: string } | { type: "setActive"; active: boolean } + | { type: "historyLoading"; loading: boolean } | { type: "historyReplay"; entries: Array<{ @@ -636,6 +637,20 @@ export class AgentServerBridge { * markers so the webview can style replayed entries differently. */ private async replayHistory(session: SessionDispatcher): Promise { + this.broadcastToWebviews({ type: "historyLoading", loading: true }); + try { + await this.replayHistoryInner(session); + } finally { + this.broadcastToWebviews({ + type: "historyLoading", + loading: false, + }); + } + } + + private async replayHistoryInner( + session: SessionDispatcher, + ): Promise { let entries: Array; try { entries = await session.dispatcher.getDisplayHistory(); diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index b58a7461a2..f0aeed8884 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -555,6 +555,22 @@ export class ChatUI { } } + /** + * Disable input and show a "loading history" placeholder until the + * extension host finishes replaying past messages on (re)connect. + */ + public setHistoryLoading(loading: boolean): void { + if (loading) { + this._inputEl.disabled = true; + this._sendBtn.disabled = true; + this._inputEl.placeholder = "Loading history…"; + } else if (!this._isSwitching) { + this._inputEl.disabled = false; + this._sendBtn.disabled = false; + this._inputEl.placeholder = ""; + } + } + /** * Called when the user switches to a different conversation. * Just clears the UI — history will be replayed via beginHistory/endHistory. diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index eb4450485e..86290028a8 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -100,6 +100,9 @@ window.addEventListener("message", (event) => { case "setActive": document.body.classList.toggle("chat-inactive", !msg.active); break; + case "historyLoading": + chatUI.setHistoryLoading(msg.loading); + break; } }); From 64d0a5f6d1255c875b6b998848956b6f8cace821 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 11:29:07 -0700 Subject: [PATCH 045/129] fix(typeagent-shell): open new chat panels in next column to keep them side-by-side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ViewColumn.Beside is computed from the currently active editor, so opening a new chat while the sidebar is focused targeted column 2 even if a chat already lived there — leading to stacked tabs instead of side-by-side panes. Now we scan existing chat panels, find the right-most column they occupy, and open in (max + 1) (capped at ViewColumn.Nine). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/extension.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ts/extensions/typeagent-shell/src/extension.ts b/ts/extensions/typeagent-shell/src/extension.ts index 26ead8f92b..e0acc94661 100644 --- a/ts/extensions/typeagent-shell/src/extension.ts +++ b/ts/extensions/typeagent-shell/src/extension.ts @@ -233,10 +233,24 @@ function openNewChatPanel( const n = panelCounter; const friendly = `Chat ${n}`; const title = `TypeAgent ${friendly}`; + + // Open in the column right of the right-most existing chat panel so + // multiple panels lay out side-by-side instead of stacking as tabs. + let targetColumn: vscode.ViewColumn = vscode.ViewColumn.Beside; + let max = 0; + for (const c of chats) { + const col = c.panel?.viewColumn; + if (typeof col === "number" && col > max) max = col; + } + if (max > 0) { + // ViewColumn.One = 1, Two = 2, Three = 3, ... up to Nine = 9. + targetColumn = Math.min(max + 1, 9) as vscode.ViewColumn; + } + const panel = vscode.window.createWebviewPanel( "typeagent-shell.chatPanel", title, - vscode.ViewColumn.Beside, + targetColumn, { enableScripts: true, retainContextWhenHidden: true, From 32bb652b0407f6979e7e3af0b675cc2fb17f84a9 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 11:44:10 -0700 Subject: [PATCH 046/129] chore(typeagent-shell): tighten .vscodeignore for vsix packaging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/.vscodeignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/extensions/typeagent-shell/.vscodeignore b/ts/extensions/typeagent-shell/.vscodeignore index a04e2bf110..f565ed684f 100644 --- a/ts/extensions/typeagent-shell/.vscodeignore +++ b/ts/extensions/typeagent-shell/.vscodeignore @@ -1,5 +1,9 @@ .gitignore +.vscode/** src/** tsconfig.json esbuild.mjs node_modules/** +**/*.map +**/_dbg.* +*.vsix From 2cd7ed753d3f80465d02daa3d85d3ac0a6ca84f1 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 11:46:28 -0700 Subject: [PATCH 047/129] fix(typeagent-shell): join sessions unfiltered so duplicate tabs both receive responses With filter:true, only the client whose connectionId matched the request received setDisplay/appendDisplay broadcasts, so a second tab on the same conversation never saw the assistant's responses. Per-connection routing (askYesNo, clear, exit) uses callback() and is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/agentServerBridge.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 17c95a55f0..000cf58efe 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -236,7 +236,11 @@ export class AgentServerBridge { let joinOpts: any = { clientType: "extension", - filter: true, + // filter: false so multiple tabs sharing the same session all + // receive setDisplay/appendDisplay broadcasts. Per-connection + // routing (askYesNo, clear, exit) goes through callback() and + // is unaffected by this flag. + filter: false, }; if (this.restoreSessionId) { // Try to rejoin a session restored from a saved panel. @@ -569,7 +573,7 @@ export class AgentServerBridge { try { newSession = await this.connection.joinSession(clientIO, { clientType: "extension", - filter: true, + filter: false, sessionId, }); } catch (e: any) { From c95a550495f144933571efc0351fa9e246d7a9ab Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 11:50:30 -0700 Subject: [PATCH 048/129] fix(typeagent-shell): render user messages from other tabs on shared sessions When a second tab on the same conversation sent a command, the receiving tab dropped the setUserRequest broadcast (since it was treated as a duplicate of a local echo). Now check whether a user-message bubble with that requestId already exists; if not, render it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/webview/chatUI.ts | 13 +++++++++++++ ts/extensions/typeagent-shell/src/webview/main.ts | 12 +++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index f0aeed8884..49be0bf4e1 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -159,6 +159,19 @@ export class ChatUI { this._scrollToBottom(); } + /** + * Returns true if a user message with this requestId is already in the DOM. + * Used to detect the local-echo case so a server setUserRequest broadcast + * isn't duplicated on the originating tab. A second tab joined to the same + * conversation will return false here and render the user message. + */ + public hasUserMessage(requestId: string): boolean { + if (!requestId) return false; + return !!this._messagesEl.querySelector( + `.message.user[data-request-id="${CSS.escape(requestId)}"]`, + ); + } + /** * setDisplay: replace the content of the active agent bubble. */ diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 86290028a8..4afeb92034 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -59,10 +59,16 @@ window.addEventListener("message", (event) => { clientIdOf(msg.message.requestId), ); break; - case "setUserRequest": - // Live: user message shown immediately on send — skip echo. - // History now comes through historyReplay batch, not here. + case "setUserRequest": { + // Echo from server. If we already rendered this locally (this tab + // is the originator), skip. Otherwise, another tab on the same + // conversation sent it — render it so both tabs stay in sync. + const rid = clientIdOf(msg.requestId); + if (rid && !chatUI.hasUserMessage(rid)) { + chatUI.addUserMessage(msg.command, undefined, "done", rid); + } break; + } case "setDisplayInfo": chatUI.setDisplayInfo( msg.source, From 3d1685e5c46b8e34818b463b226fdcd42092ed8d Mon Sep 17 00:00:00 2001 From: talzacc Date: Fri, 24 Apr 2026 12:16:38 -0700 Subject: [PATCH 049/129] fix(typeagent-shell): dedup setDisplay+appendDisplay and drop late temps Three small fixes for multi-tab chat session sync regressions: 1. setAgentDisplay now updates _lastAppendedContent so a follow-up appendDisplay with the same content (chat agent emits setDisplay after streaming the same text) is deduped instead of doubling the bubble. 2. appendAgentDisplay drops temporary status messages that arrive within 2s after onCommandComplete - WebSocket RPC can race behind the faster postMessage commandComplete and leave an orphaned status indicator. 3. replayHistory skips temporary append-display entries - they are transient status updates that should not persist into the transcript. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 49be0bf4e1..5fe61dbd94 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -51,6 +51,11 @@ export class ChatUI { // Dedup: track last appended content to avoid duplicates private _lastAppendedContent?: string; + // Timestamp of last commandComplete; used to drop late-arriving + // "temporary" status messages (e.g. when WebSocket RPC arrives after + // commandComplete went through faster postMessage). + private _lastCompletedAt?: number; + // Track switching state to keep input disabled private _isSwitching = false; @@ -118,6 +123,9 @@ export class ChatUI { this._removeTemporary(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + // New user request — clear the late-temp guard so legitimate + // status messages for this command can render. + this._lastCompletedAt = undefined; const row = document.createElement("div"); row.className = "message user"; @@ -194,6 +202,10 @@ export class ChatUI { if (contentEl) { const html = this._renderDisplayContent(content); contentEl.innerHTML = html; + // Track what's now in the bubble so a follow-up appendDisplay with + // the same content (e.g., a chat agent emitting setDisplay after + // streaming the same text) doesn't double up. + this._lastAppendedContent = html; if (html && html.trim().length > 0) { bubble.classList.remove("empty"); } @@ -216,6 +228,15 @@ export class ChatUI { this._removeStatusIndicator(); if (mode === "temporary") { + // Drop late-arriving status messages that race in after the + // command already completed (otherwise they'd be orphaned with + // nothing to clear them until the next interaction). + if ( + this._lastCompletedAt !== undefined && + Date.now() - this._lastCompletedAt < 2000 + ) { + return; + } // Show as a replaceable status line — each new temporary replaces the last this._removeTemporary(); const el = document.createElement("div"); @@ -348,6 +369,7 @@ export class ChatUI { this._removeStatusIndicator(); this._activeResponseEl = undefined; this._lastAppendedContent = undefined; + this._lastCompletedAt = Date.now(); if (requestId) { const agentBubble = this._agentBubblesByRequestId.get(requestId); @@ -634,6 +656,14 @@ export class ChatUI { ); break; case "append-display": + // Skip temporary status messages (e.g. "Translating...", + // "Executing action ..."). They're meant to be ephemeral + // and were already replaced by real content during the + // original interaction. Replaying them leaves orphan + // status lines in the transcript. + if (e.mode === "temporary") { + break; + } this.appendAgentDisplay( e.message?.message, e.message?.source, From e5fcc00a43cc86793634c02ef54e678f03f91318 Mon Sep 17 00:00:00 2001 From: talzacc Date: Fri, 24 Apr 2026 12:36:42 -0700 Subject: [PATCH 050/129] fix(typeagent-shell): defensive append dedup and forward metrics to peer tabs * appendAgentDisplay now also dedups when the bubble's existing content already ends with the rendered chunk - catches setDisplay+appendDisplay carrying the same payload even if _lastAppendedContent has been overwritten by an intervening event. * New peerMetrics message type: when a command completes on the originating bridge, peer bridges sharing the same session receive the metrics and apply the timing tooltip to their local bubble for that requestId. AgentServerBridge maintains a static bridgesBySession registry, updated on join/switch/disconnect/dispose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 78 +++++++++++++++++++ .../typeagent-shell/src/webview/chatUI.ts | 31 ++++++++ .../typeagent-shell/src/webview/main.ts | 5 ++ 3 files changed, 114 insertions(+) diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index 000cf58efe..c19e435d36 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -48,6 +48,7 @@ export type BridgeToWebviewMessage = | { type: "notify"; event: string; data: any; source: string; seq?: number; requestId?: string } | { type: "commandResult"; requestId: string; result: any } | { type: "commandComplete"; requestId: string; result: any } + | { type: "peerMetrics"; requestId: string; result: any } | { type: "error"; message: string } | { type: "switching"; switching: boolean; targetName?: string } | { type: "userInfo"; name: string } @@ -89,6 +90,36 @@ export type BridgeFromWebviewMessage = * and bridges messages to/from webview panels. */ export class AgentServerBridge { + // Static registry: bridges grouped by sessionId, so the originator of a + // command can broadcast its result/metrics to peer tabs sharing the same + // session (the agent-server clientIO doesn't carry per-request metrics). + private static bridgesBySession: Map> = + new Map(); + + private static registerForSession( + sessionId: string, + bridge: AgentServerBridge, + ): void { + let set = AgentServerBridge.bridgesBySession.get(sessionId); + if (!set) { + set = new Set(); + AgentServerBridge.bridgesBySession.set(sessionId, set); + } + set.add(bridge); + } + + private static unregisterForSession( + sessionId: string, + bridge: AgentServerBridge, + ): void { + const set = AgentServerBridge.bridgesBySession.get(sessionId); + if (!set) return; + set.delete(bridge); + if (set.size === 0) { + AgentServerBridge.bridgesBySession.delete(sessionId); + } + } + private connection: AgentServerConnection | undefined; private session: SessionDispatcher | undefined; private webviews: Set = new Set(); @@ -224,6 +255,12 @@ export class AgentServerBridge { return; } this.isConnected = false; + if (this.session) { + AgentServerBridge.unregisterForSession( + this.session.sessionId, + this, + ); + } this.session = undefined; this.updateStatusBar(false); this.broadcastToWebviews({ type: "status", connected: false }); @@ -284,6 +321,11 @@ export class AgentServerBridge { joinOpts, ); + AgentServerBridge.registerForSession( + this.session.sessionId, + this, + ); + this.isConnected = true; this.updateStatusBar(true); this.broadcastToWebviews({ @@ -321,6 +363,12 @@ export class AgentServerBridge { if (this.connection) { await this.connection.close(); this.connection = undefined; + if (this.session) { + AgentServerBridge.unregisterForSession( + this.session.sessionId, + this, + ); + } this.session = undefined; this.isConnected = false; this.updateStatusBar(false); @@ -587,6 +635,14 @@ export class AgentServerBridge { this.session = newSession; this.nameOverride = undefined; + if (oldSession) { + AgentServerBridge.unregisterForSession( + oldSession.sessionId, + this, + ); + } + AgentServerBridge.registerForSession(newSession.sessionId, this); + // Phase 2: leave the old session (best-effort). // If we were on an ephemeral session and we're moving away from // it, also delete it so it doesn't pile up on the server. @@ -818,6 +874,9 @@ export class AgentServerBridge { requestId: requestId ?? "", result: result ?? null, }); + // Forward metrics to peer tabs sharing this session so their + // bubbles for this requestId also pick up the timing tooltip. + this.broadcastMetricsToPeers(requestId, result ?? null); } catch (e: any) { this.broadcastToWebviews({ type: "commandComplete", @@ -831,6 +890,25 @@ export class AgentServerBridge { } } + private broadcastMetricsToPeers( + requestId: string | undefined, + result: any, + ): void { + if (!requestId || !this.session) return; + const peers = AgentServerBridge.bridgesBySession.get( + this.session.sessionId, + ); + if (!peers) return; + for (const peer of peers) { + if (peer === this) continue; + peer.broadcastToWebviews({ + type: "peerMetrics", + requestId, + result, + }); + } + } + /** * Create a ClientIO implementation that forwards calls to the webview. */ diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 5fe61dbd94..f4bdd39d65 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -267,6 +267,17 @@ export class ChatUI { const bubble = this._getOrCreateAgentBubble(source, timestamp, requestId); const contentEl = bubble.querySelector(".agent-content"); if (contentEl) { + // Defensive dedup: if the bubble's current content already ends + // with this rendered chunk, skip. Catches cases where setDisplay + // and a follow-up appendDisplay carry the same payload. + const existing = contentEl.innerHTML; + if ( + rendered.length > 0 && + existing.length >= rendered.length && + existing.endsWith(rendered) + ) { + return; + } contentEl.innerHTML += rendered; if (rendered && rendered.trim().length > 0) { bubble.classList.remove("empty"); @@ -385,6 +396,26 @@ export class ChatUI { } } + /** + * Apply timing metrics for a request that originated in a peer tab. + * Like onCommandComplete but does NOT touch our local activeResponse, + * temporary status, or pending-user-bubble state — those belong to + * commands issued by THIS tab. + */ + public applyPeerMetrics(requestId: string, result: any): void { + if (!requestId) return; + const agentBubble = this._agentBubblesByRequestId.get(requestId); + if (agentBubble) { + this._renderMetrics(agentBubble, result?.metrics, "agent"); + } + const userRow = this._messagesEl.querySelector( + `.message.user[data-request-id="${CSS.escape(requestId)}"]`, + ) as HTMLElement | null; + if (userRow) { + this._renderMetrics(userRow, result?.metrics, "user"); + } + } + private _renderMetrics( bubbleRow: HTMLElement, metrics: any, diff --git a/ts/extensions/typeagent-shell/src/webview/main.ts b/ts/extensions/typeagent-shell/src/webview/main.ts index 4afeb92034..3bffca139d 100644 --- a/ts/extensions/typeagent-shell/src/webview/main.ts +++ b/ts/extensions/typeagent-shell/src/webview/main.ts @@ -97,6 +97,11 @@ window.addEventListener("message", (event) => { // Command finished — clean up any remaining temporary status chatUI.onCommandComplete(msg.requestId, msg.result); break; + case "peerMetrics": + // Forwarded from a peer tab on the same session — apply the + // timing tooltip to our local bubble for that requestId. + chatUI.applyPeerMetrics(msg.requestId, msg.result); + break; case "switching": chatUI.setSwitching(msg.switching, msg.targetName); break; From 10eb45109836ed0b8463a8bb7067784afb7be312 Mon Sep 17 00:00:00 2001 From: talzacc Date: Fri, 24 Apr 2026 12:50:37 -0700 Subject: [PATCH 051/129] fix(websocket-channel-server): always handle ws.send errors so a dead client doesnt crash the server ws.send() emits write errors asynchronously via the underlying TCP socket. When no callback is supplied, an ECONNRESET on a peer that already disconnected escalates to a process-level uncaughtException. Always supply a callback so the error is captured (logged via debug, forwarded to the originally-supplied callback if any), and wrap the send in try/catch to also handle synchronous failures (e.g. socket closed between the readyState check and the send). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../webSocketChannelServer/src/server.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/ts/packages/utils/webSocketChannelServer/src/server.ts b/ts/packages/utils/webSocketChannelServer/src/server.ts index 7ef5ae2a75..1890c221bb 100644 --- a/ts/packages/utils/webSocketChannelServer/src/server.ts +++ b/ts/packages/utils/webSocketChannelServer/src/server.ts @@ -54,19 +54,27 @@ export async function createWebSocketChannelServer( } return; } - ws.send( - data, - cb - ? (err) => { - if (err) { - debugError(`send error callback: ${err}`); - cb(err); - } else { - cb(null); - } - } - : undefined, - ); + try { + ws.send( + data, + (err) => { + if (err) { + debugError(`send error callback: ${err}`); + } + if (cb) { + cb(err ?? null); + } + }, + ); + } catch (err) { + // Synchronous failures from ws.send (e.g. socket closed + // mid-write) — surface to the caller if it asked, but + // don't escalate to an uncaughtException. + debugError(`send threw: ${err}`); + if (cb) { + cb(err as Error); + } + } }, ); ws.on("message", (data: Buffer) => { From 50d2ddda00be17da4eb16f42d3694a7727049747 Mon Sep 17 00:00:00 2001 From: talzacc Date: Fri, 24 Apr 2026 13:34:44 -0700 Subject: [PATCH 052/129] fix(typeagent-shell): drop all temporary status messages after commandComplete Even with the prior 2s window, status messages can still slip in late from a busy server. Make the late-temp guard unbounded - once the originator's command completes, drop ALL subsequent appendDisplay temporary messages until the next user-initiated send (addUserMessage clears _lastCompletedAt). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/webview/chatUI.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index f4bdd39d65..cf4a13184b 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -228,13 +228,11 @@ export class ChatUI { this._removeStatusIndicator(); if (mode === "temporary") { - // Drop late-arriving status messages that race in after the - // command already completed (otherwise they'd be orphaned with - // nothing to clear them until the next interaction). - if ( - this._lastCompletedAt !== undefined && - Date.now() - this._lastCompletedAt < 2000 - ) { + // Drop status messages after the command already completed — + // they're stale (race-arrived after commandComplete) and would + // otherwise be orphaned with nothing to clear them. Reset on + // the next user message via addUserMessage. + if (this._lastCompletedAt !== undefined) { return; } // Show as a replaceable status line — each new temporary replaces the last From 73f707ac75c3ea1fb9fc352cbea4a470e96650e3 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 15:18:09 -0700 Subject: [PATCH 053/129] feat(code,typeagent-shell): chat-driven conversation actions Add new/rename/switch conversation actions to the code agent under a new code-typeagent-shell sub-action manifest. The agent routes them via ActionIO.takeAction with the 'vscode-shell-action' client action, which the agent server targets to the originating client. The typeagent-shell bridge implements takeAction to dispatch to programmatic helpers that take parameters directly without UI prompts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/agentServerBridge.ts | 145 +++++++++++++++++- ts/packages/agents/code/package.json | 16 +- .../agents/code/src/codeActionHandler.ts | 15 ++ ts/packages/agents/code/src/codeManifest.json | 8 + .../src/vscode/typeAgentShellActionsSchema.ts | 41 +++++ 5 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts diff --git a/ts/extensions/typeagent-shell/src/agentServerBridge.ts b/ts/extensions/typeagent-shell/src/agentServerBridge.ts index c19e435d36..75ab9f88af 100644 --- a/ts/extensions/typeagent-shell/src/agentServerBridge.ts +++ b/ts/extensions/typeagent-shell/src/agentServerBridge.ts @@ -1026,10 +1026,153 @@ export class AgentServerBridge { requestInteraction: (_interaction: PendingInteractionRequest) => {}, interactionResolved: () => {}, interactionCancelled: () => {}, - takeAction: () => {}, + takeAction: (_requestId, action, data) => { + if (action === "vscode-shell-action") { + this.handleShellAction(data).catch((e: any) => { + vscode.window.showErrorMessage( + `Shell action failed: ${e?.message ?? String(e)}`, + ); + }); + } + }, }; } + /** + * Handle a "vscode-shell-action" routed from the code agent. Targeted + * to the originating client by the agent server's takeAction routing, + * so only this bridge (the originator's bridge) receives it. + */ + private async handleShellAction(data: any): Promise { + if (!data || typeof data !== "object") return; + const actionName = data.actionName as string | undefined; + const params = (data.parameters ?? {}) as { + name?: string; + newName?: string; + }; + + switch (actionName) { + case "newConversation": + await this.newConversationFromAgent(params.name); + break; + case "renameConversation": + if (params.newName) { + await this.renameCurrentConversationFromAgent( + params.newName, + ); + } + break; + case "switchConversation": + await this.switchConversationFromAgent(params.name); + break; + } + } + + /** + * Create a new conversation programmatically (from a chat-issued + * action). If `name` is omitted, falls back to the interactive prompt. + */ + private async newConversationFromAgent(name?: string): Promise { + if (!this.connection) { + vscode.window.showWarningMessage("Not connected to agent server."); + return; + } + if (!name || !name.trim()) { + await this.newSession(); + return; + } + + const trimmed = name.trim(); + const sessions = await this.connection.listSessions(); + const existing = sessions.find( + (s) => s.name.toLowerCase() === trimmed.toLowerCase(), + ); + if (existing) { + vscode.window.showWarningMessage( + `A conversation named "${trimmed}" already exists; switching to it.`, + ); + await this.joinSpecificSession(existing.sessionId, existing.name); + return; + } + + const info = await this.connection.createSession(trimmed); + await this.joinSpecificSession(info.sessionId, trimmed); + vscode.window.showInformationMessage( + `Created and switched to conversation "${trimmed}"`, + ); + } + + /** + * Rename the current conversation programmatically (from chat). + */ + private async renameCurrentConversationFromAgent( + newName: string, + ): Promise { + if (!this.connection || !this.session) { + vscode.window.showWarningMessage("No active conversation."); + return; + } + const trimmed = newName.trim(); + if (!trimmed) return; + + const sessions = await this.connection.listSessions(); + const collision = sessions.find( + (s) => + s.sessionId !== this.session!.sessionId && + s.name.toLowerCase() === trimmed.toLowerCase(), + ); + if (collision) { + vscode.window.showErrorMessage( + `A conversation named "${trimmed}" already exists.`, + ); + return; + } + + await this.connection.renameSession(this.session.sessionId, trimmed); + this.nameOverride = trimmed; + this.broadcastToWebviews({ + type: "status", + connected: true, + sessionId: this.session.sessionId, + sessionName: this.getDisplayName(), + }); + this.onStatusChanged?.(); + vscode.window.showInformationMessage( + `Renamed conversation to "${trimmed}"`, + ); + } + + /** + * Switch to a conversation by display name (from chat). Falls back to + * the interactive picker if no name was provided or no match found. + */ + private async switchConversationFromAgent(name?: string): Promise { + if (!this.connection) { + vscode.window.showWarningMessage("Not connected to agent server."); + return; + } + if (!name || !name.trim()) { + await this.switchSession(); + return; + } + + const trimmed = name.trim(); + const sessions = await this.connection.listSessions(); + const match = sessions.find( + (s) => s.name.toLowerCase() === trimmed.toLowerCase(), + ); + if (!match) { + vscode.window.showWarningMessage( + `No conversation named "${trimmed}" found.`, + ); + return; + } + if (match.sessionId === this.session?.sessionId) { + return; + } + await this.joinSpecificSession(match.sessionId, match.name); + } + private broadcastToWebviews(msg: BridgeToWebviewMessage): void { for (const webview of this.webviews) { this.postToWebview(webview, msg); diff --git a/ts/packages/agents/code/package.json b/ts/packages/agents/code/package.json index dc0542e57e..c60ccdb203 100644 --- a/ts/packages/agents/code/package.json +++ b/ts/packages/agents/code/package.json @@ -18,13 +18,14 @@ }, "scripts": { "asc": "asc -i ./src/codeActionsSchema.ts -o ./dist/codeSchema.pas.json -t CodeActions -a CodeActivity", - "asc:all": "concurrently npm:asc npm:asc:debug npm:asc:display npm:asc:general npm:asc:editor npm:asc:workbench npm:asc:extension", + "asc:all": "concurrently npm:asc npm:asc:debug npm:asc:display npm:asc:general npm:asc:editor npm:asc:workbench npm:asc:extension npm:asc:typeagent-shell", "asc:debug": "asc -i ./src/vscode/debugActionsSchema.ts -o ./dist/debugSchema.pas.json -t CodeDebugActions", "asc:display": "asc -i ./src/vscode/displayActionsSchema.ts -o ./dist/displaySchema.pas.json -t CodeDisplayActions", "asc:editor": "asc -i ./src/vscode/editorCodeActionsSchema.ts -o ./dist/editorSchema.pas.json -t EditorCodeActions", "asc:extension": "asc -i ./src/vscode/extensionsActionsSchema.ts -o ./dist/extensionSchema.pas.json -t CodeWorkbenchExtensionActions", "asc:general": "asc -i ./src/vscode/generalActionsSchema.ts -o ./dist/generalSchema.pas.json -t CodeGeneralActions", "asc:workbench": "asc -i ./src/vscode/workbenchCommandActionsSchema.ts -o ./dist/workbenchSchema.pas.json -t CodeWorkbenchActions", + "asc:typeagent-shell": "asc -i ./src/vscode/typeAgentShellActionsSchema.ts -o ./dist/typeAgentShellSchema.pas.json -t TypeAgentShellActions", "build": "concurrently npm:tsc npm:asc:all", "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", "prettier": "prettier --check . --ignore-path ../../../.prettierignore", @@ -142,6 +143,19 @@ "dist/workbenchSchema.pas.json" ] } + }, + "asc:typeagent-shell": { + "dependsOn": [ + "@typeagent/action-schema-compiler#tsc" + ], + "files": { + "inputGlobs": [ + "src/vscode/typeAgentShellActionsSchema.ts" + ], + "outputGlobs": [ + "dist/typeAgentShellSchema.pas.json" + ] + } } } } diff --git a/ts/packages/agents/code/src/codeActionHandler.ts b/ts/packages/agents/code/src/codeActionHandler.ts index 9a187a860c..7c31ed41bf 100644 --- a/ts/packages/agents/code/src/codeActionHandler.ts +++ b/ts/packages/agents/code/src/codeActionHandler.ts @@ -267,6 +267,21 @@ async function executeCodeAction( return undefined; } + // TypeAgent Shell conversation-management actions are routed back to + // the originating extension webview via takeAction (which targets the + // requesting client only). They do NOT use the Coda WebSocket bridge. + if ( + action.actionName === "newConversation" || + action.actionName === "renameConversation" || + action.actionName === "switchConversation" + ) { + context.actionIO.takeAction("vscode-shell-action" as any, { + actionName: action.actionName, + parameters: (action as any).parameters, + }); + return undefined; + } + const agentContext = context.sessionContext.agentContext; const webSocketServer = agentContext.webSocketServer; diff --git a/ts/packages/agents/code/src/codeManifest.json b/ts/packages/agents/code/src/codeManifest.json index 49284f9799..36c9877cc1 100644 --- a/ts/packages/agents/code/src/codeManifest.json +++ b/ts/packages/agents/code/src/codeManifest.json @@ -59,6 +59,14 @@ "schemaFile": "../dist/extensionSchema.pas.json", "schemaType": "CodeWorkbenchExtensionActions" } + }, + "code-typeagent-shell": { + "schema": { + "description": "Manage TypeAgent Shell conversations from chat: create a new conversation, rename the current conversation, or switch to an existing conversation by name. Only meaningful inside the TypeAgent Shell VSCode extension.", + "originalSchemaFile": "./vscode/typeAgentShellActionsSchema.ts", + "schemaFile": "../dist/typeAgentShellSchema.pas.json", + "schemaType": "TypeAgentShellActions" + } } } } diff --git a/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts b/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts new file mode 100644 index 0000000000..12782d76ff --- /dev/null +++ b/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type TypeAgentShellActions = + | NewConversationAction + | RenameConversationAction + | SwitchConversationAction; + +// Create a brand-new conversation in the TypeAgent Shell extension and +// switch the originating tab to it. Use when the user asks to start a +// new chat / new conversation / open a fresh conversation. +export type NewConversationAction = { + actionName: "newConversation"; + parameters: { + // Optional name for the new conversation. If omitted, the user + // will be prompted for a name in the extension UI. + name?: string; + }; +}; + +// Rename the conversation that is currently active in the originating +// tab. Use when the user asks to "rename this conversation" or similar. +export type RenameConversationAction = { + actionName: "renameConversation"; + parameters: { + // The new name to apply to the current conversation. + newName: string; + }; +}; + +// Switch the originating tab to an existing conversation, identified by +// its display name. Use when the user asks to "switch to conversation X" +// or "open the X conversation". If no name is provided, the extension +// will show a picker. +export type SwitchConversationAction = { + actionName: "switchConversation"; + parameters: { + // The display name of the conversation to switch to. + name?: string; + }; +}; From 3a18755e8890b78a34d8167b08ebe30826104e1e Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 15:22:03 -0700 Subject: [PATCH 054/129] feat(code): default-enable code-typeagent-shell sub-schema Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/packages/agents/code/src/codeManifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/packages/agents/code/src/codeManifest.json b/ts/packages/agents/code/src/codeManifest.json index 36c9877cc1..d68b93ac70 100644 --- a/ts/packages/agents/code/src/codeManifest.json +++ b/ts/packages/agents/code/src/codeManifest.json @@ -61,6 +61,7 @@ } }, "code-typeagent-shell": { + "defaultEnabled": true, "schema": { "description": "Manage TypeAgent Shell conversations from chat: create a new conversation, rename the current conversation, or switch to an existing conversation by name. Only meaningful inside the TypeAgent Shell VSCode extension.", "originalSchemaFile": "./vscode/typeAgentShellActionsSchema.ts", From 737c9ba36f6e80743ba6525b3d5abb02dab8b524 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 15:58:07 -0700 Subject: [PATCH 055/129] fix(typeagent-shell): clear temp when permanent lands in empty bubble The peer (non-originator) tab pre-creates an empty bubble via setDisplayInfo before temporary status arrives. The previous !hadBubble check then short-circuited _removeTemporary, leaving the temp orphaned. Check the bubble's content area instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../typeagent-shell/src/webview/chatUI.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index cf4a13184b..3d42d2f392 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -255,11 +255,15 @@ export class ChatUI { } this._lastAppendedContent = rendered; - // Remove temporary when first real content arrives - const hadBubble = - (requestId && this._agentBubblesByRequestId.has(requestId)) || - !!this._activeResponseEl; - if (!hadBubble) { + // Remove temporary when first real content arrives (either no bubble + // yet, or an empty bubble that was pre-created by setDisplayInfo). + const existingBubble = + requestId && this._agentBubblesByRequestId.has(requestId) + ? this._agentBubblesByRequestId.get(requestId)! + : this._activeResponseEl; + const existingContent = + existingBubble?.querySelector(".agent-content")?.innerHTML ?? ""; + if (existingContent.trim() === "") { this._removeTemporary(); } const bubble = this._getOrCreateAgentBubble(source, timestamp, requestId); From cb8ddab290fd9eaffbe955c216108dfd3d08f9f6 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 16:06:46 -0700 Subject: [PATCH 056/129] fix(typeagent-shell): peer tab clears temp + engages late-guard on metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The peer (non-originator) tab never receives commandComplete, so any "Executing action…" temp that arrives can linger. Use the peerMetrics signal (sent by the originator after its commandComplete) as the peer's equivalent: clear current temp and engage the late-temp guard so further stale temps for this command are dropped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/extensions/typeagent-shell/src/webview/chatUI.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ts/extensions/typeagent-shell/src/webview/chatUI.ts b/ts/extensions/typeagent-shell/src/webview/chatUI.ts index 3d42d2f392..4cd4cf28ea 100644 --- a/ts/extensions/typeagent-shell/src/webview/chatUI.ts +++ b/ts/extensions/typeagent-shell/src/webview/chatUI.ts @@ -400,12 +400,15 @@ export class ChatUI { /** * Apply timing metrics for a request that originated in a peer tab. - * Like onCommandComplete but does NOT touch our local activeResponse, - * temporary status, or pending-user-bubble state — those belong to - * commands issued by THIS tab. + * Renders metrics like onCommandComplete, and also clears any + * lingering temporary status + engages the late-temp guard, since + * the originator has finished and any further temporary status for + * this request would be stale. */ public applyPeerMetrics(requestId: string, result: any): void { if (!requestId) return; + this._removeTemporary(); + this._lastCompletedAt = Date.now(); const agentBubble = this._agentBubblesByRequestId.get(requestId); if (agentBubble) { this._renderMetrics(agentBubble, result?.metrics, "agent"); From a9daced70ec72ae599369fe0cf11c914cea7dd05 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 16:14:22 -0700 Subject: [PATCH 057/129] feat(code): strengthen typeagent-shell action descriptions for translator routing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/packages/agents/code/src/codeManifest.json | 2 +- .../src/vscode/typeAgentShellActionsSchema.ts | 42 +++++++++++++------ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/ts/packages/agents/code/src/codeManifest.json b/ts/packages/agents/code/src/codeManifest.json index d68b93ac70..38bb4a15ee 100644 --- a/ts/packages/agents/code/src/codeManifest.json +++ b/ts/packages/agents/code/src/codeManifest.json @@ -63,7 +63,7 @@ "code-typeagent-shell": { "defaultEnabled": true, "schema": { - "description": "Manage TypeAgent Shell conversations from chat: create a new conversation, rename the current conversation, or switch to an existing conversation by name. Only meaningful inside the TypeAgent Shell VSCode extension.", + "description": "Manage TypeAgent Shell conversations (chat tabs in the TypeAgent Shell VSCode extension): create a new conversation, rename the current conversation, or switch to an existing conversation by name. Trigger keywords: 'conversation', 'chat', 'chat tab', 'new conversation', 'rename conversation', 'switch conversation'. Does NOT handle files, windows, browser tabs, editor tabs, programs, or onboarding.", "originalSchemaFile": "./vscode/typeAgentShellActionsSchema.ts", "schemaFile": "../dist/typeAgentShellSchema.pas.json", "schemaType": "TypeAgentShellActions" diff --git a/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts b/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts index 12782d76ff..04875b0902 100644 --- a/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts +++ b/ts/packages/agents/code/src/vscode/typeAgentShellActionsSchema.ts @@ -6,36 +6,52 @@ export type TypeAgentShellActions = | RenameConversationAction | SwitchConversationAction; -// Create a brand-new conversation in the TypeAgent Shell extension and -// switch the originating tab to it. Use when the user asks to start a -// new chat / new conversation / open a fresh conversation. +// Create a brand-new TypeAgent Shell conversation (a new chat tab in +// the TypeAgent Shell VSCode extension) and switch the current tab to +// it. Use ONLY for managing TypeAgent Shell conversation tabs — do NOT +// use for opening files, launching programs, web browsing, onboarding +// scaffolds, or anything outside the TypeAgent Shell chat itself. +// +// Trigger phrases include: "new conversation", "create a conversation", +// "start a new chat", "open a new chat", "make a new conversation +// named X", "new TypeAgent conversation". export type NewConversationAction = { actionName: "newConversation"; parameters: { - // Optional name for the new conversation. If omitted, the user - // will be prompted for a name in the extension UI. + // Optional display name for the new conversation. If omitted, + // the user will be prompted in the extension UI. name?: string; }; }; -// Rename the conversation that is currently active in the originating -// tab. Use when the user asks to "rename this conversation" or similar. +// Rename the TypeAgent Shell conversation that is currently active in +// this chat tab. Use ONLY for renaming the current TypeAgent Shell +// conversation — do NOT use for renaming files, variables, etc. +// +// Trigger phrases include: "rename this conversation", "rename the +// current conversation", "change the conversation name to X", "call +// this conversation X", "rename this chat". export type RenameConversationAction = { actionName: "renameConversation"; parameters: { - // The new name to apply to the current conversation. + // The new display name to apply to the current conversation. newName: string; }; }; -// Switch the originating tab to an existing conversation, identified by -// its display name. Use when the user asks to "switch to conversation X" -// or "open the X conversation". If no name is provided, the extension -// will show a picker. +// Switch the current TypeAgent Shell chat tab to a different existing +// conversation, identified by its display name. Use ONLY for switching +// between TypeAgent Shell conversations — do NOT use for switching +// browser tabs, editor tabs, windows, or workspaces. +// +// Trigger phrases include: "switch to conversation X", "open the X +// conversation", "go to the X chat", "switch conversation to X", +// "switch chat". export type SwitchConversationAction = { actionName: "switchConversation"; parameters: { - // The display name of the conversation to switch to. + // The display name of the conversation to switch to. If omitted, + // the user will be shown a picker in the extension UI. name?: string; }; }; From 9d881a3814bff7e3fb79aa24dbc2f880c6c21158 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 16:52:30 -0700 Subject: [PATCH 058/129] fix(code): return ActionResult from typeagent-shell handlers Dispatcher logged "did not return a result entity" because the handler returned undefined. Return a short status string via createActionResult. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agents/code/src/codeActionHandler.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ts/packages/agents/code/src/codeActionHandler.ts b/ts/packages/agents/code/src/codeActionHandler.ts index 7c31ed41bf..8907dc6c87 100644 --- a/ts/packages/agents/code/src/codeActionHandler.ts +++ b/ts/packages/agents/code/src/codeActionHandler.ts @@ -16,7 +16,10 @@ import { fileURLToPath } from "url"; import os from "os"; import registerDebug from "debug"; import chalk from "chalk"; -import { createActionResultFromError } from "@typeagent/agent-sdk/helpers/action"; +import { + createActionResult, + createActionResultFromError, +} from "@typeagent/agent-sdk/helpers/action"; const debug = registerDebug("typeagent:code"); @@ -275,11 +278,22 @@ async function executeCodeAction( action.actionName === "renameConversation" || action.actionName === "switchConversation" ) { + const params = (action as any).parameters ?? {}; context.actionIO.takeAction("vscode-shell-action" as any, { actionName: action.actionName, - parameters: (action as any).parameters, + parameters: params, }); - return undefined; + const summary = + action.actionName === "newConversation" + ? params.name + ? `Created new conversation "${params.name}".` + : "Creating a new conversation." + : action.actionName === "renameConversation" + ? `Renamed current conversation to "${params.newName}".` + : params.name + ? `Switched to conversation "${params.name}".` + : "Switching conversation."; + return createActionResult(summary); } const agentContext = context.sessionContext.agentContext; From 2c038d0a50d59cb4a8c725d0031b9e439edadde7 Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 21:06:27 -0700 Subject: [PATCH 059/129] feat(completion-ui): extract dropdown menu + toggle to shared package Move LocalSearchMenuUI, CompletionToggle, and SearchMenuUI interface from the shell renderer into a new @typeagent/completion-ui package so the typeagent-shell VS Code extension webview can reuse them. Shell's existing files in src/renderer/src/searchMenuUI/ now re-export from the shared package, preserving all existing import paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ts/packages/completionUI/package.json | 38 ++++ .../completionUI/src/completionToggle.ts | 55 +++++ ts/packages/completionUI/src/index.ts | 14 ++ .../completionUI/src/localSearchMenuUI.ts | 192 ++++++++++++++++++ ts/packages/completionUI/src/searchMenuUI.ts | 24 +++ ts/packages/completionUI/src/styles.css | 101 +++++++++ ts/packages/completionUI/src/tsconfig.json | 10 + ts/packages/completionUI/tsconfig.json | 8 + ts/packages/shell/package.json | 1 + .../src/searchMenuUI/completionToggle.ts | 57 +----- .../src/searchMenuUI/localSearchMenuUI.ts | 192 +----------------- .../renderer/src/searchMenuUI/searchMenuUI.ts | 15 +- ts/pnpm-lock.yaml | 19 ++ 13 files changed, 474 insertions(+), 252 deletions(-) create mode 100644 ts/packages/completionUI/package.json create mode 100644 ts/packages/completionUI/src/completionToggle.ts create mode 100644 ts/packages/completionUI/src/index.ts create mode 100644 ts/packages/completionUI/src/localSearchMenuUI.ts create mode 100644 ts/packages/completionUI/src/searchMenuUI.ts create mode 100644 ts/packages/completionUI/src/styles.css create mode 100644 ts/packages/completionUI/src/tsconfig.json create mode 100644 ts/packages/completionUI/tsconfig.json diff --git a/ts/packages/completionUI/package.json b/ts/packages/completionUI/package.json new file mode 100644 index 0000000000..129ef5af67 --- /dev/null +++ b/ts/packages/completionUI/package.json @@ -0,0 +1,38 @@ +{ + "name": "@typeagent/completion-ui", + "version": "0.0.1", + "private": true, + "description": "Shared DOM UI components for TypeAgent command-completion (dropdown menu and toggle).", + "homepage": "https://github.com/microsoft/TypeAgent#readme", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/TypeAgent.git", + "directory": "ts/packages/completionUI" + }, + "license": "MIT", + "author": "Microsoft", + "type": "module", + "exports": { + ".": "./dist/index.js", + "./styles.css": "./dist/styles.css" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "npm run tsc && npm run copy:css", + "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", + "copy:css": "node -e \"require('fs').mkdirSync('dist',{recursive:true});require('fs').copyFileSync('src/styles.css','dist/styles.css')\"", + "prettier": "prettier --check . --ignore-path ../../.prettierignore", + "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", + "tsc": "tsc -b" + }, + "dependencies": { + "agent-dispatcher": "workspace:*" + }, + "devDependencies": { + "prettier": "^3.5.3", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" + } +} diff --git a/ts/packages/completionUI/src/completionToggle.ts b/ts/packages/completionUI/src/completionToggle.ts new file mode 100644 index 0000000000..0887c9635c --- /dev/null +++ b/ts/packages/completionUI/src/completionToggle.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type CompletionToggleDirection = "expand" | "collapse"; + +export class CompletionToggle { + private readonly element: HTMLDivElement; + private direction: CompletionToggleDirection; + + constructor( + direction: CompletionToggleDirection, + onToggleMode: () => void, + ) { + this.direction = direction; + this.element = document.createElement("div"); + this.applyDirection(); + this.element.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + onToggleMode(); + }; + } + + public setDirection(direction: CompletionToggleDirection) { + if (this.direction === direction) { + return; + } + this.direction = direction; + this.applyDirection(); + } + + public show() { + this.element.style.display = ""; + } + + public hide() { + this.element.style.display = "none"; + } + + public getElement(): HTMLDivElement { + return this.element; + } + + public remove() { + this.element.remove(); + } + + private applyDirection() { + this.element.className = `completion-toggle completion-toggle-${this.direction}`; + // ▲ expand: the completion menu opens *above* the input, so "expand up". + // ▼ collapse: shrink back to inline ghost text. + this.element.textContent = + this.direction === "expand" ? "\u25B2" : "\u25BC"; + } +} diff --git a/ts/packages/completionUI/src/index.ts b/ts/packages/completionUI/src/index.ts new file mode 100644 index 0000000000..036a8ee8fb --- /dev/null +++ b/ts/packages/completionUI/src/index.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { + type SearchMenuItem, + type SearchMenuPosition, + type SearchMenuUIUpdateData, + type SearchMenuUI, +} from "./searchMenuUI.js"; +export { LocalSearchMenuUI } from "./localSearchMenuUI.js"; +export { + CompletionToggle, + type CompletionToggleDirection, +} from "./completionToggle.js"; diff --git a/ts/packages/completionUI/src/localSearchMenuUI.ts b/ts/packages/completionUI/src/localSearchMenuUI.ts new file mode 100644 index 0000000000..bdb1ae443c --- /dev/null +++ b/ts/packages/completionUI/src/localSearchMenuUI.ts @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + SearchMenuItem, + SearchMenuPosition, + SearchMenuUI, + SearchMenuUIUpdateData, +} from "./searchMenuUI.js"; + +/** + * DOM-based dropdown completion menu. Renders into document.body. + * + * Caller is responsible for keyboard wiring (use adjustSelection / selectCompletion / close) + * and for calling update({position, prefix, items}) when state changes. + */ +export class LocalSearchMenuUI implements SearchMenuUI { + private readonly searchContainer: HTMLDivElement; + private readonly scrollBar: HTMLDivElement; + private readonly scrollBarIndicator: HTMLDivElement; + private completions: HTMLUListElement | undefined; + private selected: number = -1; + private items: SearchMenuItem[] = []; + private prefix: string = ""; + private top: number = 0; + + private get closed() { + return this.searchContainer.parentElement === null; + } + + constructor( + private readonly onCompletion: (item: SearchMenuItem) => void, + private readonly visibleItemsCount = 15, + ) { + this.onCompletion = onCompletion; + this.searchContainer = document.createElement("div"); + this.searchContainer.className = "autocomplete-container"; + + this.searchContainer.onwheel = (event) => { + this.adjustSelection(event.deltaY); + }; + + this.scrollBar = document.createElement("div"); + this.scrollBar.classList.add("autocomplete-scrollbar"); + this.scrollBarIndicator = document.createElement("div"); + this.scrollBarIndicator.classList.add( + "autocomplete-scrollbar-indicator", + ); + this.scrollBar.appendChild(this.scrollBarIndicator); + this.searchContainer.append(this.scrollBar); + + document.body.appendChild(this.searchContainer); + } + + public close() { + this.searchContainer.remove(); + } + + public selectCompletion() { + if (this.closed) { + return; + } + const index = this.selected; + if (index >= 0 && index < this.items.length) { + this.onCompletion(this.items[index]); + } + } + + public update(data: SearchMenuUIUpdateData) { + if (this.closed) { + return; + } + + if (data.position) { + this.setPosition(data.position); + } + + let updateDisplay: boolean = false; + if (data.prefix !== undefined) { + updateDisplay = true; + this.prefix = data.prefix; + } + + if (data.items !== undefined) { + updateDisplay = true; + this.items = data.items; + this.selected = 0; + this.top = 0; + } + + if (updateDisplay) { + this.updateDisplay(); + } + } + + private setPosition(position: SearchMenuPosition) { + this.searchContainer.style.left = `${position.left}px`; + this.searchContainer.style.bottom = `${position.bottom}px`; + } + + public adjustSelection(deltaY: number) { + if (this.closed) { + return; + } + if (deltaY > 0 && this.selected < this.items.length - 1) { + this.selected++; + } else if (deltaY < 0 && this.selected > 0) { + this.selected--; + } + + this.updateDisplay(); + } + + private updateDisplay() { + if (this.completions) { + this.searchContainer.removeChild(this.completions); + this.completions = undefined; + this.searchContainer.style.visibility = "hidden"; + } + if (this.items.length > 0) { + this.completions = document.createElement("ul"); + this.completions.className = "completions"; + + if ( + this.selected < this.top || + this.selected >= this.top + this.visibleItemsCount + ) { + this.top = this.selected; + } + if (this.top + this.visibleItemsCount > this.items.length) { + this.top = Math.max( + 0, + this.items.length - this.visibleItemsCount, + ); + } + for (let i = this.top; i < this.top + this.visibleItemsCount; i++) { + const li = document.createElement("li"); + const item = this.items[i]; + if (i === this.selected) { + li.className = "completion-selected"; + } + if (item.emojiChar) { + const symbolSpan = document.createElement("span"); + symbolSpan.className = "search-symbol"; + symbolSpan.innerText = item.emojiChar; + li.appendChild(symbolSpan); + } + const prefixSpan = document.createElement("span"); + prefixSpan.className = "search-prefix"; + prefixSpan.innerText = this.prefix; + li.appendChild(prefixSpan); + const suffix = item.matchText.substring(this.prefix.length); + const resultSpan = document.createElement("span"); + resultSpan.className = "search-suffix"; + resultSpan.innerText = suffix; + li.appendChild(resultSpan); + this.completions.appendChild(li); + + li.onmousedown = () => { + this.onCompletion(item); + }; + li.onmousemove = () => { + if (this.selected != i) { + this.selected = i; + this.updateDisplay(); + } + }; + + if (i === this.items.length - 1) { + break; + } + } + this.searchContainer.appendChild(this.completions); + this.searchContainer.style.visibility = "visible"; + + if (this.items.length > this.visibleItemsCount) { + this.scrollBar.style.visibility = "visible"; + this.setScrollBarPosition(); + } else { + this.scrollBar.style.visibility = "hidden"; + } + } + } + + private setScrollBarPosition() { + const heightPercentage = this.visibleItemsCount / this.items.length; + this.scrollBarIndicator.style.height = `${this.searchContainer.scrollHeight * heightPercentage}px`; + + const offsetPercentage = this.top / this.items.length; + this.scrollBarIndicator.style.top = `${this.searchContainer.scrollHeight * offsetPercentage}px`; + } +} diff --git a/ts/packages/completionUI/src/searchMenuUI.ts b/ts/packages/completionUI/src/searchMenuUI.ts new file mode 100644 index 0000000000..a7ac4ca312 --- /dev/null +++ b/ts/packages/completionUI/src/searchMenuUI.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import type { SearchMenuItem } from "agent-dispatcher/helpers/completion"; + +export type { SearchMenuItem }; + +export type SearchMenuPosition = { + left: number; + bottom: number; +}; + +export type SearchMenuUIUpdateData = { + position?: SearchMenuPosition; + prefix?: string; + items?: SearchMenuItem[]; +}; + +export interface SearchMenuUI { + update(data: SearchMenuUIUpdateData): void; + adjustSelection(deltaY: number): void; + selectCompletion(): void; + close(): void; +} diff --git a/ts/packages/completionUI/src/styles.css b/ts/packages/completionUI/src/styles.css new file mode 100644 index 0000000000..58bdb4e4bf --- /dev/null +++ b/ts/packages/completionUI/src/styles.css @@ -0,0 +1,101 @@ +/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ + +/* Shared dropdown completion menu styles. */ +.autocomplete-container { + box-sizing: border-box; + font-family: inherit; + border-radius: 12px; + position: absolute; + bottom: 100%; + z-index: 100; + overflow: hidden; + border: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: + 0 4px 16px rgba(0, 0, 0, 0.12), + 0 1px 4px rgba(0, 0, 0, 0.06); + background-color: #fffffff0; + width: max-content; + backdrop-filter: blur(8px); +} + +.autocomplete-container .completions { + padding: 0; + margin: 0; +} + +.autocomplete-container .completions li { + list-style-type: none; + padding: 6px 12px; + cursor: pointer; + transition: background-color 0.15s ease; +} + +.autocomplete-container .completions li:hover { + background-color: rgba(167, 139, 250, 0.08); +} + +.autocomplete-container .completions li .search-prefix { + font-weight: bold; +} + +.autocomplete-container .completions .completion-selected, +.autocomplete-container .completions .completion-selected:hover { + background: linear-gradient(135deg, #a78bfa, #7c3aed); + color: white; +} + +.autocomplete-container .completions li .search-symbol { + margin-right: 5px; +} + +.autocomplete-scrollbar { + background-color: rgba(0, 0, 0, 0.04); + width: 6px; + top: 4px; + bottom: 4px; + right: 2px; + position: absolute; + border-radius: 3px; +} + +.autocomplete-scrollbar-indicator { + background-color: rgba(124, 58, 237, 0.3); + width: 100%; + position: absolute; + border-radius: 3px; +} + +.completion-toggle { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 8px; + line-height: 1; + opacity: 0; + transition: opacity 0.15s ease; + color: #a78bfa; + padding: 3px 8px; + user-select: none; +} + +.completion-toggle::before, +.completion-toggle::after { + content: ""; + flex: 1; + height: 1px; + background-color: currentColor; + opacity: 0.4; +} + +.completion-toggle::before { + margin-right: 6px; +} + +.completion-toggle::after { + margin-left: 6px; +} + +.completion-toggle:hover { + opacity: 1 !important; +} diff --git a/ts/packages/completionUI/src/tsconfig.json b/ts/packages/completionUI/src/tsconfig.json new file mode 100644 index 0000000000..84e1eb083b --- /dev/null +++ b/ts/packages/completionUI/src/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": ".", + "outDir": "../dist", + "lib": ["ES2022", "DOM"] + }, + "include": ["./**/*.ts"] +} diff --git a/ts/packages/completionUI/tsconfig.json b/ts/packages/completionUI/tsconfig.json new file mode 100644 index 0000000000..313a2bc21a --- /dev/null +++ b/ts/packages/completionUI/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true + }, + "include": [], + "references": [{ "path": "./src" }] +} diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index 691ca37264..c5f338d25b 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -63,6 +63,7 @@ "@typeagent/agent-sdk": "workspace:*", "@typeagent/agent-server-client": "workspace:*", "@typeagent/common-utils": "workspace:*", + "@typeagent/completion-ui": "workspace:*", "@typeagent/dispatcher-rpc": "workspace:*", "agent-dispatcher": "workspace:*", "aiclient": "workspace:*", diff --git a/ts/packages/shell/src/renderer/src/searchMenuUI/completionToggle.ts b/ts/packages/shell/src/renderer/src/searchMenuUI/completionToggle.ts index 0887c9635c..83bc11cd17 100644 --- a/ts/packages/shell/src/renderer/src/searchMenuUI/completionToggle.ts +++ b/ts/packages/shell/src/renderer/src/searchMenuUI/completionToggle.ts @@ -1,55 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -export type CompletionToggleDirection = "expand" | "collapse"; - -export class CompletionToggle { - private readonly element: HTMLDivElement; - private direction: CompletionToggleDirection; - - constructor( - direction: CompletionToggleDirection, - onToggleMode: () => void, - ) { - this.direction = direction; - this.element = document.createElement("div"); - this.applyDirection(); - this.element.onmousedown = (e) => { - e.preventDefault(); - e.stopPropagation(); - onToggleMode(); - }; - } - - public setDirection(direction: CompletionToggleDirection) { - if (this.direction === direction) { - return; - } - this.direction = direction; - this.applyDirection(); - } - - public show() { - this.element.style.display = ""; - } - - public hide() { - this.element.style.display = "none"; - } - - public getElement(): HTMLDivElement { - return this.element; - } - - public remove() { - this.element.remove(); - } - - private applyDirection() { - this.element.className = `completion-toggle completion-toggle-${this.direction}`; - // ▲ expand: the completion menu opens *above* the input, so "expand up". - // ▼ collapse: shrink back to inline ghost text. - this.element.textContent = - this.direction === "expand" ? "\u25B2" : "\u25BC"; - } -} +// Re-export from the shared @typeagent/completion-ui package. +export { + CompletionToggle, + type CompletionToggleDirection, +} from "@typeagent/completion-ui"; diff --git a/ts/packages/shell/src/renderer/src/searchMenuUI/localSearchMenuUI.ts b/ts/packages/shell/src/renderer/src/searchMenuUI/localSearchMenuUI.ts index 900b42db47..5554d9e418 100644 --- a/ts/packages/shell/src/renderer/src/searchMenuUI/localSearchMenuUI.ts +++ b/ts/packages/shell/src/renderer/src/searchMenuUI/localSearchMenuUI.ts @@ -1,193 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { - SearchMenuItem, - SearchMenuPosition, - SearchMenuUI, - SearchMenuUIUpdateData, -} from "./searchMenuUI"; - -export class LocalSearchMenuUI implements SearchMenuUI { - private readonly searchContainer: HTMLDivElement; - private readonly scrollBar: HTMLDivElement; - private readonly scrollBarIndicator: HTMLDivElement; - private completions: HTMLUListElement | undefined; - private selected: number = -1; - private items: SearchMenuItem[] = []; - private prefix: string = ""; - private top: number = 0; - - private get closed() { - return this.searchContainer.parentElement === null; - } - - constructor( - private readonly onCompletion: (item: SearchMenuItem) => void, - private readonly visibleItemsCount = 15, - ) { - this.onCompletion = onCompletion; - this.searchContainer = document.createElement("div"); - this.searchContainer.className = "autocomplete-container"; - - this.searchContainer.onwheel = (event) => { - this.adjustSelection(event.deltaY); - }; - - this.scrollBar = document.createElement("div"); - this.scrollBar.classList.add("autocomplete-scrollbar"); - this.scrollBarIndicator = document.createElement("div"); - this.scrollBarIndicator.classList.add( - "autocomplete-scrollbar-indicator", - ); - this.scrollBar.appendChild(this.scrollBarIndicator); - this.searchContainer.append(this.scrollBar); - - document.body.appendChild(this.searchContainer); - } - - public close() { - this.searchContainer.remove(); - } - - public selectCompletion() { - if (this.closed) { - return; - } - const index = this.selected; - if (index >= 0 && index < this.items.length) { - this.onCompletion(this.items[index]); - } - } - - public update(data: SearchMenuUIUpdateData) { - if (this.closed) { - return; - } - - if (data.position) { - this.setPosition(data.position); - } - - let updateDisplay: boolean = false; - if (data.prefix !== undefined) { - updateDisplay = true; - this.prefix = data.prefix; - } - - if (data.items !== undefined) { - updateDisplay = true; - this.items = data.items; - this.selected = 0; - this.top = 0; - } - - if (updateDisplay) { - this.updateDisplay(); - } - } - - private setPosition(position: SearchMenuPosition) { - this.searchContainer.style.left = `${position.left}px`; - this.searchContainer.style.bottom = `${position.bottom}px`; - } - - public adjustSelection(deltaY: number) { - if (this.closed) { - return; - } - if (deltaY > 0 && this.selected < this.items.length - 1) { - this.selected++; - } else if (deltaY < 0 && this.selected > 0) { - this.selected--; - } - - this.updateDisplay(); - } - - private updateDisplay() { - if (this.completions) { - this.searchContainer.removeChild(this.completions); - this.completions = undefined; - this.searchContainer.style.visibility = "hidden"; - } - if (this.items.length > 0) { - this.completions = document.createElement("ul"); - this.completions.className = "completions"; - - if ( - this.selected < this.top || - this.selected >= this.top + this.visibleItemsCount - ) { - this.top = this.selected; - } - if (this.top + this.visibleItemsCount > this.items.length) { - this.top = Math.max( - 0, - this.items.length - this.visibleItemsCount, - ); - } - for (let i = this.top; i < this.top + this.visibleItemsCount; i++) { - const li = document.createElement("li"); - const item = this.items[i]; - if (i === this.selected) { - li.className = "completion-selected"; // highlight the selected completion - } - if (item.emojiChar) { - const symbolSpan = document.createElement("span"); - symbolSpan.className = "search-symbol"; - symbolSpan.innerText = item.emojiChar; - li.appendChild(symbolSpan); - } - // make a span for the prefix - const prefixSpan = document.createElement("span"); - prefixSpan.className = "search-prefix"; - prefixSpan.innerText = this.prefix; - li.appendChild(prefixSpan); - // make a span for the suffix - const suffix = item.matchText.substring(this.prefix.length); - const resultSpan = document.createElement("span"); - resultSpan.className = "search-suffix"; - resultSpan.innerText = suffix; - li.appendChild(resultSpan); - this.completions.appendChild(li); - - // handle mouse events - li.onmousedown = () => { - this.onCompletion(item); - }; - li.onmousemove = () => { - if (this.selected != i) { - this.selected = i; - this.updateDisplay(); - } - }; - - if (i === this.items.length - 1) { - break; - } - } - this.searchContainer.appendChild(this.completions); - this.searchContainer.style.visibility = "visible"; - - // Show scrollbar only when items overflow the visible area - if (this.items.length > this.visibleItemsCount) { - this.scrollBar.style.visibility = "visible"; - this.setScrollBarPosition(); - } else { - this.scrollBar.style.visibility = "hidden"; - } - } - } - - /** - * Sets the offset and size of the scrollbar indicator - */ - private setScrollBarPosition() { - const heightPercentage = this.visibleItemsCount / this.items.length; - this.scrollBarIndicator.style.height = `${this.searchContainer.scrollHeight * heightPercentage}px`; - - const offsetPercentage = this.top / this.items.length; - this.scrollBarIndicator.style.top = `${this.searchContainer.scrollHeight * offsetPercentage}px`; - } -} +// Re-export from the shared @typeagent/completion-ui package. +export { LocalSearchMenuUI } from "@typeagent/completion-ui"; diff --git a/ts/packages/shell/src/renderer/src/searchMenuUI/searchMenuUI.ts b/ts/packages/shell/src/renderer/src/searchMenuUI/searchMenuUI.ts index d1b33138f6..52dba85aef 100644 --- a/ts/packages/shell/src/renderer/src/searchMenuUI/searchMenuUI.ts +++ b/ts/packages/shell/src/renderer/src/searchMenuUI/searchMenuUI.ts @@ -1,16 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { +// Re-export from the shared @typeagent/completion-ui package. Kept as a +// stub to preserve existing relative-import paths inside the shell renderer. +export type { SearchMenuItem, SearchMenuPosition, SearchMenuUIUpdateData, -} from "../../../preload/electronTypes"; -export type { SearchMenuItem, SearchMenuPosition, SearchMenuUIUpdateData }; - -export interface SearchMenuUI { - update(data: SearchMenuUIUpdateData): void; - adjustSelection(deltaY: number): void; - selectCompletion(): void; - close(): void; -} + SearchMenuUI, +} from "@typeagent/completion-ui"; diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index a08d1e11c2..1a1e6c808d 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -3260,6 +3260,22 @@ importers: specifier: ~5.4.5 version: 5.4.5 + packages/completionUI: + dependencies: + agent-dispatcher: + specifier: workspace:* + version: link:../dispatcher/dispatcher + devDependencies: + prettier: + specifier: ^3.5.3 + version: 3.5.3 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: ~5.4.5 + version: 5.4.5 + packages/defaultAgentProvider: dependencies: '@modelcontextprotocol/sdk': @@ -4287,6 +4303,9 @@ importers: '@typeagent/common-utils': specifier: workspace:* version: link:../utils/commonUtils + '@typeagent/completion-ui': + specifier: workspace:* + version: link:../completionUI '@typeagent/dispatcher-rpc': specifier: workspace:* version: link:../dispatcher/rpc From 45f82d0cbfa13b3f6964d8f01a11cc0434cf8cab Mon Sep 17 00:00:00 2001 From: Tal Zaccai Date: Fri, 24 Apr 2026 22:37:49 -0700 Subject: [PATCH 060/129] feat(typeagent-shell): add IntelliSense completion to chat input Adds inline ghost text + dropdown completion menu to the VS Code extension's chat textarea, mirroring the Electron shell behavior. - New TextareaPartialCompletion adapter (webview) using a mirror div for ghost text and the shared LocalSearchMenuUI dropdown. - Per-session CompletionController in AgentServerBridge proxies getCommandCompletion over WS; pcUpdate/pcAccept/pcDismiss/pcHide/ pcDispose messages flow webview -> bridge, pcState flows back. - Adds agent-dispatcher and @typeagent/completion-ui as deps. - esbuild webview config gains a CSS text loader so completion-ui styles can be injected via a