diff --git a/ts/examples/memoryProviders/package.json b/ts/examples/memoryProviders/package.json index c2fc773cb3..6835499490 100644 --- a/ts/examples/memoryProviders/package.json +++ b/ts/examples/memoryProviders/package.json @@ -28,7 +28,7 @@ "tsc": "tsc -b" }, "dependencies": { - "@elastic/elasticsearch": "^8.17.0", + "@elastic/elasticsearch": "^8.19.1", "aiclient": "workspace:*", "better-sqlite3": "12.2.0", "debug": "^4.4.0", diff --git a/ts/package.json b/ts/package.json index 58d41c51c9..68cae25470 100644 --- a/ts/package.json +++ b/ts/package.json @@ -54,6 +54,7 @@ "@fluidframework/build-tools": "^0.57.0", "@types/node": "^20.17.28", "markdown-link-check": "^3.14.2", + "prebuild-install": "^7.1.3", "prettier": "^3.5.3", "shx": "^0.4.0" }, diff --git a/ts/packages/agents/calendar/src/calendarSchema.agr b/ts/packages/agents/calendar/src/calendarSchema.agr index 8ec576df8a..4ea768bf2a 100644 --- a/ts/packages/agents/calendar/src/calendarSchema.agr +++ b/ts/packages/agents/calendar/src/calendarSchema.agr @@ -105,31 +105,3 @@ | (can you)? find what I have scheduled this week -> { actionName: "findThisWeeksEvents" } - -// Common entity types and patterns - -@ = - $(x:number) -| one -> 1 -| two -> 2 -| three -> 3 -| four -> 4 -| five -> 5 -| six -> 6 -| seven -> 7 -| eight -> 8 -| nine -> 9 -| ten -> 10 - -@ = - first -> 1 -| second -> 2 -| third -> 3 -| fourth -> 4 -| fifth -> 5 -| sixth -> 6 -| seventh -> 7 -| eighth -> 8 -| ninth -> 9 -| tenth -> 10 - diff --git a/ts/packages/agents/playerLocal/README.md b/ts/packages/agents/playerLocal/README.md new file mode 100644 index 0000000000..394d6cf0f2 --- /dev/null +++ b/ts/packages/agents/playerLocal/README.md @@ -0,0 +1,135 @@ +# Local Music Player TypeAgent + +A TypeAgent for playing local audio files without requiring any external service like Spotify. + +## Features + +- **Play local audio files** - MP3, WAV, OGG, FLAC, M4A, AAC, WMA +- **Queue management** - Add files to queue, show queue, clear queue +- **Playback controls** - Play, pause, resume, stop, next, previous +- **Volume control** - Set volume, mute/unmute +- **Shuffle and repeat** - Shuffle mode, repeat one/all +- **File search** - Search for files in your music folder +- **Cross-platform** - Works on Windows, macOS, and Linux + +## Setup + +No external API keys required! The agent uses the system's built-in audio capabilities: + +- **Windows**: Uses PowerShell with Windows Media Player +- **macOS**: Uses `afplay` +- **Linux**: Uses `mpv` (must be installed separately; for example: Debian/Ubuntu: `sudo apt install mpv`, Fedora: `sudo dnf install mpv`, Arch: `sudo pacman -S mpv`) + +## Configuration + +Set your music folder using the command: + +``` +@localPlayer folder set /path/to/music +``` + +Or use natural language: + +``` +set music folder to C:\Users\Me\Music +``` + +## Usage + +### Enable the agent + +In the shell or interactive mode: + +``` +@config localPlayer on +``` + +### Example commands + +**Play music:** + +``` +play some music +play song.mp3 +play all songs in the folder +``` + +**Control playback:** + +``` +pause +resume +stop +next track +previous track +``` + +**Volume:** + +``` +set volume to 50 +turn up the volume +mute +``` + +**Queue management:** + +``` +show the queue +add rock song to queue +clear the queue +play the third track +``` + +**Browse files:** + +``` +list files +search for beethoven +show music folder +``` + +## Available Actions + +| Action | Description | +| ----------------- | ------------------------------------- | +| `playFile` | Play a specific audio file | +| `playFolder` | Play all audio files in a folder | +| `playFromQueue` | Play a track from the queue by number | +| `status` | Show current playback status | +| `pause` | Pause playback | +| `resume` | Resume playback | +| `stop` | Stop playback | +| `next` | Skip to next track | +| `previous` | Go to previous track | +| `shuffle` | Turn shuffle on/off | +| `repeat` | Set repeat mode (off/one/all) | +| `setVolume` | Set volume level (0-100) | +| `changeVolume` | Adjust volume by amount | +| `mute` | Mute audio | +| `unmute` | Unmute audio | +| `listFiles` | List audio files in folder | +| `searchFiles` | Search for files by name | +| `addToQueue` | Add file to playback queue | +| `clearQueue` | Clear the queue | +| `showQueue` | Display the queue | +| `setMusicFolder` | Set default music folder | +| `showMusicFolder` | Show current music folder | + +## Supported Audio Formats + +- MP3 (.mp3) +- WAV (.wav) +- OGG (.ogg) +- FLAC (.flac) +- M4A (.m4a) +- AAC (.aac) +- WMA (.wma) + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft +trademarks or logos is subject to and must follow +[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). +Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. +Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/ts/packages/agents/playerLocal/package.json b/ts/packages/agents/playerLocal/package.json new file mode 100644 index 0000000000..21936940a3 --- /dev/null +++ b/ts/packages/agents/playerLocal/package.json @@ -0,0 +1,41 @@ +{ + "name": "music-local", + "version": "0.0.1", + "private": true, + "description": "Local media player agent for TypeAgent", + "homepage": "https://github.com/microsoft/TypeAgent#readme", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/TypeAgent.git", + "directory": "ts/packages/agents/playerLocal" + }, + "license": "MIT", + "author": "Microsoft", + "type": "module", + "exports": { + "./agent/manifest": "./src/agent/localPlayerManifest.json", + "./agent/handlers": "./dist/agent/localPlayerHandlers.js" + }, + "scripts": { + "agc": "agc -i ./src/agent/localPlayerSchema.agr -o ./dist/agent/localPlayerSchema.ag.json", + "asc": "asc -i ./src/agent/localPlayerSchema.ts -o ./dist/agent/localPlayerSchema.pas.json -t LocalPlayerActions -e LocalPlayerEntities", + "build": "concurrently npm:tsc npm:asc npm:agc", + "clean": "rimraf --glob dist *.tsbuildinfo *.done.build.log", + "tsc": "tsc -p src" + }, + "dependencies": { + "@typeagent/agent-sdk": "workspace:*", + "@typeagent/common-utils": "workspace:*", + "chalk": "^5.4.1", + "debug": "^4.4.0", + "play-sound": "^1.1.6" + }, + "devDependencies": { + "@typeagent/action-schema-compiler": "workspace:*", + "@types/debug": "^4.1.12", + "action-grammar-compiler": "workspace:*", + "concurrently": "^9.1.2", + "rimraf": "^6.0.1", + "typescript": "~5.4.5" + } +} diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerCommands.ts b/ts/packages/agents/playerLocal/src/agent/localPlayerCommands.ts new file mode 100644 index 0000000000..19cc60ca52 --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerCommands.ts @@ -0,0 +1,424 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + ActionContext, + AppAgentCommandInterface, + ParsedCommandParams, +} from "@typeagent/agent-sdk"; +import { + CommandHandler, + CommandHandlerNoParams, + CommandHandlerTable, + getCommandInterface, +} from "@typeagent/agent-sdk/helpers/command"; +import { + displayStatus, + displaySuccess, + displayWarn, + displayError, +} from "@typeagent/agent-sdk/helpers/display"; +import { LocalPlayerActionContext } from "./localPlayerHandlers.js"; + +// Helper to get service with error handling +function getService(context: ActionContext) { + const service = context.sessionContext.agentContext.playerService; + if (!service) { + displayError( + "Local player not initialized. Enable it with: @config localPlayer on", + context, + ); + return undefined; + } + return service; +} + +// Status command handler +class StatusCommandHandler implements CommandHandlerNoParams { + public readonly description = "Show local player status"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const state = service.getState(); + + if (state.currentTrack) { + const status = state.isPlaying + ? "▶️ Playing" + : state.isPaused + ? "⏸️ Paused" + : "⏹️ Stopped"; + displaySuccess( + `${status}: ${state.currentTrack.name}\n` + + `Volume: ${state.volume}%${state.isMuted ? " (muted)" : ""}\n` + + `Shuffle: ${state.shuffle ? "On" : "Off"} | Repeat: ${state.repeat}\n` + + `Queue: ${state.currentIndex + 1}/${state.queue.length} tracks`, + context, + ); + } else { + displayWarn( + "No track loaded. Use '@localPlayer play' to start.", + context, + ); + } + } +} + +// Play command handler with optional file parameter +const playParameters = { + args: { + file: { + description: + "File name or path to play (optional - plays first file if not specified)", + optional: true, + }, + }, +} as const; + +const playHandler: CommandHandler = { + description: "Play an audio file or resume playback", + parameters: playParameters, + run: async ( + context: ActionContext, + params: ParsedCommandParams, + ) => { + const service = getService(context); + if (!service) return; + + const fileName = params.args.file; + + if (fileName) { + const success = await service.playFile(fileName); + if (success) { + const state = service.getState(); + displaySuccess( + `▶️ Playing: ${state.currentTrack?.name}`, + context, + ); + } else { + displayError(`Could not find or play: ${fileName}`, context); + } + } else { + // Resume or play first file + const state = service.getState(); + if (state.isPaused) { + service.resume(); + displaySuccess( + `▶️ Resumed: ${state.currentTrack?.name}`, + context, + ); + } else if (state.queue.length > 0) { + await service.playFromQueue(state.currentIndex + 1); + displaySuccess( + `▶️ Playing: ${service.getState().currentTrack?.name}`, + context, + ); + } else { + // Play first file from folder + const success = await service.playFolder(); + if (success) { + displaySuccess( + `▶️ Playing: ${service.getState().currentTrack?.name}`, + context, + ); + } else { + displayWarn( + "No audio files found. Set music folder with: @localPlayer setfolder ", + context, + ); + } + } + } + }, +}; + +// Pause command +class PauseCommandHandler implements CommandHandlerNoParams { + public readonly description = "Pause playback"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + service.pause(); + displaySuccess("⏸️ Paused", context); + } +} + +// Resume command +class ResumeCommandHandler implements CommandHandlerNoParams { + public readonly description = "Resume playback"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + service.resume(); + const state = service.getState(); + displaySuccess( + `▶️ Resumed: ${state.currentTrack?.name || ""}`, + context, + ); + } +} + +// Stop command +class StopCommandHandler implements CommandHandlerNoParams { + public readonly description = "Stop playback"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + service.stop(); + displaySuccess("⏹️ Stopped", context); + } +} + +// Next command +class NextCommandHandler implements CommandHandlerNoParams { + public readonly description = "Play next track"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const success = await service.next(); + if (success) { + const state = service.getState(); + displaySuccess(`⏭️ Next: ${state.currentTrack?.name}`, context); + } else { + displayWarn("No next track available", context); + } + } +} + +// Previous command +class PrevCommandHandler implements CommandHandlerNoParams { + public readonly description = "Play previous track"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const success = await service.previous(); + if (success) { + const state = service.getState(); + displaySuccess(`⏮️ Previous: ${state.currentTrack?.name}`, context); + } else { + displayWarn("No previous track available", context); + } + } +} + +// Folder command - show current folder +class FolderCommandHandler implements CommandHandlerNoParams { + public readonly description = "Show current music folder"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const folder = service.getMusicFolder(); + displayStatus(`📁 Music folder: ${folder}`, context); + } +} + +// Set folder command with parameter +const setFolderParameters = { + args: { + path: { + description: "Path to the music folder", + }, + }, +} as const; + +const setFolderHandler: CommandHandler = { + description: "Set the music folder path", + parameters: setFolderParameters, + run: async ( + context: ActionContext, + params: ParsedCommandParams, + ) => { + const service = getService(context); + if (!service) return; + + const folderPath = params.args.path; + const success = service.setMusicFolder(folderPath); + + if (success) { + const files = service.listFiles(); + displaySuccess( + `📁 Music folder set to: ${folderPath}\nFound ${files.length} audio files`, + context, + ); + } else { + displayError(`Invalid folder path: ${folderPath}`, context); + } + }, +}; + +// List command +class ListCommandHandler implements CommandHandlerNoParams { + public readonly description = "List audio files in music folder"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const files = service.listFiles(); + + if (files.length === 0) { + displayWarn("No audio files found in music folder", context); + return; + } + + const fileList = files + .slice(0, 20) + .map((f, i) => `${i + 1}. ${f.name}`) + .join("\n"); + + let message = `🎵 Found ${files.length} audio files:\n${fileList}`; + if (files.length > 20) { + message += `\n...and ${files.length - 20} more`; + } + + displaySuccess(message, context); + } +} + +// Queue command +class QueueCommandHandler implements CommandHandlerNoParams { + public readonly description = "Show playback queue"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const queue = service.getQueue(); + const state = service.getState(); + + if (queue.length === 0) { + displayWarn("Queue is empty", context); + return; + } + + const queueList = queue + .slice(0, 20) + .map((track, i) => { + const current = i === state.currentIndex ? " ▶️" : ""; + return `${i + 1}. ${track.name}${current}`; + }) + .join("\n"); + + let message = `📋 Queue (${queue.length} tracks):\n${queueList}`; + if (queue.length > 20) { + message += `\n...and ${queue.length - 20} more`; + } + + displaySuccess(message, context); + } +} + +// Clear command +class ClearCommandHandler implements CommandHandlerNoParams { + public readonly description = "Clear playback queue"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + service.clearQueue(); + displaySuccess("🗑️ Queue cleared", context); + } +} + +// Shuffle command +class ShuffleCommandHandler implements CommandHandlerNoParams { + public readonly description = "Toggle shuffle mode"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const state = service.getState(); + service.setShuffle(!state.shuffle); + displaySuccess(`🔀 Shuffle: ${!state.shuffle ? "On" : "Off"}`, context); + } +} + +// Volume command with parameter +const volumeParameters = { + args: { + level: { + description: "Volume level (0-100)", + }, + }, +} as const; + +const volumeHandler: CommandHandler = { + description: "Set volume level (0-100)", + parameters: volumeParameters, + run: async ( + context: ActionContext, + params: ParsedCommandParams, + ) => { + const service = getService(context); + if (!service) return; + + const level = parseInt(params.args.level, 10); + if (isNaN(level) || level < 0 || level > 100) { + displayError("Volume must be a number between 0 and 100", context); + return; + } + + service.setVolume(level); + displaySuccess(`🔊 Volume: ${level}%`, context); + }, +}; + +// Mute command +class MuteCommandHandler implements CommandHandlerNoParams { + public readonly description = "Toggle mute"; + + public async run(context: ActionContext) { + const service = getService(context); + if (!service) return; + + const state = service.getState(); + if (state.isMuted) { + service.unmute(); + displaySuccess(`🔊 Unmuted (Volume: ${state.volume}%)`, context); + } else { + service.mute(); + displaySuccess("🔇 Muted", context); + } + } +} + +const handlers: CommandHandlerTable = { + description: "Local music player commands", + defaultSubCommand: "status", + commands: { + status: new StatusCommandHandler(), + play: playHandler, + pause: new PauseCommandHandler(), + resume: new ResumeCommandHandler(), + stop: new StopCommandHandler(), + next: new NextCommandHandler(), + prev: new PrevCommandHandler(), + folder: new FolderCommandHandler(), + setfolder: setFolderHandler, + list: new ListCommandHandler(), + queue: new QueueCommandHandler(), + clear: new ClearCommandHandler(), + shuffle: new ShuffleCommandHandler(), + volume: volumeHandler, + mute: new MuteCommandHandler(), + }, +}; + +export function getLocalPlayerCommandInterface(): AppAgentCommandInterface { + return getCommandInterface(handlers); +} diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts b/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts new file mode 100644 index 0000000000..77acb11451 --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts @@ -0,0 +1,427 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import chalk from "chalk"; +import { + AppAgent, + SessionContext, + ActionContext, + AppAgentEvent, + TypeAgentAction, +} from "@typeagent/agent-sdk"; +import { + createActionResultFromHtmlDisplay, + createActionResultFromError, +} from "@typeagent/agent-sdk/helpers/action"; +import { LocalPlayerActions } from "./localPlayerSchema.js"; +import { + getLocalPlayerService, + LocalPlayerService, +} from "../localPlayerService.js"; +import { getLocalPlayerCommandInterface } from "./localPlayerCommands.js"; +import registerDebug from "debug"; + +const debug = registerDebug("typeagent:localPlayer"); + +export function instantiate(): AppAgent { + return { + initializeAgentContext: initializeLocalPlayerContext, + updateAgentContext: updateLocalPlayerContext, + executeAction: executeLocalPlayerAction, + ...getLocalPlayerCommandInterface(), + }; +} + +export type LocalPlayerActionContext = { + playerService: LocalPlayerService | undefined; +}; + +async function initializeLocalPlayerContext(): Promise { + return { + playerService: undefined, + }; +} + +async function updateLocalPlayerContext( + enable: boolean, + context: SessionContext, +): Promise { + if (enable) { + if (context.agentContext.playerService) { + return; + } + try { + context.agentContext.playerService = getLocalPlayerService(); + const musicFolder = + context.agentContext.playerService.getMusicFolder(); + const message = `Local player enabled. Music folder: ${musicFolder}`; + debug(message); + context.notify(AppAgentEvent.Info, chalk.green(message)); + } catch (e: any) { + const message = `Failed to initialize local player: ${e.message}`; + debug(message); + context.notify(AppAgentEvent.Error, chalk.red(message)); + } + } else { + if (context.agentContext.playerService) { + context.agentContext.playerService.stop(); + context.agentContext.playerService = undefined; + } + } +} + +async function executeLocalPlayerAction( + action: TypeAgentAction, + context: ActionContext, +) { + const playerService = context.sessionContext.agentContext.playerService; + + if (!playerService) { + return createActionResultFromError( + "Local player not initialized. Use '@localPlayer on' to enable.", + ); + } + + try { + switch (action.actionName) { + case "playFile": + return handlePlayFile( + playerService, + action.parameters.fileName, + ); + + case "playFolder": + return handlePlayFolder( + playerService, + action.parameters?.folderPath, + action.parameters?.shuffle, + ); + + case "playFromQueue": + return handlePlayFromQueue( + playerService, + action.parameters.trackNumber, + ); + + case "status": + return handleStatus(playerService); + + case "pause": + return handlePause(playerService); + + case "resume": + return handleResume(playerService); + + case "stop": + return handleStop(playerService); + + case "next": + return handleNext(playerService); + + case "previous": + return handlePrevious(playerService); + + case "shuffle": + return handleShuffle(playerService, action.parameters.on); + + case "repeat": + return handleRepeat(playerService, action.parameters.mode); + + case "setVolume": + return handleSetVolume(playerService, action.parameters.level); + + case "changeVolume": + return handleChangeVolume( + playerService, + action.parameters.amount, + ); + + case "mute": + return handleMute(playerService); + + case "unmute": + return handleUnmute(playerService); + + case "listFiles": + return handleListFiles( + playerService, + action.parameters?.folderPath, + ); + + case "searchFiles": + return handleSearchFiles( + playerService, + action.parameters.query, + ); + + case "addToQueue": + return handleAddToQueue( + playerService, + action.parameters.fileName, + ); + + case "clearQueue": + return handleClearQueue(playerService); + + case "showQueue": + return handleShowQueue(playerService); + + case "setMusicFolder": + return handleSetMusicFolder( + playerService, + action.parameters.folderPath, + ); + + case "showMusicFolder": + return handleShowMusicFolder(playerService); + + default: + return createActionResultFromError( + `Unknown action: ${(action as any).actionName}`, + ); + } + } catch (error: any) { + return createActionResultFromError(`Error: ${error.message}`); + } +} + +// Action handlers + +async function handlePlayFile(service: LocalPlayerService, fileName: string) { + const success = await service.playFile(fileName); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

