diff --git a/ts/packages/chat-ui/src/chatPanel.ts b/ts/packages/chat-ui/src/chatPanel.ts index 5afb8b9654..4980125dc4 100644 --- a/ts/packages/chat-ui/src/chatPanel.ts +++ b/ts/packages/chat-ui/src/chatPanel.ts @@ -1417,7 +1417,10 @@ export class ChatPanel { this.speechState === "always-on" || this.speechState === "wake-word"; this.micButton.classList.toggle("listening", listening); - this.micButton.disabled = this.speechState === "disabled"; + this.micButton.disabled = + this.speechState === "disabled" || + this.speechState === "preparing" || + this.speechState === "finalizing"; // Filled mic glyph; the `listening` class drives the pulse styling. this.micButton.innerHTML = ``; } @@ -1428,7 +1431,16 @@ export class ChatPanel { this.speechState === "listening" || this.speechState === "always-on" || this.speechState === "wake-word"; - if (listening) { + if ( + this.speechState === "preparing" || + this.speechState === "finalizing" + ) { + this.voiceBanner.textContent = + this.speechState === "preparing" + ? "Preparing speech recognition..." + : "Finishing transcription..."; + this.voiceBanner.style.display = ""; + } else if (listening) { this.voiceBanner.textContent = this.speechState === "wake-word" ? "Waiting for wake word…" diff --git a/ts/packages/chat-ui/src/providers.ts b/ts/packages/chat-ui/src/providers.ts index 78f159541d..806eac1269 100644 --- a/ts/packages/chat-ui/src/providers.ts +++ b/ts/packages/chat-ui/src/providers.ts @@ -17,6 +17,8 @@ export type SpeechState = | "idle" | "listening" | "disabled" + | "preparing" + | "finalizing" | "always-on" | "wake-word"; diff --git a/ts/packages/chat-ui/test/speechInput.spec.ts b/ts/packages/chat-ui/test/speechInput.spec.ts new file mode 100644 index 0000000000..84c4b7fceb --- /dev/null +++ b/ts/packages/chat-ui/test/speechInput.spec.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { afterEach, expect, it, jest } from "@jest/globals"; +import { ChatPanel } from "../src/chatPanel.js"; +import type { SpeechInputProvider, SpeechState } from "../src/providers.js"; + +let panel: ChatPanel | undefined; +afterEach(() => { + panel?.dispose(); + document.body.innerHTML = ""; +}); + +it("shows actual capture/finalizing state and replaces streaming textbox hypotheses without submitting", () => { + let stateChanged: (state: SpeechState) => void = () => {}; + let result: (text: string, final: boolean) => void = () => {}; + const provider: SpeechInputProvider = { + getState: () => "idle", + start: () => {}, + stop: () => {}, + setContinuous: () => {}, + onStateChange: (callback) => { + stateChanged = callback; + }, + onResult: (callback) => { + result = callback; + }, + }; + const root = document.createElement("div"); + document.body.appendChild(root); + const onSend = jest.fn(); + panel = new ChatPanel(root, { + platformAdapter: { handleLinkClick() {} }, + speechProvider: provider, + onSend, + }); + const mic = root.querySelector(".chat-mic-button")!; + const banner = root.querySelector(".chat-voice-banner")!; + const input = root.querySelector("#phraseDiv")!; + + stateChanged("preparing"); + expect(mic.disabled).toBe(true); + expect(mic.classList.contains("listening")).toBe(false); + expect(banner.textContent).toBe("Preparing speech recognition..."); + stateChanged("listening"); + expect(mic.disabled).toBe(false); + expect(mic.classList.contains("listening")).toBe(true); + result("turn", false); + expect(input.textContent).toBe("turn"); + result("turn on the lights", false); + expect(input.textContent).toBe("turn on the lights"); + stateChanged("finalizing"); + expect(mic.classList.contains("listening")).toBe(false); + expect(banner.textContent).toBe("Finishing transcription..."); + result("Turn on the lights.", true); + stateChanged("idle"); + expect(input.textContent).toBe("Turn on the lights."); + expect(mic.disabled).toBe(false); + expect(banner.style.display).toBe("none"); + expect(onSend).not.toHaveBeenCalled(); +}); diff --git a/ts/packages/shell/README.md b/ts/packages/shell/README.md index ac48f9c2cf..636c3c6dc3 100644 --- a/ts/packages/shell/README.md +++ b/ts/packages/shell/README.md @@ -105,7 +105,7 @@ To read the chat view from another device, such as a phone on a trusted home net ### Azure Speech to Text service (Optional) -Currently, TypeAgent Shell optionally supports voice input via Azure Speech Services or [Local Whisper Service](../../../python/stt/whisperService/) in addition to keyboard input. +Voice input supports Azure (default), Python [Whisper](../../../python/stt/whisperService/), and Windows x64 Nemotron. Select with `@shell speechProvider azure|whisper|nemotron`; Nemotron downloads on first selection. To set up Azure [Speech to Text service](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/index-speech-to-text), the following variables in `config.local.yaml` (under `speech`) or the legacy `.env` are needed: diff --git a/ts/packages/shell/electron-builder.config.js b/ts/packages/shell/electron-builder.config.js index b1bf8ba6e5..079862b453 100644 --- a/ts/packages/shell/electron-builder.config.js +++ b/ts/packages/shell/electron-builder.config.js @@ -95,6 +95,7 @@ export default { // Don't need to install npmRebuild: false, + asarUnpack: ["node_modules/**/foundry-local-sdk/**/*"], artifactName: name + "-${version}-${platform}-${arch}.${ext}", win: { signtoolOptions: { diff --git a/ts/packages/shell/electron.vite.config.mts b/ts/packages/shell/electron.vite.config.mts index ae6f028fa7..4d0bb38757 100644 --- a/ts/packages/shell/electron.vite.config.mts +++ b/ts/packages/shell/electron.vite.config.mts @@ -35,6 +35,15 @@ export default defineConfig({ ], build: { sourcemap: true, + rollupOptions: { + input: { + index: resolve(__dirname, "src/main/index.ts"), + localAsrWorker: resolve( + __dirname, + "src/main/localAsrWorker.ts", + ), + }, + }, }, }, preload: { diff --git a/ts/packages/shell/package.json b/ts/packages/shell/package.json index d91a87dced..7e2c50408b 100644 --- a/ts/packages/shell/package.json +++ b/ts/packages/shell/package.json @@ -49,7 +49,8 @@ "start:package:linux": "./dist/linux-unpacked/typeagentshell", "start:package:macos": "open ./dist/mac-arm64/TypeAgent\\ Shell.app", "start:package:win32": ".\\dist\\win-unpacked\\typeagentshell.exe", - "test:local": "pnpm run jest-esm --testPathPattern=\".*[.]spec[.]js\"", + "test:asr": "tsx --test test/unit/localAsr.test.mts", + "test:local": "pnpm run test:asr && pnpm run jest-esm --testPathPattern=\".*[.]spec[.]js\"", "tsc:model": "tsc -b src test", "typecheck": "concurrently npm:typecheck:node npm:typecheck:web", "typecheck:node": "tsc -p tsconfig.node.json", @@ -82,6 +83,7 @@ "dompurify": "^3.4.17", "dotenv": "^16.3.1", "electron-updater": "^6.8.9", + "foundry-local-sdk": "2.1.0", "jose": "^5.9.6", "js-yaml": "^4.3.2", "markdown-it": "^14.3.2", diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 24b06cc5d3..1167018fb3 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -17,7 +17,7 @@ import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ testDir: "./test", /* Exclude Jest unit tests from Playwright test discovery */ - testIgnore: /\/partialCompletion\//, + testIgnore: /\/(partialCompletion|unit)\//, /* Run tests sequentially otherwise the client will complain about locked session file */ fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ diff --git a/ts/packages/shell/src/main/agent.ts b/ts/packages/shell/src/main/agent.ts index bf3b83242b..78e654703f 100644 --- a/ts/packages/shell/src/main/agent.ts +++ b/ts/packages/shell/src/main/agent.ts @@ -23,6 +23,10 @@ import { displayWarn, } from "@typeagent/agent-sdk/helpers/display"; import { getLocalWhisperCommandHandlers } from "./localWhisperCommandHandler.js"; +import { + getSpeechProviderCommandHandlers, + selectSpeechProvider, +} from "./speechProvider.js"; import { ShellWindow } from "./shellWindow.js"; import { getObjectProperty, @@ -388,7 +392,8 @@ const handlers: CommandHandlerTable = { }, }, topmost: new ShellSetTopMostCommandHandler(), - localWhisper: getLocalWhisperCommandHandlers(), + localWhisper: getLocalWhisperCommandHandlers(selectSpeechProvider), + speechProvider: getSpeechProviderCommandHandlers(), theme: getThemeCommandHandlers(), voice: getVoiceCommandHandlers(), update: updateHandlerTable, diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index 3c83fe3a05..918e7768b2 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -19,6 +19,7 @@ import { } from "./shellSettings.js"; import { readFileSync } from "node:fs"; import { closeLocalWhisper } from "./localWhisperCommandHandler.js"; +import { localAsrHost } from "./localAsrHost.js"; import { getInstanceDir } from "agent-dispatcher/helpers/data"; import { initializeInstance, @@ -465,6 +466,7 @@ function initializeQuit() { globalShortcut.unregisterAll(); closeLocalWhisper(); + await localAsrHost.dispose(); debugShellCleanup("Closing instance"); diff --git a/ts/packages/shell/src/main/localAsrHost.ts b/ts/packages/shell/src/main/localAsrHost.ts new file mode 100644 index 0000000000..1708d37b65 --- /dev/null +++ b/ts/packages/shell/src/main/localAsrHost.ts @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + app, + dialog, + utilityProcess, + type UtilityProcess, + type WebContents, +} from "electron"; +import { randomUUID } from "node:crypto"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type { + LocalAsrCommand, + LocalAsrEvent, + LocalAsrWorkerEvent, +} from "../preload/localAsrTypes.js"; + +class LocalAsrHost { + private worker?: UtilityProcess; + private stoppingWorker?: UtilityProcess; + private requestId = 0; + private pending = new Map< + number, + { resolve: () => void; reject: (error: Error) => void } + >(); + private owner?: { sender: WebContents; id: string; release: () => void }; + private progress?: (message: string, percent?: number) => void; + + private getWorker() { + if (this.worker) return this.worker; + const worker = utilityProcess.fork( + fileURLToPath(new URL("./localAsrWorker.js", import.meta.url)), + [], + { serviceName: "TypeAgent local speech", stdio: "pipe" }, + ); + worker.stderr?.on("data", (data: Buffer) => + console.error(`[local-speech] ${data}`), + ); + worker.on("message", (event: LocalAsrWorkerEvent) => { + if (event.type === "reply") { + const pending = this.pending.get(event.requestId); + this.pending.delete(event.requestId); + if (event.error) pending?.reject(new Error(event.error)); + else pending?.resolve(); + } else if (event.type === "progress") { + this.progress?.(event.message, event.percent); + } else if ( + this.owner?.id === event.sessionId && + !this.owner.sender.isDestroyed() + ) { + this.owner.sender.send("local-asr-event", event); + } + }); + worker.on("exit", (code) => { + if (this.worker !== worker) return; + const stopped = this.stoppingWorker === worker; + this.worker = undefined; + this.stoppingWorker = undefined; + const error = new Error( + stopped + ? "Local speech was stopped." + : `Local speech worker exited (code ${code}). Select Nemotron again to restart it.`, + ); + for (const pending of this.pending.values()) pending.reject(error); + this.pending.clear(); + const owner = this.owner; + this.releaseOwner(); + if (owner && !owner.sender.isDestroyed()) { + owner.sender.send("local-asr-event", { + type: "error", + sessionId: owner.id, + message: error.message, + } satisfies LocalAsrEvent); + } + if (!stopped) console.error(error); + }); + this.worker = worker; + return worker; + } + + private request(command: LocalAsrCommand) { + const worker = this.getWorker(); + const requestId = ++this.requestId; + return new Promise((resolve, reject) => { + this.pending.set(requestId, { resolve, reject }); + try { + worker.postMessage({ ...command, requestId }); + } catch (error) { + this.pending.delete(requestId); + reject(error); + } + }); + } + + async prepare( + allowDownload: boolean, + progress?: (message: string, percent?: number) => void, + ) { + this.progress = progress; + try { + await this.request({ + type: "prepare", + cacheDir: path.join(app.getPath("userData"), "local-speech"), + allowDownload, + }); + } finally { + this.progress = undefined; + } + } + + cancelSetup() { + return this.request({ type: "cancelSetup" }); + } + + async start(sender: WebContents) { + if (this.owner) throw new Error("Local speech is already recording."); + await this.prepare(false); + if (sender.isDestroyed()) + throw new Error("The speech window was closed."); + if (this.owner) throw new Error("Local speech is already recording."); + const id = randomUUID(); + const cancel = () => { + void this.end(sender, id, true).catch((error) => + console.error("Failed to cancel local speech", error), + ); + }; + sender.once("destroyed", cancel); + sender.once("did-start-navigation", cancel); + this.owner = { + sender, + id, + release: () => { + sender.removeListener("destroyed", cancel); + sender.removeListener("did-start-navigation", cancel); + }, + }; + try { + await this.request({ type: "start", sessionId: id }); + return id; + } catch (error) { + this.releaseOwner(); + throw error; + } + } + + async append(sender: WebContents, id: string, pcm: Uint8Array) { + this.checkOwner(sender, id); + await this.request({ type: "append", sessionId: id, pcm }); + } + + async end(sender: WebContents, id: string, cancel: boolean) { + this.checkOwner(sender, id); + try { + await this.request({ + type: cancel ? "cancel" : "finish", + sessionId: id, + }); + } finally { + if (this.owner?.id === id) this.releaseOwner(); + } + } + + private checkOwner(sender: WebContents, id: string) { + if (this.owner?.sender !== sender || this.owner.id !== id) { + throw new Error( + "The speech session does not belong to this window.", + ); + } + } + + private releaseOwner() { + this.owner?.release(); + this.owner = undefined; + } + + isRecording() { + return this.owner !== undefined; + } + + async dispose() { + const worker = this.worker; + this.releaseOwner(); + if (worker) { + this.stoppingWorker = worker; + await new Promise((resolve) => { + worker.once("exit", () => resolve()); + worker.kill(); + }); + } + } + + reportError(message: string) { + console.error("[local-speech]", message); + dialog.showErrorBox("Speech recognition", message); + } +} + +export const localAsrHost = new LocalAsrHost(); diff --git a/ts/packages/shell/src/main/localAsrTranscript.ts b/ts/packages/shell/src/main/localAsrTranscript.ts new file mode 100644 index 0000000000..801b6511f7 --- /dev/null +++ b/ts/packages/shell/src/main/localAsrTranscript.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export class LocalAsrTranscript { + private committed: string[] = []; + private current = ""; + + update(text: string, kind: "none" | "partial" | "final"): string { + // Nemotron's non-segmented SDK stream uses "none" for token deltas. + this.current = kind === "none" ? this.current + text : text; + const hypothesis = [...this.committed, this.current] + .filter(Boolean) + .join(" ") + .trim(); + if (kind === "final") { + if (this.current) this.committed.push(this.current.trim()); + this.current = ""; + } + return hypothesis; + } +} diff --git a/ts/packages/shell/src/main/localAsrWorker.ts b/ts/packages/shell/src/main/localAsrWorker.ts new file mode 100644 index 0000000000..f9ae137dc4 --- /dev/null +++ b/ts/packages/shell/src/main/localAsrWorker.ts @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + AudioSession, + FoundryLocalManager, + Item, + ItemQueue, + Request, + type IModel, +} from "foundry-local-sdk"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import type { + LocalAsrRequest, + LocalAsrWorkerEvent, +} from "../preload/localAsrTypes.js"; +import { LocalAsrTranscript } from "./localAsrTranscript.js"; + +const modelAlias = "nemotron-3.5-asr-streaming-0.6b"; +const port = process.parentPort; +if (!port) + throw new Error("Local ASR must run in an isolated utility process."); +const emit = (event: LocalAsrWorkerEvent) => port.postMessage(event); +let manager: FoundryLocalManager | undefined; +let model: IModel | undefined; +let setup: AbortController | undefined; + +type Recognition = { + id: string; + queue: ItemQueue; + session: AudioSession; + abort: AbortController; + done: Promise; + error?: Error; + finished: boolean; +}; +let active: Recognition | undefined; + +async function prepare(cacheDir: string, allowDownload: boolean) { + if (setup) throw new Error("Local speech setup is already in progress."); + const abort = new AbortController(); + setup = abort; + try { + emit({ + type: "progress", + message: "Loading the local speech runtime...", + }); + if (!manager) { + const root = path + .dirname( + path.dirname( + fileURLToPath(import.meta.resolve("foundry-local-sdk")), + ), + ) + .replace(/\.asar([\\/])/, ".asar.unpacked$1"); + manager = FoundryLocalManager.create({ + appName: "typeagent-shell", + appDataDir: cacheDir, + modelCacheDir: path.join(cacheDir, "models"), + logsDir: path.join(cacheDir, "logs"), + disableNonessentialTelemetry: true, + libraryPath: path.join( + root, + "prebuilds", + `${process.platform}-${process.arch}`, + ), + additionalSettings: { AzureCatalogFilter: "'',test" }, + }); + } + model ??= await manager.catalog.getModel(modelAlias); + if (!model.isCached) { + if (!allowDownload) { + throw new Error( + "Nemotron is not cached. Run @shell speechProvider nemotron to download it.", + ); + } + emit({ type: "progress", message: `Downloading ${modelAlias}...` }); + await model.download((percent) => { + emit({ + type: "progress", + message: `Downloading ${modelAlias}...`, + percent, + }); + }, abort.signal); + } + abort.signal.throwIfAborted(); + emit({ + type: "progress", + message: "Loading the Nemotron speech model...", + }); + if (!(await model.isLoaded())) await model.load(); + abort.signal.throwIfAborted(); + emit({ type: "progress", message: "Nemotron speech is ready." }); + } finally { + setup = undefined; + } +} + +function start(id: string) { + if (active) throw new Error("A local speech session is already active."); + if (!model) throw new Error("The Nemotron speech model is not ready."); + const session = new AudioSession(model); + const queue = new ItemQueue(); + const request = new Request() + .addItem(Item.audioDescriptor("pcm", 16000, 1)) + .addItem(queue); + const recognition: Recognition = { + id, + session, + queue, + abort: new AbortController(), + done: Promise.resolve(), + finished: false, + }; + active = recognition; + recognition.done = transcribe(recognition, request); +} + +async function transcribe(recognition: Recognition, request: Request) { + try { + const transcript = new LocalAsrTranscript(); + const stream = recognition.session.processStreamingRequest(request, { + signal: recognition.abort.signal, + }); + const [, response] = await Promise.all([ + (async () => { + for await (const item of stream) { + if ( + item.type === "speechSegment" && + !recognition.abort.signal.aborted + ) { + emit({ + type: "result", + sessionId: recognition.id, + final: false, + text: transcript.update(item.text, item.kind), + }); + } + } + })(), + stream.response, + ]); + if (!recognition.abort.signal.aborted) { + if (response.finishReason === "error") + throw new Error("Nemotron transcription failed."); + const aggregate = response.output.find( + (item): item is Extract => + item.type === "speechResult", + ); + const text = + aggregate?.text ?? + response.output + .flatMap((item) => + item.type === "text" ? [item.text] : [], + ) + .join(""); + emit({ + type: "result", + sessionId: recognition.id, + text: text.trim(), + final: true, + }); + } + } catch (error) { + if (!recognition.abort.signal.aborted) { + recognition.error = + error instanceof Error ? error : new Error(String(error)); + emit({ + type: "error", + sessionId: recognition.id, + message: recognition.error.message, + }); + } + } finally { + recognition.queue.dispose(); + recognition.session.dispose(); + if (active === recognition) active = undefined; + } +} + +async function handle(request: LocalAsrRequest) { + switch (request.type) { + case "prepare": + return prepare(request.cacheDir, request.allowDownload); + case "cancelSetup": + setup?.abort(); + return; + case "start": + return start(request.sessionId); + case "append": { + const recognition = active; + if ( + !recognition || + recognition.id !== request.sessionId || + recognition.finished + ) { + throw new Error("The local speech session is not recording."); + } + if ( + !(request.pcm instanceof Uint8Array) || + request.pcm.byteLength === 0 || + request.pcm.byteLength > 3200 || + request.pcm.byteLength % 2 !== 0 + ) { + throw new Error( + "Expected at most 100 ms of mono 16 kHz PCM16 audio.", + ); + } + // More than two seconds queued means this device cannot sustain live capture. + if (recognition.queue.size >= 20) { + throw new Error( + "Local speech cannot keep up with capture on this device. Stop and try a supported faster device.", + ); + } + recognition.queue.push(Item.bytes(request.pcm)); + return; + } + case "finish": + case "cancel": { + const recognition = active; + if (!recognition || recognition.id !== request.sessionId) return; + recognition.finished = true; + if (request.type === "cancel") recognition.abort.abort(); + else recognition.queue.markFinished(); + await recognition.done; + if (recognition.error) throw recognition.error; + } + } +} + +port.on("message", ({ data }: { data: LocalAsrRequest }) => { + void handle(data).then( + () => emit({ type: "reply", requestId: data.requestId }), + (error: unknown) => + emit({ + type: "reply", + requestId: data.requestId, + error: error instanceof Error ? error.message : String(error), + }), + ); +}); diff --git a/ts/packages/shell/src/main/localWhisperCommandHandler.ts b/ts/packages/shell/src/main/localWhisperCommandHandler.ts index 7fa3f76750..def951d017 100644 --- a/ts/packages/shell/src/main/localWhisperCommandHandler.ts +++ b/ts/packages/shell/src/main/localWhisperCommandHandler.ts @@ -3,14 +3,9 @@ import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import { CommandHandlerTable } from "@typeagent/agent-sdk/helpers/command"; -import { - displayError, - displayResult, - displayStatus, - displaySuccess, - displayWarn, -} from "@typeagent/agent-sdk/helpers/display"; +import { displayWarn } from "@typeagent/agent-sdk/helpers/display"; import { ActionContext } from "@typeagent/agent-sdk"; +import type { ShellContext } from "./agent.js"; import registerDebug from "debug"; import { fileURLToPath } from "url"; @@ -81,7 +76,7 @@ export function isLocalWhisperEnabled() { return localWhisperProcess !== undefined; } -async function ensureLocalWhisper() { +export async function ensureLocalWhisper() { if (localWhisperProcess === undefined) { localWhisperProcess = await createLocalWhisperHost(); localWhisperProcess.on("exit", function (code, signal) { @@ -113,45 +108,33 @@ export function closeLocalWhisper() { return false; } -export function getLocalWhisperCommandHandlers(): CommandHandlerTable { +export function getLocalWhisperCommandHandlers( + selectProvider: ( + context: ActionContext, + provider: "azure" | "whisper", + ) => Promise, +): CommandHandlerTable { return { description: "Configure Local Whisper", commands: { off: { description: "Turn off Local Whisper integration", - run: async (context: ActionContext) => { - // This is process wide - if (closeLocalWhisper()) { - displayResult("Local Whisper disabled.", context); - } else { - displayWarn( - "Local Whisper is already disabled.", - context, - ); - } + run: async (context: ActionContext) => { + displayWarn( + "Deprecated: use @shell speechProvider azure.", + context, + ); + await selectProvider(context, "azure"); }, }, on: { description: "Turn on Local Whisper integration.", - run: async (context: ActionContext) => { - if (isLocalWhisperEnabled()) { - displayWarn( - "Local Whisper is already enabled.", - context, - ); - return; - } - - try { - displayStatus("Starting local whisper...", context); - await ensureLocalWhisper(); - displaySuccess("Local whisper started.", context); - } catch (e: any) { - displayError( - `Error starting local whisper: ${e.message}`, - context, - ); - } + run: async (context: ActionContext) => { + displayWarn( + "Deprecated: use @shell speechProvider whisper.", + context, + ); + await selectProvider(context, "whisper"); }, }, }, diff --git a/ts/packages/shell/src/main/speech.ts b/ts/packages/shell/src/main/speech.ts index 8e5fae182a..0e68e277b7 100644 --- a/ts/packages/shell/src/main/speech.ts +++ b/ts/packages/shell/src/main/speech.ts @@ -4,7 +4,9 @@ import { dialog, globalShortcut, ipcMain } from "electron"; import { CONFIG_LOCAL_FILE } from "@typeagent/config"; import { AzureSpeech } from "./azureSpeech.js"; -import { isLocalWhisperEnabled } from "./localWhisperCommandHandler.js"; +import { ensureLocalWhisper } from "./localWhisperCommandHandler.js"; +import { getSpeechProvider } from "./speechProvider.js"; +import { localAsrHost } from "./localAsrHost.js"; import registerDebug from "debug"; import { @@ -65,9 +67,7 @@ export async function triggerRecognitionOnce() { return; } const chatView = shellWindow.chatView; - const speechToken = await getSpeechToken(false); - const useLocalWhisper = isLocalWhisperEnabled(); - chatView.webContents.send("listen-event", speechToken, useLocalWhisper); + chatView.webContents.send("listen-event", undefined, false); } export async function toggleWakeWordRecognition() { @@ -76,9 +76,7 @@ export async function toggleWakeWordRecognition() { return; } const chatView = shellWindow.chatView; - const speechToken = await getSpeechToken(false); - const useLocalWhisper = isLocalWhisperEnabled(); - chatView.webContents.send("wake-listen", speechToken, useLocalWhisper); + chatView.webContents.send("wake-listen"); } export async function toggleContinuousRecognition() { @@ -87,9 +85,7 @@ export async function toggleContinuousRecognition() { return; } const chatView = shellWindow.chatView; - const speechToken = await getSpeechToken(false); - const useLocalWhisper = isLocalWhisperEnabled(); - chatView.webContents.send("listen-always", speechToken, useLocalWhisper); + chatView.webContents.send("listen-always"); } export function initializeSpeech() { @@ -119,7 +115,40 @@ export function initializeSpeech() { if (getShellWindowForChatViewIpcEvent(event) === undefined) { return undefined; } - return isLocalWhisperEnabled(); + const selected = getSpeechProvider() === "whisper"; + if (selected) await ensureLocalWhisper(); + return selected; + }); + + ipcMain.handle("get-speech-provider", async (event) => { + if (!getShellWindowForChatViewIpcEvent(event)) + throw new Error("Untrusted speech caller."); + const provider = getSpeechProvider(); + return provider; + }); + ipcMain.handle("local-asr-start", (event) => { + if (!getShellWindowForChatViewIpcEvent(event)) + throw new Error("Untrusted speech caller."); + if (getSpeechProvider() !== "nemotron") + throw new Error("Nemotron is not the selected speech provider."); + return localAsrHost.start(event.sender); + }); + ipcMain.handle("local-asr-append", (event, id: string, pcm: Uint8Array) => { + if (!getShellWindowForChatViewIpcEvent(event)) + throw new Error("Untrusted speech caller."); + return localAsrHost.append(event.sender, id, pcm); + }); + ipcMain.handle("local-asr-end", (event, id: string, cancel: boolean) => { + if (!getShellWindowForChatViewIpcEvent(event)) + throw new Error("Untrusted speech caller."); + return localAsrHost.end(event.sender, id, cancel); + }); + ipcMain.handle("speech-error", (event, message: string) => { + if (!getShellWindowForChatViewIpcEvent(event)) + throw new Error("Untrusted speech caller."); + if (typeof message !== "string") + throw new Error("Invalid speech error."); + localAsrHost.reportError(message); }); ipcMain.handle( diff --git a/ts/packages/shell/src/main/speechProvider.ts b/ts/packages/shell/src/main/speechProvider.ts new file mode 100644 index 0000000000..5840c2bcc4 --- /dev/null +++ b/ts/packages/shell/src/main/speechProvider.ts @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { dialog } from "electron"; +import type { ActionContext } from "@typeagent/agent-sdk"; +import type { CommandHandlerTable } from "@typeagent/agent-sdk/helpers/command"; +import { + displayStatus, + displaySuccess, +} from "@typeagent/agent-sdk/helpers/display"; +import type { ShellContext } from "./agent.js"; +import { getShellWindow } from "./instance.js"; +import { + closeLocalWhisper, + ensureLocalWhisper, +} from "./localWhisperCommandHandler.js"; +import { localAsrHost } from "./localAsrHost.js"; +import type { SpeechProvider } from "../preload/localAsrTypes.js"; + +let switching = false; + +export function getSpeechProvider(): SpeechProvider { + const provider = + getShellWindow()?.getUserSettings().speechProvider ?? "azure"; + if ( + provider !== "azure" && + provider !== "whisper" && + provider !== "nemotron" + ) { + throw new Error( + `Invalid speech provider '${provider}'. Use @shell speechProvider azure, whisper, or nemotron.`, + ); + } + return provider; +} + +export async function selectSpeechProvider( + context: ActionContext, + provider: SpeechProvider, +) { + if (switching) + throw new Error("A speech provider change is already in progress."); + if (localAsrHost.isRecording()) + throw new Error( + "Stop the active recording before switching speech providers.", + ); + const shellWindow = context.sessionContext.agentContext.shellWindow; + switching = true; + try { + if (provider === "nemotron") { + if (process.platform !== "win32" || process.arch !== "x64") { + throw new Error( + "The Nemotron prototype currently supports Windows x64 only. Azure and Whisper are unchanged.", + ); + } + const dialogClosed = new AbortController(); + let finished = false; + let canceled = false; + let cancelFailure: Error | undefined; + const cancelDialog = dialog + .showMessageBox({ + type: "info", + title: "Preparing local speech", + message: "Preparing Nemotron speech recognition", + detail: "Missing model files will be downloaded and cached on this device. Progress appears in chat. Cancel to keep the current speech provider.", + buttons: ["Cancel"], + signal: dialogClosed.signal, + }) + .then(async () => { + if (!finished) { + canceled = true; + try { + await localAsrHost.cancelSetup(); + } catch (error) { + cancelFailure = + error instanceof Error + ? error + : new Error(String(error)); + } + } + }); + try { + let lastProgress = ""; + await localAsrHost.prepare(true, (message, percent) => { + const text = + percent === undefined + ? message + : `${message} ${Math.floor(percent)}%`; + if (text !== lastProgress) { + displayStatus(text, context); + lastProgress = text; + } + }); + } finally { + finished = true; + dialogClosed.abort(); + await cancelDialog; + } + if (cancelFailure) throw cancelFailure; + if (canceled) + throw new Error( + "Nemotron setup canceled; the previous speech provider is unchanged.", + ); + } else if (provider === "whisper") { + displayStatus("Starting local Whisper...", context); + await ensureLocalWhisper(); + } + shellWindow.setUserSettingValue("speechProvider", provider); + shellWindow.chatView.webContents.send("speech-provider-changed"); + if (provider !== "whisper") closeLocalWhisper(); + if (provider !== "nemotron") await localAsrHost.dispose(); + displaySuccess(`Speech provider: ${provider}.`, context); + } finally { + switching = false; + } +} + +export function getSpeechProviderCommandHandlers(): CommandHandlerTable { + const select = (provider: SpeechProvider) => ({ + description: `Use ${provider} speech recognition`, + run: (context: ActionContext) => + selectSpeechProvider(context, provider), + }); + return { + description: + "Select the speech provider (downloads Nemotron on first use)", + commands: { + azure: select("azure"), + whisper: select("whisper"), + nemotron: select("nemotron"), + }, + }; +} diff --git a/ts/packages/shell/src/preload/chatView.ts b/ts/packages/shell/src/preload/chatView.ts index df9ace349f..5be26f36fc 100644 --- a/ts/packages/shell/src/preload/chatView.ts +++ b/ts/packages/shell/src/preload/chatView.ts @@ -11,6 +11,7 @@ import { import { QueueSnapshot } from "agent-dispatcher"; import type { ManageConversationPayload } from "@typeagent/agent-server-client/conversation"; import type { ConnectionStatus } from "@typeagent/chat-ui"; +import type { LocalAsrEvent } from "./localAsrTypes.js"; import { createChannelAdapter } from "@typeagent/agent-rpc/channel"; import { createDispatcherRpcClient, @@ -189,6 +190,27 @@ const api: ClientAPI = { getLocalWhisperStatus: () => { return ipcRenderer.invoke("get-localWhisper-status"); }, + getSpeechProvider: () => ipcRenderer.invoke("get-speech-provider"), + startLocalAsr: () => ipcRenderer.invoke("local-asr-start"), + appendLocalAsr: (id, pcm) => + ipcRenderer.invoke("local-asr-append", id, pcm), + endLocalAsr: (id, cancel) => + ipcRenderer.invoke("local-asr-end", id, cancel), + onLocalAsrEvent: (callback) => { + const listener = ( + _event: Electron.IpcRendererEvent, + data: LocalAsrEvent, + ) => callback(data); + ipcRenderer.on("local-asr-event", listener); + return () => ipcRenderer.removeListener("local-asr-event", listener); + }, + onSpeechProviderChanged: (callback) => { + const listener = () => callback(); + ipcRenderer.on("speech-provider-changed", listener); + return () => + ipcRenderer.removeListener("speech-provider-changed", listener); + }, + reportSpeechError: (message) => ipcRenderer.invoke("speech-error", message), openImageFile: () => { ipcRenderer.send("open-image-file"); }, diff --git a/ts/packages/shell/src/preload/electronTypes.ts b/ts/packages/shell/src/preload/electronTypes.ts index 1909a93e54..282d2fc9de 100644 --- a/ts/packages/shell/src/preload/electronTypes.ts +++ b/ts/packages/shell/src/preload/electronTypes.ts @@ -14,6 +14,7 @@ import type { ConnectionStatus } from "@typeagent/chat-ui"; import type { DisplayContent } from "@typeagent/agent-sdk"; export type { ShellUserSettings }; +import type { LocalAsrEvent, SpeechProvider } from "./localAsrTypes.js"; export type SpeechToken = { token: string; @@ -67,6 +68,13 @@ export interface ClientAPI { registerClient: (client: Client) => void; getSpeechToken: (silent: boolean) => Promise; getLocalWhisperStatus: () => Promise; + getSpeechProvider: () => Promise; + startLocalAsr: () => Promise; + appendLocalAsr: (sessionId: string, pcm: Uint8Array) => Promise; + endLocalAsr: (sessionId: string, cancel: boolean) => Promise; + onLocalAsrEvent: (callback: (event: LocalAsrEvent) => void) => () => void; + onSpeechProviderChanged: (callback: () => void) => () => void; + reportSpeechError: (message: string) => Promise; saveSettings: (settings: ShellUserSettings) => void; openImageFile: () => void; openFolder: (path: string) => void; diff --git a/ts/packages/shell/src/preload/localAsrTypes.ts b/ts/packages/shell/src/preload/localAsrTypes.ts new file mode 100644 index 0000000000..3245a4fb4b --- /dev/null +++ b/ts/packages/shell/src/preload/localAsrTypes.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type SpeechProvider = "azure" | "whisper" | "nemotron"; + +export type LocalAsrEvent = + | { type: "result"; sessionId: string; text: string; final: boolean } + | { type: "error"; sessionId: string; message: string }; + +export type LocalAsrCommand = + | { type: "prepare"; cacheDir: string; allowDownload: boolean } + | { type: "cancelSetup" } + | { type: "start"; sessionId: string } + | { type: "append"; sessionId: string; pcm: Uint8Array } + | { type: "finish"; sessionId: string } + | { type: "cancel"; sessionId: string }; + +export type LocalAsrRequest = LocalAsrCommand & { requestId: number }; + +export type LocalAsrWorkerEvent = + | LocalAsrEvent + | { type: "progress"; message: string; percent?: number } + | { type: "reply"; requestId: number; error?: string }; diff --git a/ts/packages/shell/src/preload/shellSettingsType.ts b/ts/packages/shell/src/preload/shellSettingsType.ts index 5266bfc937..fb2b6e673a 100644 --- a/ts/packages/shell/src/preload/shellSettingsType.ts +++ b/ts/packages/shell/src/preload/shellSettingsType.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import type { SpeechProvider } from "./localAsrTypes.js"; + export type TTSSettings = { provider?: string | undefined; voice?: string | undefined; @@ -19,6 +21,7 @@ export type UISettings = { wakeWord: string; }; export type ShellUserSettings = { + speechProvider: SpeechProvider; microphoneId: string | undefined; microphoneName: string | undefined; tts: boolean; @@ -39,6 +42,7 @@ export type ShellUserSettings = { }; export const defaultUserSettings: ShellUserSettings = { + speechProvider: "azure", microphoneId: undefined, microphoneName: undefined, notifyFilter: "error;warning;", diff --git a/ts/packages/shell/src/renderer/src/electronProviders.ts b/ts/packages/shell/src/renderer/src/electronProviders.ts index dc4506b886..f292e51dda 100644 --- a/ts/packages/shell/src/renderer/src/electronProviders.ts +++ b/ts/packages/shell/src/renderer/src/electronProviders.ts @@ -28,6 +28,7 @@ import { recognizeOnce, } from "./speech"; import { getTTS, TTS } from "./tts/tts"; +import { NemotronCapture } from "./nemotronCapture.js"; /** * Speech-to-text provider backed by the shell's Azure / local-Whisper @@ -41,6 +42,45 @@ export class ElectronSpeechProvider implements SpeechInputProvider { private continuousRecognizer?: ContinousSpeechRecognizer; private continuous = false; private waitForWakeWord = false; + private generation = 0; + private localCapture?: NemotronCapture; + private cancelRecognition?: () => void; + + constructor() { + const unsubscribe = getClientAPI().onSpeechProviderChanged(() => + this.cancel(), + ); + window.addEventListener( + "beforeunload", + () => { + unsubscribe(); + this.cancel(); + }, + { once: true }, + ); + } + + private cancel() { + this.generation++; + this.localCapture?.cancel(); + this.localCapture = undefined; + this.cancelRecognition?.(); + this.cancelRecognition = undefined; + this.continuousRecognizer?.stop(); + this.continuousRecognizer = undefined; + this.continuous = false; + this.setState("idle"); + } + + private reportError(error: unknown) { + this.cancel(); + const message = error instanceof Error ? error.message : String(error); + void getClientAPI() + .reportSpeechError(message) + .catch((failure: unknown) => + console.error("Unable to display speech error", failure), + ); + } public getState(): SpeechState { return this.state; @@ -61,42 +101,87 @@ export class ElectronSpeechProvider implements SpeechInputProvider { public start(): void { if (this.state === "listening") { - // Toggle off an in-progress single-shot recognition. - this.setState("idle"); + this.stop(); return; } - void this.startSingleShot(); + if (this.state === "preparing" || this.state === "finalizing") return; + const generation = this.generation + 1; + void this.startSingleShot().catch((error: unknown) => { + if (generation === this.generation) this.reportError(error); + }); } private async startSingleShot(): Promise { - const useLocalWhisper = await getClientAPI().getLocalWhisperStatus(); + const generation = ++this.generation; + this.setState("preparing"); + const provider = await getClientAPI().getSpeechProvider(); + if (generation !== this.generation) return; + if (provider === "nemotron") { + const capture = new NemotronCapture(getClientAPI(), { + result: (text, final) => { + if (generation !== this.generation) return; + this.resultCb?.(text, final); + if (final) { + this.localCapture = undefined; + this.setState("idle"); + } + }, + listening: () => { + if (generation === this.generation) + this.setState("listening"); + }, + finalizing: () => { + if (generation === this.generation) + this.setState("finalizing"); + }, + error: (error) => { + if (generation === this.generation) this.reportError(error); + }, + }); + this.localCapture = capture; + await capture.start(); + return; + } + const useLocalWhisper = provider === "whisper"; + if (useLocalWhisper) await getClientAPI().getLocalWhisperStatus(); const token: SpeechToken | undefined = needSpeechToken(useLocalWhisper) ? await getSpeechToken(false) : undefined; + if (generation !== this.generation) return; if (needSpeechToken(useLocalWhisper) && token === undefined) { this.setState("disabled"); return; } this.setState("listening"); - recognizeOnce( + this.cancelRecognition = recognizeOnce( token, - (text) => this.resultCb?.(text, false), (text) => { + if (generation === this.generation) + this.resultCb?.(text, false); + }, + (text) => { + if (generation !== this.generation) return; this.resultCb?.(text, true); if (!this.continuous) this.setState("idle"); }, - () => { - if (!this.continuous) this.setState("idle"); + (error) => { + if (generation === this.generation) this.reportError(error); }, useLocalWhisper, ); } public stop(): void { + if (this.localCapture) { + void this.localCapture + .finish() + .catch((error: unknown) => this.reportError(error)); + return; + } if (this.continuous) { this.setContinuous(false); } else { - this.setState("idle"); + this.cancel(); } } @@ -104,25 +189,52 @@ export class ElectronSpeechProvider implements SpeechInputProvider { this.continuous = on; this.waitForWakeWord = waitForWakeWord; if (on) { - void this.startContinuous(); + const generation = this.generation + 1; + void this.startContinuous().catch((error: unknown) => { + if (generation === this.generation) this.reportError(error); + }); } else { - this.continuousRecognizer?.stop(); - this.setState("idle"); + this.cancel(); } } private async startContinuous(): Promise { - const useLocalWhisper = await getClientAPI().getLocalWhisperStatus(); + const generation = ++this.generation; + this.cancelRecognition?.(); + this.cancelRecognition = undefined; + const provider = await getClientAPI().getSpeechProvider(); + if (generation !== this.generation) return; + if (provider === "nemotron") { + throw new Error( + "Nemotron currently supports single-shot input only. Use the mic button or Alt+M.", + ); + } + const useLocalWhisper = provider === "whisper"; + if (useLocalWhisper) await getClientAPI().getLocalWhisperStatus(); const token: SpeechToken | undefined = needSpeechToken(useLocalWhisper) ? await getSpeechToken(false) : undefined; + if (generation !== this.generation) return; + if (needSpeechToken(useLocalWhisper) && !token) { + this.continuous = false; + this.setState("disabled"); + return; + } if (this.continuousRecognizer === undefined) { this.continuousRecognizer = new ContinousSpeechRecognizer( useLocalWhisper, token, - (text) => this.resultCb?.(text, false), - (text) => this.resultCb?.(text, true), - () => {}, + (text) => { + if (generation === this.generation) + this.resultCb?.(text, false); + }, + (text) => { + if (generation === this.generation) + this.resultCb?.(text, true); + }, + (error) => { + if (generation === this.generation) this.reportError(error); + }, ); } this.continuousRecognizer.start(); diff --git a/ts/packages/shell/src/renderer/src/localWhisperClient.ts b/ts/packages/shell/src/renderer/src/localWhisperClient.ts index 29c1884f6f..69ba0f6153 100644 --- a/ts/packages/shell/src/renderer/src/localWhisperClient.ts +++ b/ts/packages/shell/src/renderer/src/localWhisperClient.ts @@ -16,6 +16,12 @@ export class WhisperRecognizer { | ((result: { text: string }) => void) | undefined; private audioChunks: Blob[] = []; + private stream?: MediaStream; + private disposed = false; + private animationFrame?: number; + private httpController?: AbortController; + private errorCallback: (message: string) => void = (message) => + console.error(message); constructor() {} @@ -23,6 +29,11 @@ export class WhisperRecognizer { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, }); + this.stream = stream; + if (this.disposed) { + stream.getTracks().forEach((track) => track.stop()); + return; + } this.audioContext = new AudioContext(); this.mediaStreamSource = @@ -33,6 +44,7 @@ export class WhisperRecognizer { this.mediaRecorder = new MediaRecorder(stream); this.mediaRecorder.ondataavailable = async (e) => { + if (this.disposed) return; this.audioChunks.push(e.data); if ( @@ -42,7 +54,23 @@ export class WhisperRecognizer { const blob = new Blob(this.audioChunks, { type: "audio/mpeg-3", }); - const text = await this.getTranscription(blob); + this.httpController?.abort(); + const controller = new AbortController(); + this.httpController = controller; + let text: string; + try { + text = await this.getTranscription(blob, controller.signal); + } catch (error) { + if (!controller.signal.aborted) { + this.errorCallback( + error instanceof Error + ? error.message + : String(error), + ); + } + return; + } + if (controller.signal.aborted || this.disposed) return; if (this.mediaRecorder!.state === "inactive") { if (this.recognizedCallback && !this.recognizedFired) { this.recognizedCallback({ text }); @@ -58,6 +86,7 @@ export class WhisperRecognizer { } startRecording() { + if (this.disposed || this.mediaRecorder?.state !== "inactive") return; this.audioChunks = []; this.recognizedFired = false; this.mediaRecorder?.start(200); @@ -65,7 +94,34 @@ export class WhisperRecognizer { } stopRecording() { - this.mediaRecorder?.stop(); + if (this.mediaRecorder?.state === "recording") + this.mediaRecorder.stop(); + } + + onError(callback: (message: string) => void) { + this.errorCallback = callback; + } + + dispose() { + if (this.disposed) return; + this.disposed = true; + this.httpController?.abort(); + this.stopRecording(); + if (this.animationFrame !== undefined) + cancelAnimationFrame(this.animationFrame); + if (this.silenceTimeout !== null) clearTimeout(this.silenceTimeout); + this.stream?.getTracks().forEach((track) => track.stop()); + this.mediaStreamSource?.disconnect(); + this.analyser?.disconnect(); + if (this.audioContext?.state !== "closed") { + void this.audioContext + ?.close() + .catch((error: unknown) => + this.errorCallback( + error instanceof Error ? error.message : String(error), + ), + ); + } } onRecognizing(callback: (result: { text: string }) => void) { @@ -76,21 +132,30 @@ export class WhisperRecognizer { this.recognizedCallback = callback; } - private async getTranscription(blob: Blob): Promise { + private async getTranscription( + blob: Blob, + signal: AbortSignal, + ): Promise { this.awaitingResult = true; const formData = new FormData(); formData.append("file", blob); const response = await fetch("http://localhost:8001/transcribe", { method: "POST", body: formData, + signal, }); + if (!response.ok) + throw new Error(`Whisper returned HTTP ${response.status}.`); const responseBody = await response.json(); this.awaitingResult = false; + if (typeof responseBody.transcription !== "string") + throw new Error("Whisper returned an invalid transcript."); return responseBody.transcription; } private checkForSilence() { + if (this.disposed || this.mediaRecorder?.state !== "recording") return; const bufferLength = this.analyser!.fftSize; const amplitudeArray = new Float32Array(bufferLength || 0); const silenceDuration = 1000; @@ -101,12 +166,9 @@ export class WhisperRecognizer { const volume = this.getVolume(amplitudeArray); if (volume < silenceThreshold) { - if ( - !this.silenceTimeout && - this.mediaRecorder!.state !== "inactive" - ) { + if (!this.silenceTimeout) { this.silenceTimeout = setTimeout(() => { - this.mediaRecorder?.stop(); + this.stopRecording(); this.silenceTimeout = null; }, silenceDuration); } @@ -117,7 +179,9 @@ export class WhisperRecognizer { } } - requestAnimationFrame(() => this.checkForSilence()); + this.animationFrame = requestAnimationFrame(() => + this.checkForSilence(), + ); } private getVolume(amplitudeArray: Float32Array) { diff --git a/ts/packages/shell/src/renderer/src/main.ts b/ts/packages/shell/src/renderer/src/main.ts index 23ef209b8e..a8e80a3d95 100644 --- a/ts/packages/shell/src/renderer/src/main.ts +++ b/ts/packages/shell/src/renderer/src/main.ts @@ -309,7 +309,10 @@ document.addEventListener("DOMContentLoaded", async function () { window.__clientIO__ = client.clientIO; // get the user's name to show in the chat view - const token: SpeechToken | undefined = await getSpeechToken(); + const token: SpeechToken | undefined = + (await clientAPI.getSpeechProvider()) === "azure" + ? await getSpeechToken() + : undefined; const actualToken = token?.token.substring(token?.token.indexOf("#")); if (actualToken) { const decoded = jose.decodeJwt(actualToken); diff --git a/ts/packages/shell/src/renderer/src/nemotronCapture.ts b/ts/packages/shell/src/renderer/src/nemotronCapture.ts new file mode 100644 index 0000000000..a25bca71ea --- /dev/null +++ b/ts/packages/shell/src/renderer/src/nemotronCapture.ts @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import type { ClientAPI } from "../../preload/electronTypes.js"; + +type AudioFrame = + | { type: "audio"; pcm: Uint8Array; rms: number } + | { type: "flushed" }; + +export class NemotronCapture { + private sessionId?: string; + private stream?: MediaStream; + private context?: AudioContext; + private node?: AudioWorkletNode; + private unsubscribe?: () => void; + private pending = Promise.resolve(); + private queued = 0; + private quietSamples = 0; + private ending = false; + private terminal = false; + private flushed?: () => void; + + constructor( + private readonly api: Pick< + ClientAPI, + | "startLocalAsr" + | "appendLocalAsr" + | "endLocalAsr" + | "onLocalAsrEvent" + >, + private readonly callbacks: { + result: (text: string, final: boolean) => void; + listening: () => void; + finalizing: () => void; + error: (error: Error) => void; + }, + ) {} + + async start() { + this.unsubscribe = this.api.onLocalAsrEvent((event) => { + if (event.sessionId !== this.sessionId || this.terminal) return; + if (event.type === "error") this.fail(new Error(event.message)); + else { + this.callbacks.result(event.text, event.final); + if (event.final) { + this.terminal = true; + this.cleanup(); + } + } + }); + this.sessionId = await this.api.startLocalAsr(); + if (this.terminal) { + await this.api.endLocalAsr(this.sessionId, true); + return; + } + const selector = document.getElementById("microphoneSources"); + const device = + selector instanceof HTMLSelectElement ? selector.value : undefined; + const stream = await navigator.mediaDevices.getUserMedia({ + audio: + device && device !== "" + ? { deviceId: { exact: device } } + : true, + }); + this.stream = stream; + if (this.terminal) { + stream.getTracks().forEach((track) => track.stop()); + return; + } + // Chromium performs capture-rate conversion into this context; reject rather + // than mislabel audio if a host does not honor the requested processing rate. + const context = new AudioContext({ sampleRate: 16000 }); + this.context = context; + if (context.sampleRate !== 16000) + throw new Error( + "This device does not support a 16 kHz audio context.", + ); + await context.audioWorklet.addModule( + new URL("./pcmCaptureWorklet.js", import.meta.url).href, + ); + if (this.terminal) return; + const node = new AudioWorkletNode(context, "typeagent-pcm-capture"); + this.node = node; + node.port.onmessage = ({ data }: MessageEvent) => { + if (data.type === "flushed") { + this.flushed?.(); + return; + } + if (this.terminal) return; + if (++this.queued > 20) { + this.fail( + new Error( + "Local speech cannot keep up with microphone capture.", + ), + ); + return; + } + this.pending = this.pending + .then(async () => { + if (!this.terminal && this.sessionId) { + await this.api.appendLocalAsr(this.sessionId, data.pcm); + } + }) + .catch((error: unknown) => { + this.fail( + error instanceof Error + ? error + : new Error(String(error)), + ); + }) + .finally(() => this.queued--); + this.quietSamples = + data.rms < 0.0031623 + ? this.quietSamples + data.pcm.byteLength / 2 + : 0; + if (this.quietSamples >= 16000) { + void this.finish().catch((error: unknown) => + this.fail( + error instanceof Error + ? error + : new Error(String(error)), + ), + ); + } + }; + node.onprocessorerror = () => + this.fail(new Error("Microphone audio processing failed.")); + context.createMediaStreamSource(stream).connect(node); + node.connect(context.destination); + await context.resume(); + if (!this.terminal && !this.ending) this.callbacks.listening(); + } + + async finish() { + if (this.terminal || this.ending) return; + if (!this.node) { + this.cancel(); + return; + } + this.ending = true; + this.callbacks.finalizing(); + await new Promise((resolve) => { + this.flushed = resolve; + this.node!.port.postMessage("finish"); + }); + this.closeCapture(); + await this.pending; + if (!this.terminal && this.sessionId) { + await this.api.endLocalAsr(this.sessionId, false); + } + } + + cancel() { + if (this.terminal) return; + this.terminal = true; + this.cleanup(); + if (this.sessionId) { + void this.api + .endLocalAsr(this.sessionId, true) + .catch((error: unknown) => + this.callbacks.error( + error instanceof Error + ? error + : new Error(String(error)), + ), + ); + } + } + + private fail(error: Error) { + if (this.terminal) return; + this.cancel(); + this.callbacks.error(error); + } + + private closeCapture() { + this.stream?.getTracks().forEach((track) => track.stop()); + this.stream = undefined; + this.node?.disconnect(); + this.node?.port.close(); + this.node = undefined; + if (this.context && this.context.state !== "closed") { + void this.context + .close() + .catch((error: unknown) => + this.callbacks.error( + error instanceof Error + ? error + : new Error(String(error)), + ), + ); + } + this.context = undefined; + } + + private cleanup() { + this.flushed?.(); + this.flushed = undefined; + this.closeCapture(); + this.unsubscribe?.(); + this.unsubscribe = undefined; + } +} diff --git a/ts/packages/shell/src/renderer/src/pcmCaptureWorklet.js b/ts/packages/shell/src/renderer/src/pcmCaptureWorklet.js new file mode 100644 index 0000000000..33c0d8f21e --- /dev/null +++ b/ts/packages/shell/src/renderer/src/pcmCaptureWorklet.js @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +class PcmCaptureProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.samples = new Float32Array(1600); + this.offset = 0; + this.recording = true; + this.port.onmessage = ({ data }) => { + if (data === "finish") { + this.recording = false; + this.flush(); + this.port.postMessage({ type: "flushed" }); + } + }; + } + + flush() { + if (!this.offset) return; + const pcm = new Uint8Array(this.offset * 2); + const view = new DataView(pcm.buffer); + let energy = 0; + for (let i = 0; i < this.offset; i++) { + const sample = Math.max(-1, Math.min(1, this.samples[i])); + energy += sample * sample; + view.setInt16( + i * 2, + Math.round(sample * (sample < 0 ? 32768 : 32767)), + true, + ); + } + this.port.postMessage( + { + type: "audio", + pcm, + rms: Math.sqrt(energy / this.offset), + }, + [pcm.buffer], + ); + this.offset = 0; + } + + process(inputs) { + const channels = inputs[0]; + if (!this.recording || !channels?.length) return true; + for (let i = 0; i < channels[0].length; i++) { + let mono = 0; + for (const channel of channels) mono += channel[i]; + this.samples[this.offset++] = mono / channels.length; + if (this.offset === this.samples.length) this.flush(); + } + return true; + } +} + +registerProcessor("typeagent-pcm-capture", PcmCaptureProcessor); diff --git a/ts/packages/shell/src/renderer/src/speech.ts b/ts/packages/shell/src/renderer/src/speech.ts index f323a329d1..759f5e6104 100644 --- a/ts/packages/shell/src/renderer/src/speech.ts +++ b/ts/packages/shell/src/renderer/src/speech.ts @@ -112,9 +112,10 @@ export function recognizeOnce( onRecognized: (text: string) => void, onError: (error: string) => void, useLocalWhisper?: boolean, -) { +): () => void { if (useLocalWhisper) { const reco = new WhisperRecognizer(); + reco.onError(onError); reco.onRecognizing((data) => { onRecognizing(data.text); @@ -127,35 +128,58 @@ export function recognizeOnce( data.text, ); onRecognizedResult(result, onRecognized, onError); + reco.dispose(); }); - reco.initialize().then(() => { - reco.startRecording(); - }); + reco.initialize() + .then(() => { + reco.startRecording(); + }) + .catch((error: unknown) => { + reco.dispose(); + onError(error instanceof Error ? error.message : String(error)); + }); + return () => reco.dispose(); } else { + let cancel = () => {}; try { const audioConfig = getAudioConfig(); const speechConfig = getSpeechConfig(token); + if (!speechConfig) { + audioConfig.close(); + throw new Error("Azure Speech authorization is unavailable."); + } const reco = new speechSDK.SpeechRecognizer( - speechConfig!, + speechConfig, audioConfig, ); + let canceled = false; + cancel = () => { + if (canceled) return; + canceled = true; + reco.close(); + audioConfig.close(); + }; reco.recognizing = (_s, e) => { - onRecognizing(e.result.text); + if (!canceled) onRecognizing(e.result.text); }; reco.recognizeOnceAsync( (result) => { - onRecognizedResult(result, onRecognized, onError); + if (!canceled) + onRecognizedResult(result, onRecognized, onError); + cancel(); }, (err) => { debugError(err); - onError(`[ERROR: ${err}]`); + if (!canceled) onError(`[ERROR: ${err}]`); + cancel(); }, ); } catch (e: any) { debugError("Speech recognition setup failed", e); onError(`[ERROR: ${e.message}]`); } + return cancel; } } @@ -169,6 +193,7 @@ export class ContinousSpeechRecognizer { private onRecognized: (text: string) => void; private onError: (error: string) => void; private started: boolean = false; + private ready = Promise.resolve(); constructor( useWhisper: boolean = false, @@ -183,6 +208,7 @@ export class ContinousSpeechRecognizer { if (useWhisper) { this.whisperRecognizer = new WhisperRecognizer(); + this.whisperRecognizer.onError(onError); this.whisperRecognizer.onRecognizing((data) => { this.onRecognizing(data.text); @@ -197,9 +223,7 @@ export class ContinousSpeechRecognizer { onRecognizedResult(result, this.onRecognized, this.onError); }); - this.whisperRecognizer.initialize().then(() => { - this.whisperRecognizer?.startRecording(); - }); + this.ready = this.whisperRecognizer.initialize(); } else { this.token = token; this.audioConfig = getAudioConfig(); @@ -227,7 +251,15 @@ export class ContinousSpeechRecognizer { this.started = true; if (this.whisperRecognizer) { - this.whisperRecognizer.startRecording(); + void this.ready + .then(() => { + if (this.started) this.whisperRecognizer?.startRecording(); + }) + .catch((error: unknown) => + this.onError( + error instanceof Error ? error.message : String(error), + ), + ); } else { console.log("[voice] startContinuousRecognitionAsync called"); this.reco!.startContinuousRecognitionAsync( @@ -235,16 +267,29 @@ export class ContinousSpeechRecognizer { console.log("[voice] continuous recognition started OK"); }, (error) => { - console.log( - "[voice] continuous recognition start ERROR:", - error, - ); + this.onError(error); }, ); } } public stop() { - if (!this.started) return; + this.started = false; + this.whisperRecognizer?.dispose(); + const reco = this.reco; + this.reco = undefined; + if (reco) { + reco.stopContinuousRecognitionAsync( + () => { + reco.close(); + this.audioConfig?.close(); + }, + (error) => { + reco.close(); + this.audioConfig?.close(); + this.onError(error); + }, + ); + } } } diff --git a/ts/packages/shell/src/renderer/src/webSocketAPI.ts b/ts/packages/shell/src/renderer/src/webSocketAPI.ts index 10c4b87c3a..2e838c198c 100644 --- a/ts/packages/shell/src/renderer/src/webSocketAPI.ts +++ b/ts/packages/shell/src/renderer/src/webSocketAPI.ts @@ -47,6 +47,28 @@ export const webapi: ClientAPI = { // local whisper not supported on mobile return false; }, + getSpeechProvider: async () => "azure", + startLocalAsr: async () => { + throw new Error( + "Nemotron speech is currently available only in Electron Shell.", + ); + }, + appendLocalAsr: async () => { + throw new Error( + "Nemotron speech is currently available only in Electron Shell.", + ); + }, + endLocalAsr: async () => { + throw new Error( + "Nemotron speech is currently available only in Electron Shell.", + ); + }, + onLocalAsrEvent: () => () => {}, + onSpeechProviderChanged: () => () => {}, + reportSpeechError: async (message) => { + console.error("Speech recognition:", message); + window.alert(message); + }, openImageFile: () => { // not supported on mobile }, diff --git a/ts/packages/shell/src/tsconfig.json b/ts/packages/shell/src/tsconfig.json index c8f4a2d1c3..6f2c00daf5 100644 --- a/ts/packages/shell/src/tsconfig.json +++ b/ts/packages/shell/src/tsconfig.json @@ -6,5 +6,9 @@ "rootDir": ".", "moduleResolution": "node16" }, - "include": ["preload/electronTypes.ts", "preload/shellSettingsType.ts"] + "include": [ + "preload/electronTypes.ts", + "preload/shellSettingsType.ts", + "preload/localAsrTypes.ts" + ] } diff --git a/ts/packages/shell/test/speechProvider.spec.ts b/ts/packages/shell/test/speechProvider.spec.ts new file mode 100644 index 0000000000..0c407a1179 --- /dev/null +++ b/ts/packages/shell/test/speechProvider.spec.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { expect, test } from "@playwright/test"; +import { + runTestCallback, + sendUserRequestAndWaitForResponse, +} from "./testHelper"; + +test("the shell speech provider command selects Azure through chat", async () => { + await runTestCallback(async (page) => { + const response = await sendUserRequestAndWaitForResponse( + "@shell speechProvider azure", + page, + ); + expect(response).toContain("Speech provider: azure."); + expect( + await page.evaluate(() => globalThis.api.getSpeechProvider()), + ).toBe("azure"); + }); +}); diff --git a/ts/packages/shell/test/unit/localAsr.test.mts b/ts/packages/shell/test/unit/localAsr.test.mts new file mode 100644 index 0000000000..d6b1d17e01 --- /dev/null +++ b/ts/packages/shell/test/unit/localAsr.test.mts @@ -0,0 +1,301 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { test, type TestContext } from "node:test"; +import vm from "node:vm"; +import { LocalAsrTranscript } from "../../src/main/localAsrTranscript.js"; +import { NemotronCapture } from "../../src/renderer/src/nemotronCapture.js"; +import type { LocalAsrEvent } from "../../src/preload/localAsrTypes.js"; + +test("partial hypotheses replace the current segment and finals commit it once", () => { + const transcript = new LocalAsrTranscript(); + assert.equal(transcript.update("turn", "partial"), "turn"); + assert.equal(transcript.update("turn on", "partial"), "turn on"); + assert.equal( + transcript.update("turn on the lights", "final"), + "turn on the lights", + ); + assert.equal(transcript.update("in", "partial"), "turn on the lights in"); + assert.equal( + transcript.update("in the kitchen", "final"), + "turn on the lights in the kitchen", + ); +}); + +test("SDK none-kind token fragments form a cumulative textbox hypothesis", () => { + const transcript = new LocalAsrTranscript(); + const fragments = [ + " T", + "ur", + "n", + " ", + "on", + " the", + " ", + "l", + "ight", + "s", + ]; + const hypotheses = fragments.map((text) => transcript.update(text, "none")); + assert.equal(hypotheses[2], "Turn"); + assert.equal(hypotheses.at(-1), "Turn on the lights"); +}); + +test("the worklet downmixes, clips PCM16 little endian, and flushes trailing samples", () => { + const messages: { type: string; pcm?: Uint8Array; rms?: number }[] = []; + let processor: + | { + process: (inputs: Float32Array[][]) => boolean; + port: { onmessage: (event: { data: string }) => void }; + } + | undefined; + vm.runInNewContext( + readFileSync( + new URL( + "../../src/renderer/src/pcmCaptureWorklet.js", + import.meta.url, + ), + "utf8", + ), + { + AudioWorkletProcessor: class { + port = { + postMessage: (data: (typeof messages)[number]) => + messages.push(data), + }; + }, + registerProcessor: ( + _name: string, + ctor: new () => NonNullable, + ) => { + processor = new ctor(); + }, + }, + ); + assert.ok(processor); + processor.process([ + [new Float32Array(1600).fill(2), new Float32Array(1600).fill(-2)], + ]); + assert.equal(messages[0].pcm?.length, 3200); + assert.equal(messages[0].rms, 0); + assert.ok(messages[0].pcm?.every((byte) => byte === 0)); + processor.process([[new Float32Array(1600).fill(2)]]); + assert.deepEqual(Array.from(messages[1].pcm!.slice(0, 2)), [255, 127]); + processor.process([[new Float32Array(64).fill(-2)]]); + assert.equal(messages.length, 2); + processor.port.onmessage({ data: "finish" }); + assert.equal(messages[2].pcm?.byteLength, 128); + assert.deepEqual(Array.from(messages[2].pcm!.slice(0, 2)), [0, 128]); + assert.equal(messages[3].type, "flushed"); + processor.process([[new Float32Array(1600).fill(1)]]); + assert.equal(messages.length, 4); +}); + +function captureFixture(t: TestContext) { + const calls: string[] = []; + const results: [string, boolean][] = []; + const errors: Error[] = []; + let listener: ((event: LocalAsrEvent) => void) | undefined; + let node: FakeNode | undefined; + let stopped = 0; + let closed = 0; + let mediaReady: (() => void) | undefined; + let resolveMedia: (() => void) | undefined; + const mediaRequested = new Promise( + (resolve) => (mediaReady = resolve), + ); + let blockMedia = false; + const stream = { getTracks: () => [{ stop: () => stopped++ }] }; + class FakeContext { + sampleRate = 16000; + state = "suspended"; + audioWorklet = { addModule: async () => {} }; + createMediaStreamSource() { + return { connect() {} }; + } + async resume() { + this.state = "running"; + } + async close() { + this.state = "closed"; + closed++; + } + } + class FakeNode { + constructor() { + node = this; + } + port = { + onmessage: (event: { + data: { type: string; pcm?: Uint8Array; rms?: number }; + }) => {}, + postMessage: (message: string) => { + assert.equal(message, "finish"); + calls.push("flush"); + this.send({ type: "audio", pcm: new Uint8Array(64), rms: 1 }); + this.send({ type: "flushed" }); + }, + close() {}, + }; + send(data: { type: string; pcm?: Uint8Array; rms?: number }) { + this.port.onmessage({ data }); + } + connect() {} + disconnect() {} + } + const globals: Record = { + document: { getElementById: () => null }, + HTMLSelectElement: class {}, + AudioContext: FakeContext, + AudioWorkletNode: FakeNode, + navigator: { + mediaDevices: { + getUserMedia: async () => { + mediaReady?.(); + if (blockMedia) + await new Promise( + (resolve) => (resolveMedia = resolve), + ); + return stream; + }, + }, + }, + }; + for (const [name, value] of Object.entries(globals)) { + const previous = Object.getOwnPropertyDescriptor(globalThis, name); + Object.defineProperty(globalThis, name, { configurable: true, value }); + t.after(() => { + if (previous) Object.defineProperty(globalThis, name, previous); + else Reflect.deleteProperty(globalThis, name); + }); + } + const capture = new NemotronCapture( + { + onLocalAsrEvent: (callback) => { + listener = callback; + return () => calls.push("unsubscribe"); + }, + startLocalAsr: async () => "owned-session", + appendLocalAsr: async (_id, pcm) => { + calls.push(`append:${pcm.length}`); + }, + endLocalAsr: async (_id, cancel) => { + calls.push(cancel ? "cancel" : "finish"); + if (!cancel) + listener?.({ + type: "result", + sessionId: "owned-session", + text: "Canonical final.", + final: true, + }); + }, + }, + { + result: (text, final) => results.push([text, final]), + listening: () => calls.push("listening"), + finalizing: () => calls.push("finalizing"), + error: (error) => errors.push(error), + }, + ); + t.after(() => capture.cancel()); + return { + capture, + calls, + results, + errors, + node: () => node!, + emit: (event: LocalAsrEvent) => listener?.(event), + stopped: () => stopped, + closed: () => closed, + blockMedia: () => (blockMedia = true), + mediaRequested, + resolveMedia: () => resolveMedia?.(), + }; +} + +test("capture streams live text, drains trailing audio before finish, and releases microphone", async (t) => { + const f = captureFixture(t); + await f.capture.start(); + assert.deepEqual(f.calls, ["listening"]); + f.emit({ + type: "result", + sessionId: "other-window", + text: "Wrong owner", + final: false, + }); + f.emit({ + type: "result", + sessionId: "owned-session", + text: "Partial", + final: false, + }); + assert.deepEqual(f.results, [["Partial", false]]); + f.node().send({ type: "audio", pcm: new Uint8Array(3200), rms: 1 }); + await f.capture.finish(); + assert.deepEqual(f.calls, [ + "listening", + "finalizing", + "flush", + "append:3200", + "append:64", + "finish", + "unsubscribe", + ]); + assert.deepEqual(f.results, [ + ["Partial", false], + ["Canonical final.", true], + ]); + assert.equal(f.stopped(), 1); + assert.equal(f.closed(), 1); + assert.deepEqual(f.errors, []); +}); + +test("cancel during microphone permission acquisition releases the late stream", async (t) => { + const f = captureFixture(t); + f.blockMedia(); + const start = f.capture.start(); + await f.mediaRequested; + f.capture.cancel(); + f.resolveMedia(); + await start; + f.emit({ + type: "result", + sessionId: "owned-session", + text: "Late result", + final: true, + }); + assert.deepEqual(f.results, []); + assert.equal(f.stopped(), 1); + assert.ok(f.calls.includes("cancel")); + assert.ok(!f.calls.includes("listening")); +}); + +test("inference errors cancel the session and surface without cloud fallback", async (t) => { + const f = captureFixture(t); + await f.capture.start(); + f.emit({ + type: "error", + sessionId: "owned-session", + message: "Native inference failed", + }); + assert.equal(f.errors[0]?.message, "Native inference failed"); + assert.deepEqual(f.results, []); + assert.equal(f.stopped(), 1); + assert.ok(f.calls.includes("cancel")); +}); + +test("capture permits twenty pending frames and explicitly fails the twenty-first", async (t) => { + const f = captureFixture(t); + await f.capture.start(); + for (let index = 0; index < 20; index++) { + f.node().send({ type: "audio", pcm: new Uint8Array(3200), rms: 1 }); + } + assert.deepEqual(f.errors, []); + f.node().send({ type: "audio", pcm: new Uint8Array(3200), rms: 1 }); + assert.equal(f.errors.length, 1); + assert.match(f.errors[0].message, /cannot keep up/); + assert.equal(f.stopped(), 1); + assert.ok(f.calls.includes("cancel")); +}); diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 807599a27b..bcb6537d51 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: devDependencies: '@fluidframework/build-tools': specifier: ^0.57.0 - version: 0.57.0(@types/node@22.15.18)(supports-color@8.1.1) + version: 0.57.0(@types/node@22.20.5)(supports-color@8.1.1) '@types/node': specifier: ^22.20.5 version: 22.20.5 @@ -178,7 +178,7 @@ importers: version: 8.18.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@26.4.1)(ts-node@10.9.2(@types/node@26.4.1)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1044,7 +1044,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1134,7 +1134,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1171,7 +1171,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1610,7 +1610,7 @@ importers: version: 1.0.13 '@huggingface/transformers': specifier: 4.3.1 - version: 4.3.1(@types/node@22.15.18) + version: 4.3.1(@types/node@22.20.5) '@modelcontextprotocol/server-filesystem': specifier: 2026.1.14 version: 2026.1.14(zod@4.4.3) @@ -1655,7 +1655,7 @@ importers: version: 2.11.2(puppeteer-extra@3.3.6(puppeteer-core@24.37.5)(puppeteer@24.37.5(typescript@5.9.3))(supports-color@8.1.1))(supports-color@8.1.1) sharp: specifier: 0.35.5 - version: 0.35.5(@types/node@22.15.18) + version: 0.35.5(@types/node@22.20.5) packages/agentServer/client: dependencies: @@ -2161,7 +2161,7 @@ importers: devDependencies: '@fluidframework/build-tools': specifier: ^0.57.0 - version: 0.57.0(@types/node@22.15.18)(supports-color@8.1.1) + version: 0.57.0(@types/node@22.20.5)(supports-color@8.1.1) '@typeagent/action-grammar-compiler': specifier: workspace:* version: link:../../actionGrammarCompiler @@ -2218,7 +2218,7 @@ importers: version: 8.18.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0(supports-color@8.1.1) @@ -2227,10 +2227,10 @@ importers: version: 6.0.1 ts-jest: specifier: ^29.3.2 - version: 29.3.3(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.3.3(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)))(typescript@5.4.5) vite: specifier: ^6.4.4 - version: 6.4.4(@types/node@22.15.18)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.3) + version: 6.4.4(@types/node@22.20.5)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.3) packages/agents/browserControlRpc: dependencies: @@ -2334,7 +2334,7 @@ importers: version: 0.2.3(esbuild@0.28.1) '@fluidframework/build-tools': specifier: ^0.57.0 - version: 0.57.0(@types/node@22.20.1) + version: 0.57.0(@types/node@22.20.5)(supports-color@8.1.1) '@typeagent/action-grammar-compiler': specifier: workspace:* version: link:../../actionGrammarCompiler @@ -2388,10 +2388,10 @@ importers: version: 7.0.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-chrome: specifier: ^0.8.0 - version: 0.8.0(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5))) + version: 0.8.0(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5))) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0(supports-color@8.1.1) @@ -2406,13 +2406,13 @@ importers: version: 6.0.1 ts-jest: specifier: ^29.3.2 - version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)))(typescript@5.4.5) typescript: specifier: ~5.4.5 version: 5.4.5 vite: specifier: ^6.4.4 - version: 6.4.4(@types/node@22.20.1)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0) + version: 6.4.4(@types/node@22.20.5)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0) packages/agents/calendar: dependencies: @@ -2912,7 +2912,7 @@ importers: dependencies: '@milkdown/core': specifier: ^7.3.6 - version: 7.13.1 + version: 7.13.1(supports-color@8.1.1) '@milkdown/crepe': specifier: ^7.3.6 version: 7.13.1(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(typescript@5.4.5) @@ -3412,7 +3412,7 @@ importers: version: 9.1.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -3642,7 +3642,7 @@ importers: version: 9.1.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4267,7 +4267,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4520,7 +4520,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4529,7 +4529,7 @@ importers: version: 6.0.1 ts-jest: specifier: ^29.4.9 - version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)))(typescript@5.4.5) typescript: specifier: ~5.4.5 version: 5.4.5 @@ -4805,7 +4805,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4851,7 +4851,7 @@ importers: version: 0.28.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -4915,7 +4915,7 @@ importers: version: 0.28.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) postject: specifier: 1.0.0-alpha.6 version: 1.0.0-alpha.6 @@ -5402,7 +5402,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -5467,7 +5467,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -5492,7 +5492,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) rimraf: specifier: ^5.0.5 version: 5.0.10 @@ -6211,7 +6211,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -6374,6 +6374,9 @@ importers: electron-updater: specifier: ^6.8.9 version: 6.8.9 + foundry-local-sdk: + specifier: 2.1.0 + version: 2.1.0 jose: specifier: ^5.9.6 version: 5.10.0 @@ -6449,7 +6452,7 @@ importers: version: 4.0.1(vite@6.4.4(@types/node@22.20.1)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0)) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) less: specifier: ^4.2.0 version: 4.3.0 @@ -6526,7 +6529,7 @@ importers: version: 8.18.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -7295,7 +7298,7 @@ importers: version: 22.20.5 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -9593,37 +9596,37 @@ packages: tslib: '2' '@koromix/koffi-android-arm64@3.2.1': - resolution: {integrity: sha1-Lc55jTZp0re1bpwCqKaidKvWx8Y=} + resolution: {integrity: sha512-1pJQ4jnZlUJduK9u9DC5CGy3aOgDUPvIXpNb6syV3+Dh5Q/ugezAIGCqvY+w+1mgXsve0pd0NVvJRjdZNHQ6MA==} cpu: [arm64] os: [android] '@koromix/koffi-android-x64@3.2.1': - resolution: {integrity: sha1-TQr8uvZgQjEKwbOs8sWMb/6XN0M=} + resolution: {integrity: sha512-HH40xGh3gVQifjOBnhwT2tECC0lL1lYe+nxHvWNSzxDIyQNcVPXg38ta7vuONRFpD+uIrw7fqGYLzbZIagkVcg==} cpu: [x64] os: [android] '@koromix/koffi-darwin-arm64@3.2.1': - resolution: {integrity: sha1-IP1RZvBGbrn1lplQbLeK8cUgfQk=} + resolution: {integrity: sha512-Vj4h+xcjc5+Cn0DhPHjgRX4omKAv96Kehtcd+1YgYuY2W7FvQn9vS+3SmzVwhC5Qmg9bIwUZObYQ8T/4hBqQqA==} cpu: [arm64] os: [darwin] '@koromix/koffi-darwin-x64@3.2.1': - resolution: {integrity: sha1-A+TMtBoFV58aKcKB5IJiEzF89so=} + resolution: {integrity: sha512-gFCWxNBTZIvxo1p+PURWfsy2Ctj5FGnVVs1f03lTLhBvmxEto70pdIiFztdFLDFkAJ1pmtQmruRKapeK+E8YPA==} cpu: [x64] os: [darwin] '@koromix/koffi-freebsd-arm64@3.2.1': - resolution: {integrity: sha1-0+Tp8iGTKchvN7woVoXriVpSgb0=} + resolution: {integrity: sha512-qj+f1s2e6vULaUG1cdlTcCXmunCq2t+rjxku1+esaMIqVnHpOwj0QzPuInG0AFdXjwBNQhyVR/HpDj8daEwwsQ==} cpu: [arm64] os: [freebsd] '@koromix/koffi-freebsd-ia32@3.2.1': - resolution: {integrity: sha1-hphgO4/QJ8Hs9tw6rX303Bbv398=} + resolution: {integrity: sha512-6olHb1Qfgai0jjs6ddlDDD0ZfsCxy7SPi8rMRpuYQWH0qhgtyQu82hw5b1p7z+TJ0zZP3ZeQQ6l+U/MlM1ICHQ==} cpu: [ia32] os: [freebsd] '@koromix/koffi-freebsd-x64@3.2.1': - resolution: {integrity: sha1-hROyr0V+Jtl+gNNPY8vwBjhzBYY=} + resolution: {integrity: sha512-Dikhw1ySYNVMkmeFvFVjnU5Wdk6mffNoOjJxm9bTG96vg7OlemylxqdEven47R1YJ3yzNVJn/MlQ207ORWfi2w==} cpu: [x64] os: [freebsd] @@ -9633,22 +9636,22 @@ packages: os: [linux] '@koromix/koffi-linux-arm@3.2.1': - resolution: {integrity: sha1-U8kNnV+aiTlB862CivSHs3lejuE=} + resolution: {integrity: sha512-OfwUwZylidq95wQKp6ClInULrfB2giu7dqM6Rhe0zAe6lES5I2SXNw15T9+GnRHk3/9hKT2XZ37OZLaKSyWNLA==} cpu: [arm] os: [linux] '@koromix/koffi-linux-ia32@3.2.1': - resolution: {integrity: sha1-TvuhuJjnO2GNIqngyfPp0R192rQ=} + resolution: {integrity: sha512-rxj6UYjU1qd98gxNQOSCdLpc5cPRi5Giq9rNd3jnGuSNIyMkwa6Dxw4cUjmhIBCYESMJtmNt5NWnJp5u9wTfYQ==} cpu: [ia32] os: [linux] '@koromix/koffi-linux-loong64@3.2.1': - resolution: {integrity: sha1-Kg2NCvf2bpXiVxEeX1TsIQjYs7g=} + resolution: {integrity: sha512-aHhnHzkPRmT/IHDlGvESJ/Bs32m8N6UE6Ab6kMeJzgk74IN8af2m/81/wZJtybR1M2UxCV4NmlVNUYQQvSAO3Q==} cpu: [loong64] os: [linux] '@koromix/koffi-linux-riscv64@3.2.1': - resolution: {integrity: sha1-1VWiDsNvfBkM8W/vEFWkKtkYPU0=} + resolution: {integrity: sha512-qtQBsjbm3LiirLJvajWmKkNb7ARk7fvJVXdftJ7NtAnF3Xw8EbDvrtvmvtNI1yLPlYcBmlzCCD71hwhWYk0SIA==} cpu: [riscv64] os: [linux] @@ -9658,12 +9661,12 @@ packages: os: [linux] '@koromix/koffi-openbsd-ia32@3.2.1': - resolution: {integrity: sha1-QzemOyiO/9HqwdOHbXlK4rfuPRg=} + resolution: {integrity: sha512-mmY8fY8LQ/CB52+h3yrMYmVyoxzW3x08S0yI6VNfHWdfU6yJtZkKCbhjmQCYMrWbYKC4gMvwZwCywIGPAkLyeA==} cpu: [ia32] os: [openbsd] '@koromix/koffi-openbsd-x64@3.2.1': - resolution: {integrity: sha1-xZycfkCVP/t+MvjY+8sZ/zio7wM=} + resolution: {integrity: sha512-k4ig6aAPbFSRATOIIOfdf/KtlOGH4SVls6L9fy0QnTxRJYvY2oSltTsQtBDANgEQldlq8Kl5WnpRa1VSibP4Lw==} cpu: [x64] os: [openbsd] @@ -9673,7 +9676,7 @@ packages: os: [win32] '@koromix/koffi-win32-ia32@3.2.1': - resolution: {integrity: sha1-n60joxMqlAcm//MI5bKXVRepoys=} + resolution: {integrity: sha512-Z50EM6TAZ7CFyMmyX6thv8eNpJchqe9eenhibSIy2Eq/FQYF76gU2VK/LEoaF46L8hfC7TpTp9b10MvReHEyFA==} cpu: [ia32] os: [win32] @@ -11549,18 +11552,9 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha1-2kxjJHk6ed77emLLo5R+xa3QDVk=} - '@types/node@20.19.40': - resolution: {integrity: sha1-gKSnI24ngXY2d3g2zu24ia322i8=} - '@types/node@20.19.43': resolution: {integrity: sha1-/Oz1gLpCoNtVz0BMNyyXlzw3bJc=} - '@types/node@22.15.18': - resolution: {integrity: sha1-L4JA9+ky9XHC1F9VW6C2w/enWWM=} - - '@types/node@22.19.19': - resolution: {integrity: sha1-MSS/Jt7VQWi3aBODIf75m0IMYRI=} - '@types/node@22.20.1': resolution: {integrity: sha1-hOfN9jzaogwTSqMXzMkBqiHhbw4=} @@ -12491,7 +12485,7 @@ packages: resolution: {integrity: sha1-4KJ3XUQl52dMZLeLJCr09J2msDU=} better-sqlite3@12.11.1: - resolution: {integrity: sha1-BnhG76v3ZxlX/IqejfO+OcbMC4Q=} + resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x} bidi-js@1.0.3: @@ -14313,6 +14307,10 @@ packages: resolution: {integrity: sha1-ImmTZCiq1MFcfr6XeahL8LKoGBE=} engines: {node: '>= 0.6'} + foundry-local-sdk@2.1.0: + resolution: {integrity: sha512-sUgF+O7uHGVj3bInufe9nmj2SDry06dKGfVLpg9Y1TPPk/hxZEDAk2QPtU13eGZ0k7SIP3OOZFTkmvbtT+TPrw==} + engines: {node: '>=20'} + fresh@0.5.2: resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} engines: {node: '>= 0.6'} @@ -16471,6 +16469,10 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha1-Grpmk7DyVSWKBJ1iEykykyKq1Vg=} + node-addon-api@8.9.2: + resolution: {integrity: sha1-23rJShP/2bVebLBFhL1e+OHXSxg=} + engines: {node: ^18 || ^20 || >= 21} + node-api-version@0.2.1: resolution: {integrity: sha1-GbrVT21lYoy+5OYHoyXkSIrOLek=} @@ -21677,29 +21679,13 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@fluid-tools/version-tools@0.57.0(@types/node@22.15.18)(supports-color@8.1.1)': - dependencies: - '@oclif/core': 4.13.3 - '@oclif/plugin-autocomplete': 3.2.24(supports-color@8.1.1) - '@oclif/plugin-commands': 4.1.21 - '@oclif/plugin-help': 6.2.58 - '@oclif/plugin-not-found': 3.2.65(@types/node@22.15.18) - semver: 7.8.5 - table: 6.9.0 - transitivePeerDependencies: - - '@types/node' - - bufferutil - - react-devtools-core - - supports-color - - utf-8-validate - - '@fluid-tools/version-tools@0.57.0(@types/node@22.20.1)': + '@fluid-tools/version-tools@0.57.0(@types/node@22.20.5)(supports-color@8.1.1)': dependencies: '@oclif/core': 4.13.3 '@oclif/plugin-autocomplete': 3.2.24(supports-color@8.1.1) '@oclif/plugin-commands': 4.1.21 '@oclif/plugin-help': 6.2.58 - '@oclif/plugin-not-found': 3.2.65(@types/node@22.20.1) + '@oclif/plugin-not-found': 3.2.65(@types/node@22.20.5) semver: 7.8.5 table: 6.9.0 transitivePeerDependencies: @@ -21709,43 +21695,9 @@ snapshots: - supports-color - utf-8-validate - '@fluidframework/build-tools@0.57.0(@types/node@22.15.18)(supports-color@8.1.1)': + '@fluidframework/build-tools@0.57.0(@types/node@22.20.5)(supports-color@8.1.1)': dependencies: - '@fluid-tools/version-tools': 0.57.0(@types/node@22.15.18)(supports-color@8.1.1) - '@manypkg/get-packages': 2.2.2 - async: 3.2.6 - cosmiconfig: 8.3.6(typescript@5.4.5) - date-fns: 2.30.0 - debug: 4.4.3(supports-color@8.1.1) - detect-indent: 6.1.0 - find-up: 7.0.0 - fs-extra: 11.3.6 - glob: 7.2.3 - globby: 11.1.0 - ignore: 5.3.2 - json5: 2.2.3 - lodash.isequal: 4.5.0 - multimatch: 5.0.0 - picocolors: 1.1.1 - picomatch: 2.3.2 - picospinner: 2.0.0 - rimraf: 4.4.1 - semver: 7.8.4 - sort-package-json: 1.57.0 - ts-deepmerge: 7.0.2 - type-fest: 2.19.0 - typescript: 5.4.5 - yaml: 2.9.0 - transitivePeerDependencies: - - '@types/node' - - bufferutil - - react-devtools-core - - supports-color - - utf-8-validate - - '@fluidframework/build-tools@0.57.0(@types/node@22.20.1)': - dependencies: - '@fluid-tools/version-tools': 0.57.0(@types/node@22.20.1) + '@fluid-tools/version-tools': 0.57.0(@types/node@22.20.5)(supports-color@8.1.1) '@manypkg/get-packages': 2.2.2 async: 3.2.6 cosmiconfig: 8.3.6(typescript@5.4.5) @@ -21830,13 +21782,13 @@ snapshots: '@huggingface/tokenizers@0.2.0': {} - '@huggingface/transformers@4.3.1(@types/node@22.15.18)': + '@huggingface/transformers@4.3.1(@types/node@22.20.5)': dependencies: '@huggingface/jinja': 0.5.10 '@huggingface/tokenizers': 0.2.0 onnxruntime-node: 1.30.0 onnxruntime-web: 1.31.0-dev.20260914-8d85527a0 - sharp: 0.35.5(@types/node@22.15.18) + sharp: 0.35.5(@types/node@22.20.5) transitivePeerDependencies: - '@types/node' @@ -21981,44 +21933,27 @@ snapshots: '@img/sharp-win32-x64@0.35.5': optional: true - '@inquirer/checkbox@4.2.1(@types/node@22.15.18)': + '@inquirer/checkbox@4.2.1(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) + '@inquirer/core': 10.1.15(@types/node@22.20.5) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.15.18) + '@inquirer/type': 3.0.8(@types/node@22.20.5) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/checkbox@4.2.1(@types/node@22.20.1)': + '@inquirer/confirm@5.1.15(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.20.1) - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 - '@inquirer/confirm@5.1.15(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/confirm@5.1.15(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/core@10.1.15(@types/node@22.15.18)': + '@inquirer/core@10.1.15(@types/node@22.20.5)': dependencies: '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.15.18) + '@inquirer/type': 3.0.8(@types/node@22.20.5) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -22026,204 +21961,100 @@ snapshots: wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/core@10.1.15(@types/node@22.20.1)': + '@inquirer/editor@4.2.17(@types/node@22.20.5)': dependencies: - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.20.1) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/external-editor': 1.0.1(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 - '@inquirer/editor@4.2.17(@types/node@22.15.18)': + '@inquirer/expand@4.0.17(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/external-editor': 1.0.1(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/editor@4.2.17(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/external-editor': 1.0.1(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/expand@4.0.17(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/expand@4.0.17(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/external-editor@1.0.1(@types/node@22.15.18)': - dependencies: - chardet: 2.1.0 - iconv-lite: 0.6.3 - optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/external-editor@1.0.1(@types/node@22.20.1)': + '@inquirer/external-editor@1.0.1(@types/node@22.20.5)': dependencies: chardet: 2.1.0 iconv-lite: 0.6.3 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@inquirer/figures@1.0.13': {} - '@inquirer/input@4.2.1(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/input@4.2.1(@types/node@22.20.1)': + '@inquirer/input@4.2.1(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 - '@inquirer/number@3.0.17(@types/node@22.15.18)': + '@inquirer/number@3.0.17(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/number@3.0.17(@types/node@22.20.1)': + '@inquirer/password@4.0.17(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/password@4.0.17(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - ansi-escapes: 4.3.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/password@4.0.17(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) ansi-escapes: 4.3.2 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/prompts@7.8.3(@types/node@22.15.18)': - dependencies: - '@inquirer/checkbox': 4.2.1(@types/node@22.15.18) - '@inquirer/confirm': 5.1.15(@types/node@22.15.18) - '@inquirer/editor': 4.2.17(@types/node@22.15.18) - '@inquirer/expand': 4.0.17(@types/node@22.15.18) - '@inquirer/input': 4.2.1(@types/node@22.15.18) - '@inquirer/number': 3.0.17(@types/node@22.15.18) - '@inquirer/password': 4.0.17(@types/node@22.15.18) - '@inquirer/rawlist': 4.1.5(@types/node@22.15.18) - '@inquirer/search': 3.1.0(@types/node@22.15.18) - '@inquirer/select': 4.3.1(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/prompts@7.8.3(@types/node@22.20.1)': - dependencies: - '@inquirer/checkbox': 4.2.1(@types/node@22.20.1) - '@inquirer/confirm': 5.1.15(@types/node@22.20.1) - '@inquirer/editor': 4.2.17(@types/node@22.20.1) - '@inquirer/expand': 4.0.17(@types/node@22.20.1) - '@inquirer/input': 4.2.1(@types/node@22.20.1) - '@inquirer/number': 3.0.17(@types/node@22.20.1) - '@inquirer/password': 4.0.17(@types/node@22.20.1) - '@inquirer/rawlist': 4.1.5(@types/node@22.20.1) - '@inquirer/search': 3.1.0(@types/node@22.20.1) - '@inquirer/select': 4.3.1(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/rawlist@4.1.5(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/type': 3.0.8(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/rawlist@4.1.5(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/type': 3.0.8(@types/node@22.20.1) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/search@3.1.0(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 + '@types/node': 22.20.5 + + '@inquirer/prompts@7.8.3(@types/node@22.20.5)': + dependencies: + '@inquirer/checkbox': 4.2.1(@types/node@22.20.5) + '@inquirer/confirm': 5.1.15(@types/node@22.20.5) + '@inquirer/editor': 4.2.17(@types/node@22.20.5) + '@inquirer/expand': 4.0.17(@types/node@22.20.5) + '@inquirer/input': 4.2.1(@types/node@22.20.5) + '@inquirer/number': 3.0.17(@types/node@22.20.5) + '@inquirer/password': 4.0.17(@types/node@22.20.5) + '@inquirer/rawlist': 4.1.5(@types/node@22.20.5) + '@inquirer/search': 3.1.0(@types/node@22.20.5) + '@inquirer/select': 4.3.1(@types/node@22.20.5) optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/search@3.1.0(@types/node@22.20.1)': + '@inquirer/rawlist@4.1.5(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.20.1) + '@inquirer/core': 10.1.15(@types/node@22.20.5) + '@inquirer/type': 3.0.8(@types/node@22.20.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 - '@inquirer/select@4.3.1(@types/node@22.15.18)': + '@inquirer/search@3.1.0(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.15.18) + '@inquirer/core': 10.1.15(@types/node@22.20.5) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.15.18) - ansi-escapes: 4.3.2 + '@inquirer/type': 3.0.8(@types/node@22.20.5) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/select@4.3.1(@types/node@22.20.1)': + '@inquirer/select@4.3.1(@types/node@22.20.5)': dependencies: - '@inquirer/core': 10.1.15(@types/node@22.20.1) + '@inquirer/core': 10.1.15(@types/node@22.20.5) '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.8(@types/node@22.20.1) + '@inquirer/type': 3.0.8(@types/node@22.20.5) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 22.20.1 - - '@inquirer/type@3.0.8(@types/node@22.15.18)': - optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.5 - '@inquirer/type@3.0.8(@types/node@22.20.1)': + '@inquirer/type@3.0.8(@types/node@22.20.5)': optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@isaacs/cliui@8.0.2': dependencies: @@ -22259,77 +22090,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0(supports-color@8.1.1) - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.20.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0(supports-color@8.1.1) - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.20.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5))': + '@jest/core@29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(supports-color@8.1.1) @@ -22343,7 +22104,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -22364,7 +22125,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5))': + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(supports-color@8.1.1) @@ -22378,42 +22139,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0(supports-color@8.1.1) - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.20.1 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -22583,7 +22309,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -22998,7 +22724,7 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.37.2 '@floating-ui/dom': 1.7.1 - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/plugin-tooltip': 7.13.1 @@ -23021,13 +22747,13 @@ snapshots: - supports-color - typescript - '@milkdown/core@7.13.1': + '@milkdown/core@7.13.1(supports-color@8.1.1)': dependencies: '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/transformer': 7.13.1 - remark-parse: 11.0.0 + remark-parse: 11.0.0(supports-color@8.1.1) remark-stringify: 11.0.0 tslib: 2.8.1 unified: 11.0.5 @@ -23073,7 +22799,7 @@ snapshots: '@milkdown/kit@7.13.1(@codemirror/language@6.11.1)(@codemirror/state@6.5.2)(@codemirror/view@6.37.2)(typescript@5.4.5)': dependencies: '@milkdown/components': 7.13.1(@codemirror/language@6.11.1)(@codemirror/state@6.5.2)(@codemirror/view@6.37.2)(typescript@5.4.5) - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/plugin-block': 7.13.1 '@milkdown/plugin-clipboard': 7.13.1 @@ -23101,7 +22827,7 @@ snapshots: '@milkdown/plugin-block@7.13.1': dependencies: '@floating-ui/dom': 1.7.1 - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23114,7 +22840,7 @@ snapshots: '@milkdown/plugin-clipboard@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23124,7 +22850,7 @@ snapshots: '@milkdown/plugin-collab@7.13.1(y-prosemirror@1.3.5(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23138,7 +22864,7 @@ snapshots: '@milkdown/plugin-cursor@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23148,7 +22874,7 @@ snapshots: '@milkdown/plugin-history@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23158,7 +22884,7 @@ snapshots: '@milkdown/plugin-indent@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23168,7 +22894,7 @@ snapshots: '@milkdown/plugin-listener@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23181,7 +22907,7 @@ snapshots: '@milkdown/plugin-slash@7.13.1': dependencies: '@floating-ui/dom': 1.7.1 - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23195,7 +22921,7 @@ snapshots: '@milkdown/plugin-tooltip@7.13.1': dependencies: '@floating-ui/dom': 1.7.1 - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23208,7 +22934,7 @@ snapshots: '@milkdown/plugin-trailing@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 '@milkdown/utils': 7.13.1 @@ -23218,7 +22944,7 @@ snapshots: '@milkdown/plugin-upload@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23229,7 +22955,7 @@ snapshots: '@milkdown/preset-commonmark@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23244,7 +22970,7 @@ snapshots: '@milkdown/preset-gfm@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/preset-commonmark': 7.13.1 @@ -23277,7 +23003,7 @@ snapshots: '@milkdown/theme-nord@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/prose': 7.13.1 clsx: 2.1.1 @@ -23290,7 +23016,7 @@ snapshots: '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 remark: 15.0.1 - remark-parse: 11.0.0 + remark-parse: 11.0.0(supports-color@8.1.1) remark-stringify: 11.0.0 tslib: 2.8.1 unified: 11.0.5 @@ -23299,7 +23025,7 @@ snapshots: '@milkdown/utils@7.13.1': dependencies: - '@milkdown/core': 7.13.1 + '@milkdown/core': 7.13.1(supports-color@8.1.1) '@milkdown/ctx': 7.13.1 '@milkdown/exception': 7.13.1 '@milkdown/prose': 7.13.1 @@ -23660,18 +23386,9 @@ snapshots: dependencies: '@oclif/core': 4.13.3 - '@oclif/plugin-not-found@3.2.65(@types/node@22.15.18)': + '@oclif/plugin-not-found@3.2.65(@types/node@22.20.5)': dependencies: - '@inquirer/prompts': 7.8.3(@types/node@22.15.18) - '@oclif/core': 4.13.3 - ansis: 3.17.0 - fast-levenshtein: 3.0.0 - transitivePeerDependencies: - - '@types/node' - - '@oclif/plugin-not-found@3.2.65(@types/node@22.20.1)': - dependencies: - '@inquirer/prompts': 7.8.3(@types/node@22.20.1) + '@inquirer/prompts': 7.8.3(@types/node@22.20.5) '@oclif/core': 4.13.3 ansis: 3.17.0 fast-levenshtein: 3.0.0 @@ -24853,7 +24570,7 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.2.0 '@types/keyv': 3.1.4 - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/responselike': 1.0.3 '@types/chai-dom@1.11.3': @@ -25128,7 +24845,7 @@ snapshots: '@types/fs-extra@9.0.13': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/geojson@7946.0.16': {} @@ -25139,7 +24856,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/har-format@1.2.15': {} @@ -25221,7 +24938,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/koa-compose@3.2.9': dependencies: @@ -25297,22 +25014,10 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@20.19.40': - dependencies: - undici-types: 6.21.0 - '@types/node@20.19.43': dependencies: undici-types: 6.21.0 - '@types/node@22.15.18': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.19.19': - dependencies: - undici-types: 6.21.0 - '@types/node@22.20.1': dependencies: undici-types: 6.21.0 @@ -25359,7 +25064,7 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 '@types/retry@0.12.2': {} @@ -27321,43 +27026,13 @@ snapshots: dependencies: buffer: 5.7.1 - create-jest@29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - create-jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -27366,13 +27041,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -27381,13 +27056,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -29036,6 +28711,11 @@ snapshots: forwarded@0.2.0: {} + foundry-local-sdk@2.1.0: + dependencies: + adm-zip: 0.6.1 + node-addon-api: 8.9.2 + fresh@0.5.2: {} fresh@2.0.0: {} @@ -30167,10 +29847,10 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 - jest-chrome@0.8.0(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5))): + jest-chrome@0.8.0(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5))): dependencies: '@types/chrome': 0.0.114 - jest: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-circus@29.7.0: dependencies: @@ -30198,16 +29878,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) + '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.3 @@ -30217,16 +29897,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.3 @@ -30236,54 +29916,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.3 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-cli@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.3 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest-cli@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): - dependencies: - '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.3 @@ -30312,162 +29954,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.19.40 - ts-node: 10.9.2(@types/node@20.19.40)(typescript@5.4.5) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.19.43 - ts-node: 10.9.2(@types/node@20.19.43)(typescript@5.4.5) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.15.18 - ts-node: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.19.19 - ts-node: 10.9.2(@types/node@22.19.19)(typescript@5.4.5) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)): - dependencies: - '@babel/core': 7.29.7 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.7) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.20.1 - ts-node: 10.9.2(@types/node@20.19.40)(typescript@5.4.5) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -30493,12 +29980,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.20.1 - ts-node: 10.9.2(@types/node@20.19.43)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@22.20.1)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -30524,12 +30011,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.20.1 - ts-node: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@22.20.5)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@26.4.1)(typescript@5.4.5)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -30555,12 +30042,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 22.20.1 - ts-node: 10.9.2(@types/node@22.19.19)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@26.4.1)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -30585,13 +30072,13 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 ts-node: 10.9.2(@types/node@22.20.1)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@26.4.1)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: '@babel/core': 7.29.7 '@jest/test-sequencer': 29.7.0 @@ -30616,8 +30103,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.20.1 - ts-node: 10.9.2(@types/node@26.4.1)(typescript@5.4.5) + '@types/node': 22.20.5 + ts-node: 10.9.2(@types/node@22.20.5)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -30702,7 +30189,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.20.1 + '@types/node': 22.20.5 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -30850,7 +30337,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 22.20.5 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -30884,65 +30371,41 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.40)(ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.43)(ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + jest@29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)): + jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)): + jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + '@jest/core': 29.7.0(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -31664,14 +31127,14 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.2: + mdast-util-from-markdown@2.0.2(supports-color@8.1.1): dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 decode-named-character-reference: 1.1.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 - micromark: 4.0.2 + micromark: 4.0.2(supports-color@8.1.1) micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-decode-string: 2.0.1 micromark-util-normalize-identifier: 2.0.1 @@ -31688,7 +31151,7 @@ snapshots: decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 - micromark: 4.0.2 + micromark: 4.0.2(supports-color@8.1.1) micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-decode-string: 2.0.1 micromark-util-normalize-identifier: 2.0.1 @@ -32036,7 +31499,7 @@ snapshots: micromark-util-types@2.0.2: {} - micromark@4.0.2: + micromark@4.0.2(supports-color@8.1.1): dependencies: '@types/debug': 4.1.13 debug: 4.4.3(supports-color@8.1.1) @@ -32316,6 +31779,8 @@ snapshots: node-addon-api@7.1.1: {} + node-addon-api@8.9.2: {} + node-api-version@0.2.1: dependencies: semver: 7.8.5 @@ -33624,7 +33089,7 @@ snapshots: '@types/mdast': 4.0.4 mdast-util-gfm: 3.1.0 micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 + remark-parse: 11.0.0(supports-color@8.1.1) remark-stringify: 11.0.0 unified: 11.0.5 transitivePeerDependencies: @@ -33645,10 +33110,10 @@ snapshots: transitivePeerDependencies: - supports-color - remark-parse@11.0.0: + remark-parse@11.0.0(supports-color@8.1.1): dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.2(supports-color@8.1.1) micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -33663,7 +33128,7 @@ snapshots: remark@15.0.1: dependencies: '@types/mdast': 4.0.4 - remark-parse: 11.0.0 + remark-parse: 11.0.0(supports-color@8.1.1) remark-stringify: 11.0.0 unified: 11.0.5 transitivePeerDependencies: @@ -34148,7 +33613,7 @@ snapshots: dependencies: kind-of: 6.0.3 - sharp@0.35.5(@types/node@22.15.18): + sharp@0.35.5(@types/node@22.20.5): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 @@ -34179,7 +33644,7 @@ snapshots: '@img/sharp-win32-arm64': 0.35.5 '@img/sharp-win32-ia32': 0.35.5 '@img/sharp-win32-x64': 0.35.5 - '@types/node': 22.15.18 + '@types/node': 22.20.5 sharp@0.35.5(@types/node@26.4.1): dependencies: @@ -34949,12 +34414,12 @@ snapshots: '@ts-graphviz/common': 2.1.5 '@ts-graphviz/core': 2.0.7 - ts-jest@29.3.3(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.3.3(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -34991,12 +34456,33 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.29.7) esbuild: 0.28.1 - ts-jest@29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)))(typescript@5.4.5): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.8.4 + type-fest: 4.41.0 + typescript: 5.4.5 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.7 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.7) + esbuild: 0.28.1 + jest-util: 29.7.0 + + ts-jest@29.4.9(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 29.7.0(@types/node@22.20.1)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5)) + jest: 29.7.0(@types/node@22.20.5)(ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -35032,52 +34518,14 @@ snapshots: typescript: 5.4.5 webpack: 5.105.0(esbuild@0.28.1)(postcss@8.5.26)(webpack-cli@5.1.4) - ts-node@10.9.2(@types/node@20.19.40)(typescript@5.4.5): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.13 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.40 - acorn: 8.18.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.4.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - - ts-node@10.9.2(@types/node@20.19.43)(typescript@5.4.5): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.13 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.19.43 - acorn: 8.18.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.4.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true - - ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5): + ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.13 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.15.18 + '@types/node': 22.20.1 acorn: 8.18.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -35087,16 +34535,15 @@ snapshots: typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true - ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5): + ts-node@10.9.2(@types/node@22.20.5)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.13 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.19.19 + '@types/node': 22.20.5 acorn: 8.18.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -35108,24 +34555,6 @@ snapshots: yn: 3.1.1 optional: true - ts-node@10.9.2(@types/node@22.20.1)(typescript@5.4.5): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.13 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 22.20.1 - acorn: 8.18.0 - acorn-walk: 8.3.5 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.4.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - ts-node@10.9.2(@types/node@26.4.1)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -35486,7 +34915,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite@6.4.4(@types/node@22.15.18)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.3): + vite@6.4.4(@types/node@22.20.1)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.5) @@ -35495,7 +34924,24 @@ snapshots: rollup: 4.62.4 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 22.20.1 + fsevents: 2.3.3 + jiti: 2.7.0 + less: 4.3.0 + terser: 5.51.2 + tsx: 4.21.0 + yaml: 2.9.0 + + vite@6.4.4(@types/node@22.20.5)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.8.3): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.26 + rollup: 4.62.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 22.20.5 fsevents: 2.3.3 jiti: 2.7.0 less: 4.3.0 @@ -35503,7 +34949,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@6.4.4(@types/node@22.20.1)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0): + vite@6.4.4(@types/node@22.20.5)(jiti@2.7.0)(less@4.3.0)(terser@5.51.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.5) @@ -35512,7 +34958,7 @@ snapshots: rollup: 4.62.4 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 22.20.1 + '@types/node': 22.20.5 fsevents: 2.3.3 jiti: 2.7.0 less: 4.3.0 diff --git a/ts/pnpm-workspace.yaml b/ts/pnpm-workspace.yaml index 4ecf9246c9..31eb3b276e 100644 --- a/ts/pnpm-workspace.yaml +++ b/ts/pnpm-workspace.yaml @@ -45,6 +45,7 @@ allowBuilds: electron-winstaller: true esbuild: true exifreader: true + foundry-local-sdk: true keytar: true koffi: true node-pty: false diff --git a/ts/tools/scripts/prune-shell-deploy.mjs b/ts/tools/scripts/prune-shell-deploy.mjs index 7a50ee8b56..3ceeb70b83 100644 --- a/ts/tools/scripts/prune-shell-deploy.mjs +++ b/ts/tools/scripts/prune-shell-deploy.mjs @@ -12,8 +12,8 @@ * * 1. The native runtime packages for @github/copilot-sdk and * @anthropic-ai/claude-agent-sdk (~250MB and ~230MB per platform). The - * connect-only shell delegates ALL model work to the agent-server (speech - * classification, translation, embeddings), so it never spawns these host + * connect-only shell delegates classification, translation, and embeddings + * to the agent-server; Nemotron ASR stays local. It never spawns these host * CLIs — every platform-native package is dropped. The small JS wrapper * package (@github/copilot-sdk) is kept so aiclient's * lazy `import()` still resolves in the unlikely event it is reached; the