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