▶️ Now playing: ${state.currentTrack?.name || fileName}

`, + ); + } + return createActionResultFromError(`Could not find or play: ${fileName}`); +} + +async function handlePlayFolder( + service: LocalPlayerService, + folderPath?: string, + shuffle?: boolean, +) { + const success = await service.playFolder(folderPath, shuffle || false); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

▶️ Playing ${state.queue.length} tracks from folder${shuffle ? " (shuffled)" : ""}

+

Now playing: ${state.currentTrack?.name}

`, + ); + } + return createActionResultFromError( + "No audio files found in the specified folder", + ); +} + +async function handlePlayFromQueue( + service: LocalPlayerService, + trackNumber: number, +) { + const success = await service.playFromQueue(trackNumber); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

▶️ Now playing track ${trackNumber}: ${state.currentTrack?.name}

`, + ); + } + return createActionResultFromError(`Invalid track number: ${trackNumber}`); +} + +function handleStatus(service: LocalPlayerService) { + const state = service.getState(); + + let statusHtml = "

🎵 Local Player Status

"; + + if (state.currentTrack) { + const playIcon = state.isPlaying ? "▶️" : state.isPaused ? "⏸️" : "⏹️"; + statusHtml += `

${playIcon} ${state.currentTrack.name}

`; + } else { + statusHtml += "

No track loaded

"; + } + + statusHtml += `

Volume: ${state.volume}%${state.isMuted ? " (muted)" : ""}

`; + statusHtml += `

Shuffle: ${state.shuffle ? "On" : "Off"} | Repeat: ${state.repeat}

`; + statusHtml += `

Queue: ${state.queue.length} tracks

`; + + return createActionResultFromHtmlDisplay(statusHtml); +} + +function handlePause(service: LocalPlayerService) { + service.pause(); + return createActionResultFromHtmlDisplay("

⏸️ Paused

"); +} + +function handleResume(service: LocalPlayerService) { + service.resume(); + return createActionResultFromHtmlDisplay("

▶️ Resumed

"); +} + +function handleStop(service: LocalPlayerService) { + service.stop(); + return createActionResultFromHtmlDisplay("

⏹️ Stopped

"); +} + +async function handleNext(service: LocalPlayerService) { + const success = await service.next(); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

⏭️ Next: ${state.currentTrack?.name}

`, + ); + } + return createActionResultFromError("No next track available"); +} + +async function handlePrevious(service: LocalPlayerService) { + const success = await service.previous(); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

⏮️ Previous: ${state.currentTrack?.name}

`, + ); + } + return createActionResultFromError("No previous track available"); +} + +function handleShuffle(service: LocalPlayerService, on: boolean) { + service.setShuffle(on); + return createActionResultFromHtmlDisplay( + `

🔀 Shuffle: ${on ? "On" : "Off"}

`, + ); +} + +function handleRepeat( + service: LocalPlayerService, + mode: "off" | "one" | "all", +) { + service.setRepeat(mode); + const modeText = + mode === "one" + ? "Repeat One 🔂" + : mode === "all" + ? "Repeat All 🔁" + : "Off"; + return createActionResultFromHtmlDisplay(`

Repeat: ${modeText}

`); +} + +function handleSetVolume(service: LocalPlayerService, level: number) { + service.setVolume(level); + return createActionResultFromHtmlDisplay(`

🔊 Volume: ${level}%

`); +} + +function handleChangeVolume(service: LocalPlayerService, amount: number) { + service.changeVolume(amount); + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

🔊 Volume: ${state.volume}%

`, + ); +} + +function handleMute(service: LocalPlayerService) { + service.mute(); + return createActionResultFromHtmlDisplay("

🔇 Muted

"); +} + +function handleUnmute(service: LocalPlayerService) { + service.unmute(); + return createActionResultFromHtmlDisplay("

🔊 Unmuted

"); +} + +function handleListFiles(service: LocalPlayerService, folderPath?: string) { + const files = service.listFiles(folderPath); + + if (files.length === 0) { + return createActionResultFromHtmlDisplay("

No audio files found

"); + } + + const fileListHtml = files + .slice(0, 20) + .map((f, i) => `
  • ${i + 1}. ${f.name}
  • `) + .join(""); + + let html = `

    📁 Audio Files (${files.length} total)

      ${fileListHtml}
    `; + if (files.length > 20) { + html += `

    ...and ${files.length - 20} more

    `; + } + + return createActionResultFromHtmlDisplay(html); +} + +function handleSearchFiles(service: LocalPlayerService, query: string) { + const files = service.searchFiles(query); + + if (files.length === 0) { + return createActionResultFromHtmlDisplay( + `

    No files found matching: ${query}

    `, + ); + } + + const fileListHtml = files + .slice(0, 20) + .map((f, i) => `
  • ${i + 1}. ${f.name}
  • `) + .join(""); + + let html = `

    🔍 Search Results for "${query}" (${files.length} found)

      ${fileListHtml}
    `; + + return createActionResultFromHtmlDisplay(html); +} + +function handleAddToQueue(service: LocalPlayerService, fileName: string) { + const success = service.addFileToQueue(fileName); + if (success) { + const state = service.getState(); + return createActionResultFromHtmlDisplay( + `

    ➕ Added to queue: ${fileName} (${state.queue.length} in queue)

    `, + ); + } + return createActionResultFromError(`Could not find: ${fileName}`); +} + +function handleClearQueue(service: LocalPlayerService) { + service.clearQueue(); + return createActionResultFromHtmlDisplay("

    🗑️ Queue cleared

    "); +} + +function handleShowQueue(service: LocalPlayerService) { + const queue = service.getQueue(); + const state = service.getState(); + + if (queue.length === 0) { + return createActionResultFromHtmlDisplay("

    Queue is empty

    "); + } + + const queueHtml = queue + .slice(0, 20) + .map((track, i) => { + const current = i === state.currentIndex ? " ▶️" : ""; + return `
  • ${i + 1}. ${track.name}${current}
  • `; + }) + .join(""); + + let html = `

    📋 Playback Queue (${queue.length} tracks)

      ${queueHtml}
    `; + if (queue.length > 20) { + html += `

    ...and ${queue.length - 20} more

    `; + } + + return createActionResultFromHtmlDisplay(html); +} + +function handleSetMusicFolder(service: LocalPlayerService, folderPath: string) { + const success = service.setMusicFolder(folderPath); + if (success) { + return createActionResultFromHtmlDisplay( + `

    📁 Music folder set to: ${folderPath}

    `, + ); + } + return createActionResultFromError(`Invalid folder path: ${folderPath}`); +} + +function handleShowMusicFolder(service: LocalPlayerService) { + const folder = service.getMusicFolder(); + return createActionResultFromHtmlDisplay( + `

    📁 Music folder: ${folder}

    `, + ); +} diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerManifest.json b/ts/packages/agents/playerLocal/src/agent/localPlayerManifest.json new file mode 100644 index 0000000000..ed06df7b62 --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerManifest.json @@ -0,0 +1,14 @@ +{ + "emojiChar": "🎵", + "description": "Agent to play local music files", + "schema": { + "description": "Local Music Player agent that lets you play and control local audio files.", + "schemaFile": "./localPlayerSchema.ts", + "compiledSchemaFile": "../../dist/agent/localPlayerSchema.pas.json", + "grammarFile": "../../dist/agent/localPlayerSchema.ag.json", + "schemaType": { + "action": "LocalPlayerActions", + "entity": "LocalPlayerEntities" + } + } +} diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.agr b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.agr new file mode 100644 index 0000000000..340d4fa78e --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.agr @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Grammar rules for Local Music Player + +@ = + | + | + | + | + | + | + | + | + | + | + | + +@ = pause ((the)? music)? -> { actionName: "pause" } + +@ = resume ((the)? music)? -> { actionName: "resume" } + +@ = stop ((the)? music)? -> { actionName: "stop" } + +@ = (next | skip) (track | song)? -> { actionName: "next" } + +@ = (previous | back | last) (track | song)? -> { actionName: "previous" } + +@ = (what('s | is) | show) (playing | status | now playing)? -> { actionName: "status" } + +@ = + +@ = + play (the)? $(n:) (track | song)? -> { + actionName: "playFromQueue", + parameters: { + trackNumber: $(n) + } + } +| play track $(n:) -> { + actionName: "playFromQueue", + parameters: { + trackNumber: $(n) + } + } +| play track #$(n:number) -> { + actionName: "playFromQueue", + parameters: { + trackNumber: $(n) + } + } + +@ = | | + +@ = (turn up | increase | raise) (the)? volume + -> { actionName: "changeVolume", parameters: { amount: 10 } } + +@ = (turn down | decrease | lower) (the)? volume + -> { actionName: "changeVolume", parameters: { amount: -10 } } + +@ = set volume (to)? $(n:number) (percent)? + -> { actionName: "setVolume", parameters: { level: $(n) } } + +@ = mute (the)? (music | sound | audio)? -> { actionName: "mute" } + +@ = unmute (the)? (music | sound | audio)? -> { actionName: "unmute" } + +@ = (show | list | display) (the)? queue -> { actionName: "showQueue" } + +@ = clear (the)? queue -> { actionName: "clearQueue" } + +@ = + $(x:number) +| one -> 1 +| two -> 2 +| three -> 3 +| four -> 4 +| five -> 5 +| six -> 6 +| seven -> 7 +| eight -> 8 +| nine -> 9 +| ten -> 10 + +@ = + first -> 1 +| second -> 2 +| third -> 3 +| fourth -> 4 +| fifth -> 5 +| sixth -> 6 +| seventh -> 7 +| eighth -> 8 +| ninth -> 9 +| tenth -> 10 diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.json b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.json new file mode 100644 index 0000000000..8c5a5664ae --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.json @@ -0,0 +1,6 @@ +{ + "schemaName": "LocalPlayerActions", + "fullSchemaType": "LocalPlayerActions", + "entityName": "LocalPlayerEntities", + "actionNamespace": "localPlayer" +} diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.ts b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.ts new file mode 100644 index 0000000000..d8dc078275 --- /dev/null +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerSchema.ts @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export type LocalPlayerActions = + | PlayFileAction + | PlayFolderAction + | PlayFromQueueAction + | StatusAction + | PauseAction + | ResumeAction + | StopAction + | NextAction + | PreviousAction + | ShuffleAction + | RepeatAction + | SetVolumeAction + | ChangeVolumeAction + | MuteAction + | UnmuteAction + | ListFilesAction + | SearchFilesAction + | AddToQueueAction + | ClearQueueAction + | ShowQueueAction + | SetMusicFolderAction + | ShowMusicFolderAction; + +export type LocalPlayerEntities = FilePath; +export type FilePath = string; + +// Play a specific audio file by path or name +export interface PlayFileAction { + actionName: "playFile"; + parameters: { + // File name or path to play (can be partial match) + fileName: string; + }; +} + +// Play all audio files in a folder +export interface PlayFolderAction { + actionName: "playFolder"; + parameters: { + // Folder path to play from + folderPath?: string; + // Whether to shuffle the tracks + shuffle?: boolean; + }; +} + +// Play a specific track from the current queue by index +export interface PlayFromQueueAction { + actionName: "playFromQueue"; + parameters: { + // 1-based index of the track in the queue + trackNumber: number; + }; +} + +// Show now playing status including track information and playback state +export interface StatusAction { + actionName: "status"; +} + +// Pause playback +export interface PauseAction { + actionName: "pause"; +} + +// Resume playback +export interface ResumeAction { + actionName: "resume"; +} + +// Stop playback completely +export interface StopAction { + actionName: "stop"; +} + +// Skip to next track +export interface NextAction { + actionName: "next"; +} + +// Go to previous track +export interface PreviousAction { + actionName: "previous"; +} + +// Turn shuffle on or off +export interface ShuffleAction { + actionName: "shuffle"; + parameters: { + on: boolean; + }; +} + +// Set repeat mode +export interface RepeatAction { + actionName: "repeat"; + parameters: { + // Repeat mode: "off", "one" (repeat current track), "all" (repeat queue) + mode: "off" | "one" | "all"; + }; +} + +// Set volume to a specific level (0-100) +export interface SetVolumeAction { + actionName: "setVolume"; + parameters: { + // New volume level (0-100) + level: number; + }; +} + +// Change volume by a relative amount +export interface ChangeVolumeAction { + actionName: "changeVolume"; + parameters: { + // Amount to change volume by (can be negative) + amount: number; + }; +} + +// Mute audio +export interface MuteAction { + actionName: "mute"; +} + +// Unmute audio +export interface UnmuteAction { + actionName: "unmute"; +} + +// List audio files in the music folder or a specified folder +export interface ListFilesAction { + actionName: "listFiles"; + parameters?: { + // Folder to list (defaults to music folder) + folderPath?: string; + }; +} + +// Search for audio files by name +export interface SearchFilesAction { + actionName: "searchFiles"; + parameters: { + // Search query to match against file names + query: string; + }; +} + +// Add a file or files to the playback queue +export interface AddToQueueAction { + actionName: "addToQueue"; + parameters: { + // File name or path to add to queue + fileName: string; + }; +} + +// Clear the playback queue +export interface ClearQueueAction { + actionName: "clearQueue"; +} + +// Show the current playback queue +export interface ShowQueueAction { + actionName: "showQueue"; +} + +// Set the default music folder path +export interface SetMusicFolderAction { + actionName: "setMusicFolder"; + parameters: { + // Path to the music folder + folderPath: FilePath; + }; +} + +// Show the current music folder path +export interface ShowMusicFolderAction { + actionName: "showMusicFolder"; +} diff --git a/ts/packages/agents/playerLocal/src/localPlayerService.ts b/ts/packages/agents/playerLocal/src/localPlayerService.ts new file mode 100644 index 0000000000..e1d28b5de4 --- /dev/null +++ b/ts/packages/agents/playerLocal/src/localPlayerService.ts @@ -0,0 +1,461 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; +import { spawn, ChildProcess } from "child_process"; +import registerDebug from "debug"; + +const debug = registerDebug("typeagent:localPlayer"); +const debugError = registerDebug("typeagent:localPlayer:error"); + +// Supported audio file extensions +const AUDIO_EXTENSIONS = [ + ".mp3", + ".wav", + ".ogg", + ".flac", + ".m4a", + ".aac", + ".wma", +]; + +export interface Track { + name: string; + path: string; + duration?: number; + artist?: string; + album?: string; +} + +export interface PlaybackState { + isPlaying: boolean; + isPaused: boolean; + currentTrack: Track | null; + currentIndex: number; + volume: number; + isMuted: boolean; + shuffle: boolean; + repeat: "off" | "one" | "all"; + queue: Track[]; +} + +export class LocalPlayerService { + private state: PlaybackState; + private musicFolder: string; + private playerProcess: ChildProcess | null = null; + + constructor() { + // Default music folder + this.musicFolder = path.join(os.homedir(), "Music"); + + this.state = { + isPlaying: false, + isPaused: false, + currentTrack: null, + currentIndex: -1, + volume: 50, + isMuted: false, + shuffle: false, + repeat: "off", + queue: [], + }; + } + + public getMusicFolder(): string { + return this.musicFolder; + } + + public setMusicFolder(folderPath: string): boolean { + if ( + fs.existsSync(folderPath) && + fs.statSync(folderPath).isDirectory() + ) { + this.musicFolder = folderPath; + debug(`Music folder set to: ${folderPath}`); + return true; + } + debugError(`Invalid folder path: ${folderPath}`); + return false; + } + + public getState(): PlaybackState { + return { ...this.state }; + } + + public listFiles(folderPath?: string): Track[] { + const folder = folderPath || this.musicFolder; + const tracks: Track[] = []; + + try { + if (!fs.existsSync(folder)) { + debug(`Folder does not exist: ${folder}`); + return tracks; + } + + const files = fs.readdirSync(folder); + for (const file of files) { + const ext = path.extname(file).toLowerCase(); + if (AUDIO_EXTENSIONS.includes(ext)) { + tracks.push({ + name: path.basename(file, ext), + path: path.join(folder, file), + }); + } + } + } catch (error) { + debugError(`Error listing files: ${error}`); + } + + return tracks; + } + + public searchFiles(query: string): Track[] { + const allFiles = this.listFilesRecursive(this.musicFolder); + const lowerQuery = query.toLowerCase(); + + return allFiles.filter((track) => + track.name.toLowerCase().includes(lowerQuery), + ); + } + + private listFilesRecursive( + folder: string, + maxDepth: number = 3, + currentDepth: number = 0, + ): Track[] { + const tracks: Track[] = []; + + if (currentDepth >= maxDepth) { + return tracks; + } + + try { + const entries = fs.readdirSync(folder, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(folder, entry.name); + + if (entry.isDirectory()) { + tracks.push( + ...this.listFilesRecursive( + fullPath, + maxDepth, + currentDepth + 1, + ), + ); + } else if (entry.isFile()) { + const ext = path.extname(entry.name).toLowerCase(); + if (AUDIO_EXTENSIONS.includes(ext)) { + tracks.push({ + name: path.basename(entry.name, ext), + path: fullPath, + }); + } + } + } + } catch (error) { + debugError(`Error reading folder ${folder}: ${error}`); + } + + return tracks; + } + + public async playFile(fileName: string): Promise { + // Search for the file + const tracks = this.searchFiles(fileName); + + if (tracks.length === 0) { + debugError(`No files found matching: ${fileName}`); + return false; + } + + // Play the first match + return this.playTrack(tracks[0]); + } + + public async playTrack(track: Track): Promise { + // Stop any current playback + this.stop(); + + try { + debug(`Playing: ${track.path}`); + + if (process.platform === "win32") { + // Use Windows Media Player via PowerShell + this.playerProcess = spawn( + "powershell", + [ + "-Command", + "& { Add-Type -AssemblyName presentationCore; $player = New-Object System.Windows.Media.MediaPlayer; $player.Open($args[0]); $player.Volume = [double]$args[1]; $player.Play(); Start-Sleep -Seconds 3600 }", + track.path, + String(this.state.volume / 100), + ], + { stdio: "ignore" }, + ); + } else if (process.platform === "darwin") { + // macOS: use afplay + this.playerProcess = spawn("afplay", [track.path], { + stdio: "ignore", + }); + } else { + // Linux: use mpv or similar + this.playerProcess = spawn("mpv", ["--no-video", track.path], { + stdio: "ignore", + }); + } + + this.playerProcess.on("error", (error) => { + debugError(`Player error: ${error}`); + }); + + this.playerProcess.on("exit", () => { + debug("Playback ended"); + this.handlePlaybackEnd(); + }); + + this.state.isPlaying = true; + this.state.isPaused = false; + this.state.currentTrack = track; + + // Find index in queue + const index = this.state.queue.findIndex( + (t) => t.path === track.path, + ); + if (index >= 0) { + this.state.currentIndex = index; + } + + return true; + } catch (error) { + debugError(`Error playing track: ${error}`); + return false; + } + } + + private handlePlaybackEnd(): void { + this.state.isPlaying = false; + + if (this.state.repeat === "one" && this.state.currentTrack) { + // Repeat current track + this.playTrack(this.state.currentTrack); + } else if ( + this.state.queue.length > 0 && + this.state.currentIndex < this.state.queue.length - 1 + ) { + // Play next in queue + this.next(); + } else if (this.state.repeat === "all" && this.state.queue.length > 0) { + // Repeat all - go back to start + this.state.currentIndex = 0; + this.playTrack(this.state.queue[0]); + } + } + + public pause(): boolean { + if (this.playerProcess && this.state.isPlaying) { + // Note: Simple pause isn't supported by all players + // For a real implementation, use a library with better control + if (process.platform === "win32") { + // Send Ctrl+C to pause (not ideal) + this.playerProcess.kill("SIGSTOP"); + } + this.state.isPaused = true; + this.state.isPlaying = false; + debug("Paused"); + return true; + } + return false; + } + + public resume(): boolean { + if (this.state.isPaused && this.state.currentTrack) { + if (!this.playerProcess) { + // No active player process; restart playback + this.playTrack(this.state.currentTrack); + } else if (process.platform !== "win32") { + // On non-Windows platforms, attempt to continue the existing process + this.playerProcess.kill("SIGCONT"); + } else { + // On Windows, SIGCONT isn't supported; restart playback instead + this.playTrack(this.state.currentTrack); + } + this.state.isPaused = false; + this.state.isPlaying = true; + debug("Resumed"); + return true; + } + return false; + } + + public stop(): boolean { + if (this.playerProcess) { + this.playerProcess.kill(); + this.playerProcess = null; + } + this.state.isPlaying = false; + this.state.isPaused = false; + debug("Stopped"); + return true; + } + + public async next(): Promise { + if (this.state.queue.length === 0) { + return false; + } + + let nextIndex = this.state.currentIndex + 1; + + if (this.state.shuffle) { + const queueLength = this.state.queue.length; + if (queueLength > 1) { + let randomIndex: number; + do { + randomIndex = Math.floor(Math.random() * queueLength); + } while (randomIndex === this.state.currentIndex); + nextIndex = randomIndex; + } else { + // Only one track in the queue; keep index at 0 + nextIndex = 0; + } + } + + if (nextIndex >= this.state.queue.length) { + if (this.state.repeat === "all") { + nextIndex = 0; + } else { + return false; + } + } + + this.state.currentIndex = nextIndex; + return await this.playTrack(this.state.queue[nextIndex]); + } + + public async previous(): Promise { + if (this.state.queue.length === 0) { + return false; + } + + let prevIndex = this.state.currentIndex - 1; + + if (prevIndex < 0) { + if (this.state.repeat === "all") { + prevIndex = this.state.queue.length - 1; + } else { + prevIndex = 0; + } + } + + this.state.currentIndex = prevIndex; + return await this.playTrack(this.state.queue[prevIndex]); + } + + public setVolume(level: number): boolean { + this.state.volume = Math.max(0, Math.min(100, level)); + debug(`Volume set to: ${this.state.volume}`); + // Note: Changing volume during playback requires player-specific implementation + return true; + } + + public changeVolume(amount: number): boolean { + return this.setVolume(this.state.volume + amount); + } + + public mute(): boolean { + this.state.isMuted = true; + debug("Muted"); + return true; + } + + public unmute(): boolean { + this.state.isMuted = false; + debug("Unmuted"); + return true; + } + + public setShuffle(on: boolean): boolean { + this.state.shuffle = on; + debug(`Shuffle: ${on}`); + return true; + } + + public setRepeat(mode: "off" | "one" | "all"): boolean { + this.state.repeat = mode; + debug(`Repeat mode: ${mode}`); + return true; + } + + public addToQueue(track: Track): boolean { + this.state.queue.push(track); + debug(`Added to queue: ${track.name}`); + return true; + } + + public addFileToQueue(fileName: string): boolean { + const tracks = this.searchFiles(fileName); + if (tracks.length > 0) { + return this.addToQueue(tracks[0]); + } + return false; + } + + public clearQueue(): boolean { + this.state.queue = []; + this.state.currentIndex = -1; + debug("Queue cleared"); + return true; + } + + public getQueue(): Track[] { + return [...this.state.queue]; + } + + public async playFromQueue(index: number): Promise { + // Convert from 1-based to 0-based index + const zeroIndex = index - 1; + + if (zeroIndex >= 0 && zeroIndex < this.state.queue.length) { + this.state.currentIndex = zeroIndex; + return await this.playTrack(this.state.queue[zeroIndex]); + } + return false; + } + + public async playFolder( + folderPath?: string, + shuffle: boolean = false, + ): Promise { + const tracks = this.listFiles(folderPath); + + if (tracks.length === 0) { + return false; + } + + this.state.queue = shuffle ? this.shuffleArray(tracks) : tracks; + this.state.shuffle = shuffle; + this.state.currentIndex = 0; + + return await this.playTrack(this.state.queue[0]); + } + + private shuffleArray(array: T[]): T[] { + const shuffled = [...array]; + for (let i = shuffled.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; + } + return shuffled; + } +} + +// Singleton instance +let playerInstance: LocalPlayerService | null = null; + +export function getLocalPlayerService(): LocalPlayerService { + if (!playerInstance) { + playerInstance = new LocalPlayerService(); + } + return playerInstance; +} diff --git a/ts/packages/agents/playerLocal/src/tsconfig.json b/ts/packages/agents/playerLocal/src/tsconfig.json new file mode 100644 index 0000000000..2ea48741dc --- /dev/null +++ b/ts/packages/agents/playerLocal/src/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "rootDir": ".", + "outDir": "../dist", + "composite": true + }, + "include": ["./**/*.ts", "./**/*.mts", "./**/*.json"], + "exclude": ["../dist/**/*", "../node_modules/**/*"], + "extends": "../../../../tsconfig.base.json" +} diff --git a/ts/packages/cache/package.json b/ts/packages/cache/package.json index 08800e8e7f..1e460da9c8 100644 --- a/ts/packages/cache/package.json +++ b/ts/packages/cache/package.json @@ -55,6 +55,7 @@ "jest": "^29.7.0", "prettier": "^3.5.3", "rimraf": "^6.0.1", + "test-lib": "workspace:*", "typescript": "~5.4.5" } } diff --git a/ts/packages/cache/test/grammarIntegration.spec.ts b/ts/packages/cache/test/grammarIntegration.spec.ts index e0f47c0601..3ffb6714e8 100644 --- a/ts/packages/cache/test/grammarIntegration.spec.ts +++ b/ts/packages/cache/test/grammarIntegration.spec.ts @@ -22,6 +22,7 @@ import { compileGrammarToNFA, loadGrammarRules, } from "action-grammar"; +import { describeIf, hasTestKeys } from "test-lib"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -841,172 +842,186 @@ describe("Grammar Integration", () => { }); }); - describe("Grammar Generation via populateCache", () => { - it("should generate and add grammar rules from request/action pairs", async () => { - const staticGrammarText = `@ = + describeIf( + "Grammar Generation via populateCache", + () => hasTestKeys(), + () => { + it("should generate and add grammar rules from request/action pairs", async () => { + const staticGrammarText = `@ = @ = play $(track:string) -> { actionName: "play", parameters: { track: $(track) } }`; - const grammar = loadGrammarRules("player", staticGrammarText, []); - const cache = new AgentCache( - "test", - mockExplainerFactory, - undefined, - ); - const agentGrammarRegistry = new AgentGrammarRegistry(); - const persistedStore = new PersistedGrammarStore(); - - await persistedStore.newStore(grammarStoreFile); + const grammar = loadGrammarRules( + "player", + staticGrammarText, + [], + ); + const cache = new AgentCache( + "test", + mockExplainerFactory, + undefined, + ); + const agentGrammarRegistry = new AgentGrammarRegistry(); + const persistedStore = new PersistedGrammarStore(); - cache.grammarStore.addGrammar("player", grammar!); - agentGrammarRegistry.registerAgent( - "player", - grammar!, - compileGrammarToNFA(grammar!, "player"), - ); + await persistedStore.newStore(grammarStoreFile); - // Use real player schema file from the agents package - const playerSchemaPath = path.join( - __dirname, - "../../../agents/player/dist/agent/playerSchema.pas.json", - ); + cache.grammarStore.addGrammar("player", grammar!); + agentGrammarRegistry.registerAgent( + "player", + grammar!, + compileGrammarToNFA(grammar!, "player"), + ); - // Verify schema file exists - if (!fs.existsSync(playerSchemaPath)) { - console.log( - `⚠ Player schema not found at ${playerSchemaPath}`, + // Use real player schema file from the agents package + const playerSchemaPath = path.join( + __dirname, + "../../../agents/player/dist/agent/playerSchema.pas.json", ); - console.log( - "Run 'npm run build' in packages/agents/player to generate the schema", + + // Verify schema file exists + if (!fs.existsSync(playerSchemaPath)) { + console.log( + `⚠ Player schema not found at ${playerSchemaPath}`, + ); + console.log( + "Run 'npm run build' in packages/agents/player to generate the schema", + ); + return; // Skip test if schema not built + } + + console.log(`Using player schema: ${playerSchemaPath}`); + + // Configure with schema path getter + cache.configureGrammarGeneration( + agentGrammarRegistry, + persistedStore, + true, + (schemaName: string) => playerSchemaPath, ); - return; // Skip test if schema not built - } - console.log(`Using player schema: ${playerSchemaPath}`); + const namespaceKeys = cache.getNamespaceKeys( + ["player"], + undefined, + ); - // Configure with schema path getter - cache.configureGrammarGeneration( - agentGrammarRegistry, - persistedStore, - true, - (schemaName: string) => playerSchemaPath, - ); + // Before generation, "pause" should not match + const matchesBefore = cache.match("pause", { namespaceKeys }); + expect(matchesBefore.length).toBe(0); - const namespaceKeys = cache.getNamespaceKeys(["player"], undefined); + // Import populateCache dynamically + const { populateCache } = await import( + "action-grammar/generation" + ); - // Before generation, "pause" should not match - const matchesBefore = cache.match("pause", { namespaceKeys }); - expect(matchesBefore.length).toBe(0); - - // Import populateCache dynamically - const { populateCache } = await import("action-grammar/generation"); - - try { - // Generate grammar rule for a new action - const genResult = await populateCache({ - request: "pause", - schemaName: "player", - action: { - actionName: "pause", - parameters: {}, - }, - schemaPath: playerSchemaPath, - }); - - console.log("populateCache result:", genResult); - - // If generation succeeded, add the rule - if (genResult.success && genResult.generatedRule) { - await persistedStore.addRule({ + try { + // Generate grammar rule for a new action + const genResult = await populateCache({ + request: "pause", schemaName: "player", - grammarText: genResult.generatedRule, + action: { + actionName: "pause", + parameters: {}, + }, + schemaPath: playerSchemaPath, }); - const agentGrammar = - agentGrammarRegistry.getAgent("player"); - const addResult = agentGrammar!.addGeneratedRules( - genResult.generatedRule, - ); - console.log( - "addGeneratedRules result:", - JSON.stringify(addResult, null, 2), - ); - if (!addResult.success) { + console.log("populateCache result:", genResult); + + // If generation succeeded, add the rule + if (genResult.success && genResult.generatedRule) { + await persistedStore.addRule({ + schemaName: "player", + grammarText: genResult.generatedRule, + }); + + const agentGrammar = + agentGrammarRegistry.getAgent("player"); + const addResult = agentGrammar!.addGeneratedRules( + genResult.generatedRule, + ); console.log( - "Failed to add generated rules - this may be expected if the rule format is invalid", + "addGeneratedRules result:", + JSON.stringify(addResult, null, 2), ); - // Don't fail test if rule addition fails - focus on validating the generation worked + if (!addResult.success) { + console.log( + "Failed to add generated rules - this may be expected if the rule format is invalid", + ); + // Don't fail test if rule addition fails - focus on validating the generation worked + } else { + console.log( + "✓ Successfully added generated rule to agent grammar", + ); + + cache.syncAgentGrammar("player"); + + // After generation, "pause" should match + const matchesAfter = cache.match("pause", { + namespaceKeys, + }); + expect(matchesAfter.length).toBeGreaterThan(0); + expect( + matchesAfter[0].match.actions[0].action + .actionName, + ).toBe("pause"); + + console.log( + "✓ Grammar generation and integration successful", + ); + } } else { console.log( - "✓ Successfully added generated rule to agent grammar", + "Grammar generation was rejected:", + genResult.rejectionReason, ); + // Don't fail test if generation was legitimately rejected + } + } catch (error: any) { + // Let all errors throw so tests fail properly + console.error("Grammar generation error:", error.message); + throw error; + } + }, 180000); // 3 minute timeout for API call (LLM can be slow) - cache.syncAgentGrammar("player"); + it("should handle grammar generation errors gracefully", async () => { + // Test that populateCache handles errors gracefully (e.g., invalid schema path) + try { + const { populateCache } = await import( + "action-grammar/generation" + ); - // After generation, "pause" should match - const matchesAfter = cache.match("pause", { - namespaceKeys, - }); - expect(matchesAfter.length).toBeGreaterThan(0); - expect( - matchesAfter[0].match.actions[0].action.actionName, - ).toBe("pause"); + const result = await populateCache({ + request: "test request", + schemaName: "test", + action: { + actionName: "testAction", + parameters: {}, + }, + schemaPath: "/nonexistent/mock/path.pas.json", // Invalid path + }); - console.log( - "✓ Grammar generation and integration successful", - ); - } - } else { + // Should fail gracefully with invalid schema path + expect(result.success).toBe(false); + expect(result.rejectionReason).toBeDefined(); + console.log( + "Handled error gracefully:", + result.rejectionReason, + ); + } catch (error: any) { + // Error during file reading is expected and acceptable console.log( - "Grammar generation was rejected:", - genResult.rejectionReason, + "Expected error for invalid schema path:", + error.message, ); - // Don't fail test if generation was legitimately rejected + expect(error).toBeDefined(); } - } catch (error: any) { - // Let all errors throw so tests fail properly - console.error("Grammar generation error:", error.message); - throw error; - } - }, 60000); // 60 second timeout for API call - - it("should handle grammar generation errors gracefully", async () => { - // Test that populateCache handles errors gracefully (e.g., invalid schema path) - try { - const { populateCache } = await import( - "action-grammar/generation" - ); - - const result = await populateCache({ - request: "test request", - schemaName: "test", - action: { - actionName: "testAction", - parameters: {}, - }, - schemaPath: "/nonexistent/mock/path.pas.json", // Invalid path - }); - - // Should fail gracefully with invalid schema path - expect(result.success).toBe(false); - expect(result.rejectionReason).toBeDefined(); - console.log( - "Handled error gracefully:", - result.rejectionReason, - ); - } catch (error: any) { - // Error during file reading is expected and acceptable - console.log( - "Expected error for invalid schema path:", - error.message, - ); - expect(error).toBeDefined(); - } - }); - }); + }); + }, + ); describe("Grammar Merging - Comprehensive Tests", () => { it("should handle multi-token sequences correctly after merging", () => { diff --git a/ts/packages/defaultAgentProvider/data/config.json b/ts/packages/defaultAgentProvider/data/config.json index ad74901c54..e5f199f31f 100644 --- a/ts/packages/defaultAgentProvider/data/config.json +++ b/ts/packages/defaultAgentProvider/data/config.json @@ -3,6 +3,9 @@ "player": { "name": "music" }, + "localPlayer": { + "name": "music-local" + }, "calendar": { "name": "calendar" }, diff --git a/ts/packages/defaultAgentProvider/package.json b/ts/packages/defaultAgentProvider/package.json index e6f6745b84..f9c1ec04a6 100644 --- a/ts/packages/defaultAgentProvider/package.json +++ b/ts/packages/defaultAgentProvider/package.json @@ -64,6 +64,7 @@ "markdown-agent": "workspace:*", "montage-agent": "workspace:*", "music": "workspace:*", + "music-local": "workspace:*", "oracle-agent": "workspace:*", "photo-agent": "workspace:*", "proper-lockfile": "^4.1.2", diff --git a/ts/packages/mcp/thoughts/package.json b/ts/packages/mcp/thoughts/package.json index 419ed6c667..4f414e3bcb 100644 --- a/ts/packages/mcp/thoughts/package.json +++ b/ts/packages/mcp/thoughts/package.json @@ -42,6 +42,7 @@ }, "devDependencies": { "@types/node": "^20.11.19", + "rimraf": "^6.0.1", "typescript": "^5.5.4" } } diff --git a/ts/packages/testLib/src/file.ts b/ts/packages/testLib/src/file.ts index 4cb854f123..363905de54 100644 --- a/ts/packages/testLib/src/file.ts +++ b/ts/packages/testLib/src/file.ts @@ -48,7 +48,7 @@ export function readTestJsonFile(filePath: string): any { export function ensureDir(folderPath: string): string { if (!fs.existsSync(folderPath)) { - fs.promises.mkdir(folderPath, { recursive: true }); + fs.mkdirSync(folderPath, { recursive: true }); } return folderPath; } diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 15dc5efb0f..8d8e63e736 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: markdown-link-check: specifier: ^3.14.2 version: 3.14.2 + prebuild-install: + specifier: ^7.1.3 + version: 7.1.3 prettier: specifier: ^3.5.3 version: 3.5.3 @@ -475,8 +478,8 @@ importers: examples/memoryProviders: dependencies: '@elastic/elasticsearch': - specifier: ^8.17.0 - version: 8.18.2 + specifier: ^8.19.1 + version: 8.19.1 aiclient: specifier: workspace:* version: link:../../packages/aiclient @@ -513,7 +516,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -540,7 +543,7 @@ importers: version: 23.11.1(typescript@5.4.5) ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) + version: 10.9.2(@types/node@24.10.11)(typescript@5.4.5) xml2js: specifier: ^0.6.2 version: 0.6.2 @@ -726,7 +729,7 @@ importers: version: 12.0.2(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -864,7 +867,7 @@ importers: version: 12.0.2(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -922,7 +925,7 @@ importers: version: 2.0.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -969,7 +972,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1013,7 +1016,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1041,7 +1044,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1280,7 +1283,7 @@ importers: version: 2.4.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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2002,7 +2005,7 @@ importers: version: 2.4.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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2017,7 +2020,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) + version: 6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) packages/agents/montage: dependencies: @@ -2093,7 +2096,7 @@ importers: version: 12.0.2(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -2200,6 +2203,43 @@ importers: specifier: ~5.4.5 version: 5.4.5 + packages/agents/playerLocal: + dependencies: + '@typeagent/agent-sdk': + specifier: workspace:* + version: link:../../agentSdk + '@typeagent/common-utils': + specifier: workspace:* + version: link:../../utils/commonUtils + chalk: + specifier: ^5.4.1 + version: 5.6.2 + debug: + specifier: ^4.4.0 + version: 4.4.3(supports-color@8.1.1) + play-sound: + specifier: ^1.1.6 + version: 1.1.6 + devDependencies: + '@typeagent/action-schema-compiler': + specifier: workspace:* + version: link:../../actionSchemaCompiler + '@types/debug': + specifier: ^4.1.12 + version: 4.1.12 + action-grammar-compiler: + specifier: workspace:* + version: link:../../actionGrammarCompiler + concurrently: + specifier: ^9.1.2 + version: 9.1.2 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: ~5.4.5 + version: 5.4.5 + packages/agents/settings: dependencies: '@typeagent/agent-sdk': @@ -2334,7 +2374,7 @@ importers: version: 12.0.2(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2436,7 +2476,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -2521,7 +2561,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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2582,7 +2622,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -2643,13 +2683,16 @@ importers: version: 2.0.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 rimraf: specifier: ^6.0.1 version: 6.0.1 + test-lib: + specifier: workspace:* + version: link:../testLib typescript: specifier: ~5.4.5 version: 5.4.5 @@ -2680,7 +2723,7 @@ importers: version: 5.6.3(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2767,7 +2810,7 @@ importers: version: 10.1.2 ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) + version: 10.9.2(@types/node@24.10.11)(typescript@5.4.5) typechat: specifier: ^0.1.1 version: 0.1.1(typescript@5.4.5)(zod@3.25.76) @@ -2786,7 +2829,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2958,7 +3001,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -2967,7 +3010,7 @@ importers: version: 5.0.10 ts-jest: specifier: ^29.1.2 - version: 29.3.3(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(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.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(jest@29.7.0(@types/node@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)))(typescript@5.4.5) typescript: specifier: ~5.4.5 version: 5.4.5 @@ -3064,6 +3107,9 @@ importers: music: specifier: workspace:* version: link:../agents/player + music-local: + specifier: workspace:* + version: link:../agents/playerLocal oracle-agent: specifier: workspace:* version: link:../agents/oracle @@ -3127,7 +3173,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3263,7 +3309,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3303,7 +3349,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3409,7 +3455,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3504,7 +3550,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3544,7 +3590,7 @@ importers: version: 12.0.2(webpack@5.105.0) 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3588,6 +3634,9 @@ importers: '@types/node': specifier: ^20.11.19 version: 20.19.25 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 typescript: specifier: ^5.5.4 version: 5.9.3 @@ -3651,7 +3700,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3767,7 +3816,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -3858,7 +3907,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3980,7 +4029,7 @@ importers: devDependencies: '@electron-toolkit/tsconfig': specifier: ^1.0.1 - version: 1.0.1(@types/node@22.15.18) + version: 1.0.1(@types/node@24.10.11) '@fontsource/lato': specifier: ^5.2.5 version: 5.2.5 @@ -4004,7 +4053,7 @@ importers: version: 26.3.1(electron-builder-squirrel-windows@26.3.1) electron-vite: specifier: ^4.0.1 - version: 4.0.1(vite@6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0)) + version: 4.0.1(vite@6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0)) less: specifier: ^4.2.0 version: 4.3.0 @@ -4022,7 +4071,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.1 - version: 6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) + version: 6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) packages/telemetry: dependencies: @@ -4053,7 +4102,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4084,7 +4133,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4152,7 +4201,7 @@ importers: version: 16.5.0 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4180,7 +4229,7 @@ importers: version: 0.25.11 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4211,7 +4260,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4257,7 +4306,7 @@ importers: version: 29.5.14 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -5023,12 +5072,12 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@elastic/elasticsearch@8.18.2': - resolution: {integrity: sha512-2pOc/hGdxkbaDavfAlnUfjJdVsFRCGqg7fpsWJfJ2UzpgViIyojdViHg8zOCT1J14lAwvDgb9CNETWa3SBZRfw==} + '@elastic/elasticsearch@8.19.1': + resolution: {integrity: sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==} engines: {node: '>=18'} - '@elastic/transport@8.9.6': - resolution: {integrity: sha512-v71jgmZtgPg2ouXF5KTPxU1a6z7YYc8nazAS7jLySteC/vrShs1OJh6oEEeo5oDc19MYUofV/JV1h5vqJVBXOw==} + '@elastic/transport@8.10.1': + resolution: {integrity: sha512-xo2lPBAJEt81fQRAKa9T/gUq1SPGBHpSnVUXhoSpL996fPZRAfQwFA4BZtEUQL1p8Dezodd3ZN8Wwno+mYyKuw==} engines: {node: '>=18'} '@electron-toolkit/preload@3.0.2': @@ -6726,8 +6775,8 @@ packages: resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==} engines: {node: '>=18.0.0'} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.18': + resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} @@ -7112,6 +7161,9 @@ packages: '@types/node@22.15.18': resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==} + '@types/node@24.10.11': + resolution: {integrity: sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -7580,8 +7632,8 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - apache-arrow@18.1.0: - resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==} + apache-arrow@21.1.0: + resolution: {integrity: sha512-kQrYLxhC+NTVVZ4CCzGF6L/uPVOzJmD1T3XgbiUnP7oTeVFOFgEUu6IKNwCDkpFoBVqDKQivlX4RUFqqnWFlEA==} hasBin: true app-builder-bin@5.0.0-alpha.12: @@ -7623,10 +7675,6 @@ packages: resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==} engines: {node: '>=0.10.0'} - array-back@3.1.0: - resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} - engines: {node: '>=6'} - array-back@6.2.2: resolution: {integrity: sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==} engines: {node: '>=12.17'} @@ -8193,9 +8241,14 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - command-line-args@5.2.1: - resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} - engines: {node: '>=4.0.0'} + command-line-args@6.0.1: + resolution: {integrity: sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==} + engines: {node: '>=12.20'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true command-line-usage@7.0.3: resolution: {integrity: sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==} @@ -9283,9 +9336,17 @@ packages: resolution: {integrity: sha512-Z+suHH+7LSE40WfUeZPIxSxypCWvrzdVc60xAjUShZeT5eMWM0/FQUduq3HjluyfAHWvC/aOBkT1pTZktyF/jg==} engines: {node: '>= 0.12'} - find-replace@3.0.0: - resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} - engines: {node: '>=4.0.0'} + find-exec@1.0.3: + resolution: {integrity: sha512-gnG38zW90mS8hm5smNcrBnakPEt+cGJoiMkJwCU0IYnEb0H2NQk0NIljhNW+48oniCriFek/PH6QXbwsJo/qug==} + + find-replace@5.0.2: + resolution: {integrity: sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==} + engines: {node: '>=14'} + peerDependencies: + '@75lb/nature': latest + peerDependenciesMeta: + '@75lb/nature': + optional: true find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -9303,8 +9364,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatbuffers@24.3.25: - resolution: {integrity: sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ==} + flatbuffers@25.9.23: + resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} follow-redirects@1.15.9: resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} @@ -11742,6 +11803,9 @@ packages: pkg-types@2.2.0: resolution: {integrity: sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==} + play-sound@1.1.6: + resolution: {integrity: sha512-09eO4QiXNFXJffJaOW5P6x6F5RLihpLUkXttvUZeWml0fU6x6Zp7AjG9zaeMpgH2ZNvq4GR1ytB22ddYcqJIZA==} + playwright-core@1.57.0: resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} engines: {node: '>=18'} @@ -13150,10 +13214,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typical@4.0.0: - resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} - engines: {node: '>=8'} - typical@7.3.0: resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} engines: {node: '>=12.17'} @@ -13183,8 +13243,11 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@6.21.3: - resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} undici@7.11.0: @@ -13595,8 +13658,8 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - wordwrapjs@5.1.0: - resolution: {integrity: sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==} + wordwrapjs@5.1.1: + resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==} engines: {node: '>=12.17'} workerpool@6.5.1: @@ -15215,23 +15278,25 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@elastic/elasticsearch@8.18.2': + '@elastic/elasticsearch@8.19.1': dependencies: - '@elastic/transport': 8.9.6 - apache-arrow: 18.1.0 - tslib: 2.6.2 + '@elastic/transport': 8.10.1 + apache-arrow: 21.1.0 + tslib: 2.8.1 transitivePeerDependencies: + - '@75lb/nature' - supports-color - '@elastic/transport@8.9.6': + '@elastic/transport@8.10.1': dependencies: '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.0.0(@opentelemetry/api@1.9.0) debug: 4.4.3(supports-color@8.1.1) hpagent: 1.2.0 ms: 2.1.3 secure-json-parse: 3.0.2 tslib: 2.8.1 - undici: 6.21.3 + undici: 6.23.0 transitivePeerDependencies: - supports-color @@ -15239,9 +15304,9 @@ snapshots: dependencies: electron: 37.4.0 - '@electron-toolkit/tsconfig@1.0.1(@types/node@22.15.18)': + '@electron-toolkit/tsconfig@1.0.1(@types/node@24.10.11)': dependencies: - '@types/node': 22.15.18 + '@types/node': 24.10.11 '@electron/asar@3.4.1': dependencies: @@ -15892,7 +15957,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@24.10.11)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -15906,7 +15971,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -17509,7 +17574,7 @@ snapshots: '@smithy/types': 4.2.0 tslib: 2.8.1 - '@swc/helpers@0.5.15': + '@swc/helpers@0.5.18': dependencies: tslib: 2.8.1 @@ -17997,6 +18062,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@24.10.11': + dependencies: + undici-types: 7.16.0 + '@types/normalize-package-data@2.4.4': {} '@types/plist@3.0.5': @@ -18523,17 +18592,19 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - apache-arrow@18.1.0: + apache-arrow@21.1.0: dependencies: - '@swc/helpers': 0.5.15 + '@swc/helpers': 0.5.18 '@types/command-line-args': 5.2.3 '@types/command-line-usage': 5.0.4 - '@types/node': 20.19.25 - command-line-args: 5.2.1 + '@types/node': 24.10.11 + command-line-args: 6.0.1 command-line-usage: 7.0.3 - flatbuffers: 24.3.25 + flatbuffers: 25.9.23 json-bignum: 0.0.3 tslib: 2.8.1 + transitivePeerDependencies: + - '@75lb/nature' app-builder-bin@5.0.0-alpha.12: {} @@ -18631,8 +18702,6 @@ snapshots: arr-union@3.1.0: {} - array-back@3.1.0: {} - array-back@6.2.2: {} array-buffer-byte-length@1.0.2: @@ -19311,12 +19380,12 @@ snapshots: dependencies: delayed-stream: 1.0.0 - command-line-args@5.2.1: + command-line-args@6.0.1: dependencies: - array-back: 3.1.0 - find-replace: 3.0.0 + array-back: 6.2.2 + find-replace: 5.0.2 lodash.camelcase: 4.3.0 - typical: 4.0.0 + typical: 7.3.0 command-line-usage@7.0.3: dependencies: @@ -19522,13 +19591,13 @@ snapshots: - 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -20132,7 +20201,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@4.0.1(vite@6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0)): + electron-vite@4.0.1(vite@6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0)): dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4) @@ -20140,7 +20209,7 @@ snapshots: esbuild: 0.25.11 magic-string: 0.30.17 picocolors: 1.1.1 - vite: 6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) + vite: 6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0) transitivePeerDependencies: - supports-color @@ -20671,9 +20740,11 @@ snapshots: dependencies: user-home: 2.0.0 - find-replace@3.0.0: + find-exec@1.0.3: dependencies: - array-back: 3.1.0 + shell-quote: 1.8.1 + + find-replace@5.0.2: {} find-up@4.1.0: dependencies: @@ -20693,7 +20764,7 @@ snapshots: flat@5.0.2: {} - flatbuffers@24.3.25: {} + flatbuffers@25.9.23: {} follow-redirects@1.15.9(debug@4.4.1): optionalDependencies: @@ -21774,16 +21845,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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(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(ts-node@10.9.2(@types/node@24.10.11)(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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(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-config: 29.7.0(@types/node@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -21886,7 +21957,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.19.25)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)): dependencies: '@babel/core': 7.28.4 '@jest/test-sequencer': 29.7.0 @@ -21912,12 +21983,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.25 - ts-node: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@24.10.11)(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)): + jest-config@29.7.0(@types/node@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)): dependencies: '@babel/core': 7.28.4 '@jest/test-sequencer': 29.7.0 @@ -21942,8 +22013,8 @@ snapshots: 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) + '@types/node': 24.10.11 + ts-node: 10.9.2(@types/node@24.10.11)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -22208,12 +22279,12 @@ snapshots: - 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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(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(ts-node@10.9.2(@types/node@24.10.11)(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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -23394,7 +23465,7 @@ snapshots: node-abi@3.77.0: dependencies: - semver: 7.7.2 + semver: 7.7.3 node-abi@4.24.0: dependencies: @@ -23871,6 +23942,10 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 + play-sound@1.1.6: + dependencies: + find-exec: 1.0.3 + playwright-core@1.57.0: {} playwright@1.57.0: @@ -25228,7 +25303,7 @@ snapshots: table-layout@4.1.1: dependencies: array-back: 6.2.2 - wordwrapjs: 5.1.0 + wordwrapjs: 5.1.1 table@6.9.0: dependencies: @@ -25497,12 +25572,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.28.4) esbuild: 0.25.11 - ts-jest@29.3.3(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(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.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(jest@29.7.0(@types/node@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(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@24.10.11)(ts-node@10.9.2(@types/node@24.10.11)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -25575,14 +25650,14 @@ snapshots: 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@24.10.11)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.15.18 + '@types/node': 24.10.11 acorn: 8.11.1 acorn-walk: 8.3.0 arg: 4.1.3 @@ -25682,8 +25757,6 @@ snapshots: typescript@5.9.3: {} - typical@4.0.0: {} - typical@7.3.0: {} uc.micro@2.1.0: {} @@ -25710,7 +25783,9 @@ snapshots: undici-types@6.21.0: {} - undici@6.21.3: {} + undici-types@7.16.0: {} + + undici@6.23.0: {} undici@7.11.0: {} @@ -25880,7 +25955,7 @@ snapshots: terser: 5.39.2 yaml: 2.7.0 - vite@6.4.1(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0): + vite@6.4.1(@types/node@24.10.11)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.7.0): dependencies: esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) @@ -25889,7 +25964,7 @@ snapshots: rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.15.18 + '@types/node': 24.10.11 fsevents: 2.3.3 jiti: 2.5.1 less: 4.3.0 @@ -26210,7 +26285,7 @@ snapshots: wordwrap@1.0.0: {} - wordwrapjs@5.1.0: {} + wordwrapjs@5.1.1: {} workerpool@6.5.1: {}