From 9d6bcecd8d5089ffaa5fb95c2b068560b1e96bd6 Mon Sep 17 00:00:00 2001
From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com>
Date: Sat, 24 Oct 2026 03:35:46 -0700
Subject: [PATCH 1/2] add local Nemotron speech recognition
Add a Nemotron speech provider hosted in an Electron utility process
using Foundry Local and ONNX Runtime.
- Add @shell speechProvider azure|whisper|nemotron with persisted selection
- Download and cache Nemotron on first selection, with progress and cancellation
- Stream microphone PCM to native inference and partial transcripts to the textbox
- Add preparing/finalizing states while preserving editable text and no auto-submit
- Retain Azure as the default and localWhisper on/off as deprecated aliases
- Clean up capture and recognition resources on cancellation, switching, and shutdown
- Add focused tests and Electron worker/native-library packaging configuration
Initially supports Windows x64 single-shot dictation.
---
ts/packages/chat-ui/src/chatPanel.ts | 16 +-
ts/packages/chat-ui/src/providers.ts | 2 +
ts/packages/chat-ui/test/speechInput.spec.ts | 61 +
ts/packages/shell/README.md | 2 +-
ts/packages/shell/electron-builder.config.js | 1 +
ts/packages/shell/electron.vite.config.mts | 9 +
ts/packages/shell/package.json | 4 +-
ts/packages/shell/playwright.config.ts | 2 +-
ts/packages/shell/src/main/agent.ts | 2 +
ts/packages/shell/src/main/index.ts | 2 +
ts/packages/shell/src/main/localAsrHost.ts | 200 +++
.../shell/src/main/localAsrTranscript.ts | 21 +
ts/packages/shell/src/main/localAsrWorker.ts | 242 ++++
.../src/main/localWhisperCommandHandler.ts | 53 +-
ts/packages/shell/src/main/speech.ts | 51 +-
ts/packages/shell/src/main/speechProvider.ts | 133 ++
ts/packages/shell/src/preload/chatView.ts | 22 +
.../shell/src/preload/electronTypes.ts | 8 +
.../shell/src/preload/localAsrTypes.ts | 23 +
.../shell/src/preload/shellSettingsType.ts | 4 +
.../src/renderer/src/electronProviders.ts | 144 ++-
.../src/renderer/src/localWhisperClient.ts | 82 +-
ts/packages/shell/src/renderer/src/main.ts | 5 +-
.../shell/src/renderer/src/nemotronCapture.ts | 203 +++
.../src/renderer/src/pcmCaptureWorklet.js | 57 +
ts/packages/shell/src/renderer/src/speech.ts | 79 +-
.../shell/src/renderer/src/webSocketAPI.ts | 22 +
ts/packages/shell/src/tsconfig.json | 6 +-
ts/packages/shell/test/speechProvider.spec.ts | 21 +
ts/packages/shell/test/unit/localAsr.test.mts | 301 +++++
ts/pnpm-lock.yaml | 1090 ++++-------------
ts/pnpm-workspace.yaml | 1 +
ts/tools/scripts/prune-shell-deploy.mjs | 4 +-
33 files changed, 1952 insertions(+), 921 deletions(-)
create mode 100644 ts/packages/chat-ui/test/speechInput.spec.ts
create mode 100644 ts/packages/shell/src/main/localAsrHost.ts
create mode 100644 ts/packages/shell/src/main/localAsrTranscript.ts
create mode 100644 ts/packages/shell/src/main/localAsrWorker.ts
create mode 100644 ts/packages/shell/src/main/speechProvider.ts
create mode 100644 ts/packages/shell/src/preload/localAsrTypes.ts
create mode 100644 ts/packages/shell/src/renderer/src/nemotronCapture.ts
create mode 100644 ts/packages/shell/src/renderer/src/pcmCaptureWorklet.js
create mode 100644 ts/packages/shell/test/speechProvider.spec.ts
create mode 100644 ts/packages/shell/test/unit/localAsr.test.mts
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..f16a90720a 100644
--- a/ts/packages/shell/src/main/agent.ts
+++ b/ts/packages/shell/src/main/agent.ts
@@ -23,6 +23,7 @@ import {
displayWarn,
} from "@typeagent/agent-sdk/helpers/display";
import { getLocalWhisperCommandHandlers } from "./localWhisperCommandHandler.js";
+import { getSpeechProviderCommandHandlers } from "./speechProvider.js";
import { ShellWindow } from "./shellWindow.js";
import {
getObjectProperty,
@@ -389,6 +390,7 @@ const handlers: CommandHandlerTable = {
},
topmost: new ShellSetTopMostCommandHandler(),
localWhisper: getLocalWhisperCommandHandlers(),
+ 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..208e6f0a43 100644
--- a/ts/packages/shell/src/main/localWhisperCommandHandler.ts
+++ b/ts/packages/shell/src/main/localWhisperCommandHandler.ts
@@ -3,14 +3,10 @@
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 { selectSpeechProvider } from "./speechProvider.js";
import registerDebug from "debug";
import { fileURLToPath } from "url";
@@ -81,7 +77,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) {
@@ -119,39 +115,22 @@ export function getLocalWhisperCommandHandlers(): CommandHandlerTable {
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 selectSpeechProvider(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 selectSpeechProvider(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
From 5c1f45dce017e507b51bc73a347b2c81ac7cb800 Mon Sep 17 00:00:00 2001
From: Hillary Mutisya <150286414+hillary-mutisya@users.noreply.github.com>
Date: Sat, 24 Oct 2026 04:00:34 -0700
Subject: [PATCH 2/2] fix(shell): break speech provider command import cycle
Inject the shared speech provider selector when registering the deprecated Whisper commands, removing their runtime import back to speechProvider while preserving provider selection behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
ts/packages/shell/src/main/agent.ts | 7 +++++--
.../shell/src/main/localWhisperCommandHandler.ts | 12 ++++++++----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/ts/packages/shell/src/main/agent.ts b/ts/packages/shell/src/main/agent.ts
index f16a90720a..78e654703f 100644
--- a/ts/packages/shell/src/main/agent.ts
+++ b/ts/packages/shell/src/main/agent.ts
@@ -23,7 +23,10 @@ import {
displayWarn,
} from "@typeagent/agent-sdk/helpers/display";
import { getLocalWhisperCommandHandlers } from "./localWhisperCommandHandler.js";
-import { getSpeechProviderCommandHandlers } from "./speechProvider.js";
+import {
+ getSpeechProviderCommandHandlers,
+ selectSpeechProvider,
+} from "./speechProvider.js";
import { ShellWindow } from "./shellWindow.js";
import {
getObjectProperty,
@@ -389,7 +392,7 @@ const handlers: CommandHandlerTable = {
},
},
topmost: new ShellSetTopMostCommandHandler(),
- localWhisper: getLocalWhisperCommandHandlers(),
+ localWhisper: getLocalWhisperCommandHandlers(selectSpeechProvider),
speechProvider: getSpeechProviderCommandHandlers(),
theme: getThemeCommandHandlers(),
voice: getVoiceCommandHandlers(),
diff --git a/ts/packages/shell/src/main/localWhisperCommandHandler.ts b/ts/packages/shell/src/main/localWhisperCommandHandler.ts
index 208e6f0a43..def951d017 100644
--- a/ts/packages/shell/src/main/localWhisperCommandHandler.ts
+++ b/ts/packages/shell/src/main/localWhisperCommandHandler.ts
@@ -6,7 +6,6 @@ import { CommandHandlerTable } from "@typeagent/agent-sdk/helpers/command";
import { displayWarn } from "@typeagent/agent-sdk/helpers/display";
import { ActionContext } from "@typeagent/agent-sdk";
import type { ShellContext } from "./agent.js";
-import { selectSpeechProvider } from "./speechProvider.js";
import registerDebug from "debug";
import { fileURLToPath } from "url";
@@ -109,7 +108,12 @@ 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: {
@@ -120,7 +124,7 @@ export function getLocalWhisperCommandHandlers(): CommandHandlerTable {
"Deprecated: use @shell speechProvider azure.",
context,
);
- await selectSpeechProvider(context, "azure");
+ await selectProvider(context, "azure");
},
},
on: {
@@ -130,7 +134,7 @@ export function getLocalWhisperCommandHandlers(): CommandHandlerTable {
"Deprecated: use @shell speechProvider whisper.",
context,
);
- await selectSpeechProvider(context, "whisper");
+ await selectProvider(context, "whisper");
},
},
},