diff --git a/ts/.vscode/launch.json b/ts/.vscode/launch.json index 4114ff63d2..547f916754 100644 --- a/ts/.vscode/launch.json +++ b/ts/.vscode/launch.json @@ -49,11 +49,11 @@ { "type": "node", "request": "launch", - "name": "CLI interactive", + "name": "CLI connect", "skipFiles": ["/**"], "cwd": "${workspaceFolder}/packages/cli", "program": "./bin/run.js", - "args": ["interactive"], + "args": ["connect"], "console": "integratedTerminal", "outFiles": ["${workspaceFolder}/**/*.js"] }, @@ -76,11 +76,11 @@ { "type": "node", "request": "launch", - "name": "CLI (dev) interactive", + "name": "CLI (dev) connect", "skipFiles": ["/**"], "cwd": "${workspaceFolder}/packages/cli", "program": "./bin/dev.js", - "args": ["interactive"], + "args": ["connect"], "runtimeArgs": [ "--loader", "ts-node/esm", @@ -92,11 +92,11 @@ { "type": "node", "request": "launch", - "name": "CLI (dev) interactive [intergrated terminal]", + "name": "CLI (dev) connect [integrated terminal]", "skipFiles": ["/**"], "cwd": "${workspaceFolder}/packages/cli", "program": "./bin/dev.js", - "args": ["interactive"], + "args": ["connect"], "runtimeArgs": [ "--loader", "ts-node/esm", diff --git a/ts/CLAUDE.md b/ts/CLAUDE.md index 44ac528fa0..f2987a5df9 100644 --- a/ts/CLAUDE.md +++ b/ts/CLAUDE.md @@ -59,7 +59,7 @@ Detail architecture descriptions are located in the **`docs/architecture`** dire - **`packages/knowPro/`** — Structured RAG implementation for conversational memory. - **`packages/agents/`** — All application agents (player, calendar, email, list, browser, etc.). - **`packages/shell/`** — Electron GUI app. -- **`packages/cli/`** — Console app. +- **`packages/cli/`** — Console app (connected-mode only; all commands route through `agent-server` via WebSocket RPC). ### Agent plugin structure diff --git a/ts/README.md b/ts/README.md index 103c9147b1..bcdf9cf421 100644 --- a/ts/README.md +++ b/ts/README.md @@ -175,7 +175,7 @@ Also, you can go to the shell directory `./packages/shell` and start from there. [TypeAgent CLI](./packages/cli) provides a console based _interactive agents_ with _natural language interfaces_ experience. Additional console command is available to explore different part of TypeAgent functionalities. - Run `pnpm run cli` to get the available command -- Run `pnpm run cli -- interactive` will start the interactive prompt +- Run `pnpm run cli -- connect` will start the interactive prompt (connecting to the agent server via WebSocket RPC) Also, you can go to the CLI directory `./packages/cli` and start from there. Please see instruction in TypeAgent CLI's [README.md](./packages/cli/README.md) for more options and detail. @@ -277,13 +277,13 @@ If you open this directory as a workspace in VSCode, multiple launch task is def Common Debug Launch Task: -- CLI interactive - `./package/cli/bin/run.js interactive` -- CLI (dev) interactive - `./package/cli/bin/dev.js interactive` with a new command prompt -- CLI (dev) interactive [Integrated Terminal] - `./bin/dev.js interactive` using VSCode terminal (needed for WSL) +- CLI connect - `./package/cli/bin/run.js connect` +- CLI (dev) connect - `./package/cli/bin/dev.js connect` with a new command prompt +- CLI (dev) connect [Integrated Terminal] - `./bin/dev.js connect` using VSCode terminal (needed for WSL) #### Attaching to running sessions -To attaching to an existing session with TypeAgent CLI's interactive mode or TypeAgent Shell, you can start inspector by issuing the command `@debug` and use the VSCode `Attach` debugger launch task to attach. +To attaching to an existing session with TypeAgent CLI's connect mode or TypeAgent Shell, you can start inspector by issuing the command `@debug` and use the VSCode `Attach` debugger launch task to attach. #### TypeAgent Shell Browser Process @@ -298,7 +298,7 @@ The project uses [debug](https://www.npmjs.com/package/debug) package to enable For example (in Linux), to trace the GPT prompt that get sent when running the interactive CLI. ```bash -DEBUG=typeagent:prompt packages/cli/bin/run.js interactive +DEBUG=typeagent:prompt packages/cli/bin/run.js connect ``` **Option 2**: In the shell or CLI's interactive mode, you can issue the command `@trace ` to add to the list of namespace. Use "-" or "-\*" to disable all the trace. diff --git a/ts/docs/architecture/agentServerSessions.md b/ts/docs/architecture/agentServerSessions.md index 4d498f7055..8e5586128a 100644 --- a/ts/docs/architecture/agentServerSessions.md +++ b/ts/docs/architecture/agentServerSessions.md @@ -357,6 +357,7 @@ agent-cli connect # connect to the 'CLI' session (created agent-cli connect --resume # resume the last used session agent-cli connect --session # connect to a specific session by ID agent-cli connect --port # connect to a server on a non-default port (default: 8999) +agent-cli connect --hidden # start the server hidden (no visible window) ``` By default (no flags), `connect` targets a session named `"CLI"`. It calls `listSessions("CLI")` and joins the first match, or calls `createSession("CLI")` if none exists. @@ -365,8 +366,21 @@ Pass `--resume` / `-r` to instead resume the last used session, whose ID is pers Pass `--session` / `-s ` to connect to a specific session by UUID. This takes priority over `--resume` if both are provided; errors propagate as-is without the recovery prompt. +Pass `--hidden` to start the agent server without a visible window. Default is a visible window for interactive use. + On every successful connection the connected session ID is written to `~/.typeagent/cli-state.json` for use by future `--resume` invocations. +#### `run` — non-interactive commands + +`agent-cli run request`, `run translate`, and `run explain` each accept `--session ` / `-s` to target a specific session. If omitted, they use the find-or-create `"CLI"` session. The server is started hidden by default for non-interactive commands; use `--show` to get a visible window. + +#### `server` — manage the server process + +```bash +agent-cli server status # show whether the server is running +agent-cli server stop # send a graceful shutdown to the running server +``` + #### `sessions` topic — session CRUD | Command | RPC call | diff --git a/ts/docs/architecture/completion.md b/ts/docs/architecture/completion.md index dcc83c6df9..20596b4a6c 100644 --- a/ts/docs/architecture/completion.md +++ b/ts/docs/architecture/completion.md @@ -888,7 +888,7 @@ needs shadow candidates". ## CLI integration -The CLI (`packages/cli/src/commands/interactive.ts`) follows the same +The CLI (`packages/cli/src/commands/connect.ts`) follows the same contract but with simpler plumbing: 1. Sends full input and a `direction` (always `"forward"` for tab-completion, diff --git a/ts/packages/agentServer/README.md b/ts/packages/agentServer/README.md index 1475b03973..9323dbb180 100644 --- a/ts/packages/agentServer/README.md +++ b/ts/packages/agentServer/README.md @@ -105,17 +105,20 @@ Session dispatchers are automatically evicted from memory after 5 minutes with n ## Connection lifecycle ``` -Client calls ensureAndConnectDispatcher(clientIO, port) +Client calls ensureAgentServer(port, hidden) │ - ├─ Is server already listening on ws://localhost:? - │ └─ No → spawnAgentServer() — detached child process, survives parent exit + └─ Is server already listening on ws://localhost:? + └─ No → spawnAgentServer() — detached child process, survives parent exit + hidden=true suppresses the terminal/window + +Client calls connectAgentServer(url) │ ├─ Open WebSocket → create RPC channels │ - ├─ Send joinSession({ clientType, filter }) on agent-server channel + ├─ Send joinSession({ sessionId, clientType, filter }) on agent-server channel │ └─ Server assigns connectionId, returns { connectionId, sessionId } │ - └─ Return Dispatcher RPC proxy to caller + └─ Return AgentServerConnection (call .joinSession() to get a Dispatcher proxy) ``` On disconnect, the server removes all of that connection's sessions from its routing table. @@ -142,10 +145,29 @@ Chat UI (renderer) ↔ IPC ↔ Main process ↔ WebSocket ↔ agentServer ## CLI integration -The CLI ([`packages/cli/src/commands/connect.ts`](../cli/src/commands/connect.ts)) always uses remote connection. It calls `ensureAndConnectDispatcher()`, which auto-spawns the server if not already running, then enters an interactive readline loop. +The CLI ([`packages/cli/`](../cli/)) always uses remote connection via WebSocket. ``` -Terminal ↔ EnhancedConsoleClientIO ↔ WebSocket ↔ agentServer +Terminal ↔ ConsoleClientIO ↔ WebSocket ↔ agentServer +``` + +### `agent-cli connect` (interactive) + +`connect` calls `ensureAgentServer(port, hidden, idleTimeout)` to auto-spawn the server if needed, then calls `connectAgentServer()` and `joinSession()` directly. By default the spawned server window is visible; pass `--hidden` to suppress it. Pass `--idle-timeout ` to enable idle shutdown when spawning (default: `0`, server stays alive indefinitely). + +### `agent-cli run` (non-interactive) + +The `run request`, `run translate`, and `run explain` subcommands also call `ensureAgentServer()` — but default to **hidden** (no window), with `--show` to opt into a visible window. All three support `--session ` to target a specific session instead of the default `"CLI"` session. When spawning, passes `--idle-timeout 600` so the server exits 10 minutes after the last client disconnects. + +### `agent-cli replay` + +`replay` always creates an ephemeral session (`cli-replay-`) and deletes it on exit. Defaults to hidden; `--show` to opt in. Also passes `--idle-timeout 600` when spawning. + +### `agent-cli server` + +```bash +agent-cli server status # check whether the server is running +agent-cli server stop # send a graceful shutdown via RPC ``` --- @@ -161,17 +183,18 @@ Shell launches → createDispatcher() in-process → no server involved **Shell or CLI — server already running** ``` -Client → ensureAndConnectDispatcher(port=8999) - → server already running → connect → joinSession() → Dispatcher proxy +Client → ensureAgentServer(port=8999, hidden) + → server already running → no-op +Client → connectAgentServer() → joinSession() → Dispatcher proxy ``` **Shell or CLI — server not yet running** ``` -Client → ensureAndConnectDispatcher(port=8999) - → server not found → spawnAgentServer() +Client → ensureAgentServer(port=8999, hidden, idleTimeout) + → server not found → spawnAgentServer() (hidden or visible window) → poll until ready (60 s timeout) - → connect → joinSession() → Dispatcher proxy +Client → connectAgentServer() → joinSession() → Dispatcher proxy ``` **Headless server** @@ -182,6 +205,13 @@ pnpm --filter agent-server start → any number of Shell/CLI clients can connect and share sessions ``` +**Stopping the server** + +```bash +agent-cli server stop # via CLI (recommended) +pnpm --filter agent-server stop # via pnpm script +``` + --- ## Session persistence diff --git a/ts/packages/agentServer/client/README.md b/ts/packages/agentServer/client/README.md index 9245a6a89d..0a6fa0b3fe 100644 --- a/ts/packages/agentServer/client/README.md +++ b/ts/packages/agentServer/client/README.md @@ -40,33 +40,72 @@ await connection.close(); | `deleteSession(sessionId)` | Delete a session and its persisted data | | `close()` | Close the WebSocket connection | -### `ensureAndConnectDispatcher(clientIO, port?, options?, onDisconnect?)` +### `ensureAgentServer(port?, hidden?, idleTimeout?)` -Convenience wrapper that auto-spawns the server if needed and joins a session, returning a `Dispatcher` directly. Used by Shell and CLI. +Ensures the agentServer is running, spawning it if needed. -1. Checks whether a server is already listening on `ws://localhost:` (default 8999). -2. If not, calls `spawnAgentServer()` to start it as a detached child process. +1. Calls `isServerRunning(url)` to check whether a server is already listening. +2. If not, calls `spawnAgentServer(hidden, idleTimeout)` to start it as a detached child process. 3. Polls until the server is ready (500 ms interval, 60 s timeout). -4. Calls `connectDispatcher()` and returns the `Dispatcher` proxy. ```typescript -const dispatcher = await ensureAndConnectDispatcher( - clientIO, - 8999, - { clientType: "shell" }, - () => { - console.error("Disconnected"); - process.exit(1); - }, -); +// Start hidden with 10-minute idle shutdown — used by non-interactive CLI commands +await ensureAgentServer(8999, true, 600); -await dispatcher.processCommand("help"); +// Start in a visible window, no idle shutdown — used by interactive connect +await ensureAgentServer(8999, false); + +const connection = await connectAgentServer("ws://localhost:8999"); +``` + +| Parameter | Type | Default | Description | +| ------------- | --------- | ------- | ------------------------------------------------------------------------------------ | +| `port` | `number` | `8999` | Port to check and spawn on | +| `hidden` | `boolean` | `false` | When spawning, suppress the terminal/window (`true` = hidden) | +| `idleTimeout` | `number` | `0` | Pass `--idle-timeout` to the spawned server; `0` disables (server runs indefinitely) | + +### `isServerRunning(url)` + +Returns `true` if a server is already listening at the given WebSocket URL. + +```typescript +if (await isServerRunning("ws://localhost:8999")) { + console.log("Server is up"); +} ``` ### `stopAgentServer(port?)` Connects to the running server on the given port and sends a `shutdown()` RPC. +### `ensureAndConnectSession(clientIO, port?, options?, onDisconnect?, hidden?, idleTimeout?)` + +Convenience wrapper: ensures the server is running, connects, and joins a session in one call. Returns a `SessionDispatcher` directly. + +```typescript +const session = await ensureAndConnectSession( + clientIO, + 8999, + { sessionId }, + onDisconnect, + true, + 600, +); +``` + +| Parameter | Type | Default | Description | +| -------------- | -------------------------- | ------------ | ----------------------------------------------------- | +| `clientIO` | `ClientIO` | _(required)_ | Client IO implementation | +| `port` | `number` | `8999` | Port to connect to | +| `options` | `DispatcherConnectOptions` | `undefined` | Session join options (e.g. `sessionId`) | +| `onDisconnect` | `() => void` | `undefined` | Called when the WebSocket disconnects | +| `hidden` | `boolean` | `false` | Suppress terminal/window when spawning | +| `idleTimeout` | `number` | `0` | Pass `--idle-timeout` to spawned server; `0` disables | + +### `ensureAndConnectDispatcher(clientIO, port?, options?, onDisconnect?)` _(deprecated)_ + +Convenience wrapper that auto-spawns the server if needed and joins a session, returning a `Dispatcher` directly. Prefer calling `ensureAgentServer()` + `connectAgentServer()` + `joinSession()` separately for full control. + ### `connectDispatcher(clientIO, url, options?, onDisconnect?)` _(deprecated)_ Backward-compatible wrapper: connects and immediately joins a session, returning a `Dispatcher`. Use `connectAgentServer()` for full multi-session support. diff --git a/ts/packages/agentServer/client/src/agentServerClient.ts b/ts/packages/agentServer/client/src/agentServerClient.ts index e870fd81f3..5983f0d9bb 100644 --- a/ts/packages/agentServer/client/src/agentServerClient.ts +++ b/ts/packages/agentServer/client/src/agentServerClient.ts @@ -234,7 +234,7 @@ function getAgentServerEntryPoint(): string { return serverPath; } -function isServerRunning(url: string): Promise { +export function isServerRunning(url: string): Promise { return new Promise((resolve) => { const ws = new WebSocket(url); const timer = setTimeout(() => { @@ -253,7 +253,12 @@ function isServerRunning(url: string): Promise { }); } -function spawnAgentServer(serverPath: string, port: number): void { +function spawnAgentServer( + serverPath: string, + port: number, + hidden: boolean = false, + idleTimeout: number = 0, +): void { // Use an exclusive lock file to prevent two concurrent client processes from // both concluding the server is down and each spawning their own copy. // fs.openSync with 'wx' is atomic: exactly one caller creates the file. @@ -271,19 +276,69 @@ function spawnAgentServer(serverPath: string, port: number): void { ); return; } + + const extraArgs = + idleTimeout > 0 ? ["--idle-timeout", String(idleTimeout)] : []; + try { debug(`Starting agent server from ${serverPath}`); const isWindows = process.platform === "win32"; - const child = spawn("node", [serverPath, "--port", String(port)], { - // On Unix, detached creates a new session so the child survives parent exit. - // On Windows, detached creates a visible console window, so we skip it — - // stdio: 'ignore' + unref() is sufficient for the child to outlive the parent. - detached: !isWindows, - stdio: "ignore", - windowsHide: true, - }); - child.unref(); - debug(`Agent server process spawned (pid: ${child.pid})`); + if (isWindows) { + if (hidden) { + // Hidden mode: spawn node directly with windowsHide so no + // console window appears. The process is detached so it + // survives the parent exiting. + const child = spawn( + "node", + [serverPath, "--port", String(port), ...extraArgs], + { + detached: true, + stdio: "ignore", + windowsHide: true, + }, + ); + child.unref(); + debug( + `Agent server process spawned hidden (pid: ${child.pid})`, + ); + } else { + // Visible mode: spawn a PowerShell window so the user can see + // server output and any errors. Try PowerShell 7 (pwsh.exe) + // first, falling back to Windows PowerShell 5 (powershell.exe). + // We wrap in `cmd /c start` to force CREATE_NEW_CONSOLE — without + // this, the child inherits the parent's console and no new window + // appears when the CLI is itself running inside a console host. + const pwsh7 = "C:\\Program Files\\PowerShell\\7\\pwsh.exe"; + const psExe = fs.existsSync(pwsh7) ? pwsh7 : "powershell.exe"; + const psCommand = `node "${serverPath}" --port ${port}${idleTimeout > 0 ? ` --idle-timeout ${idleTimeout}` : ""}`; + const psArgs = ["-NoExit", "-Command", psCommand]; + const child = spawn( + "cmd.exe", + ["/c", "start", "", psExe, ...psArgs], + { + detached: true, + stdio: "ignore", + }, + ); + child.unref(); + debug( + `Agent server process spawned via ${psExe} in new window (pid: ${child.pid})`, + ); + } + } else { + // On Unix, detached creates a new session so the child survives + // parent exit. Background node process, no visible window. + const child = spawn( + "node", + [serverPath, "--port", String(port), ...extraArgs], + { + detached: true, + stdio: "ignore", + }, + ); + child.unref(); + debug(`Agent server process spawned (pid: ${child.pid})`); + } } finally { fs.closeSync(fd); try { @@ -311,12 +366,26 @@ async function waitForServer( ); } -export async function ensureAgentServer(port: number = 8999): Promise { +export async function ensureAgentServer( + port: number = 8999, + hidden: boolean = false, + idleTimeout: number = 0, +): Promise { const url = `ws://localhost:${port}`; - if (!(await isServerRunning(url))) { + if (await isServerRunning(url)) { + console.log( + `Connecting to existing TypeAgent server on port ${port}...`, + ); + } else { + if (hidden) { + console.log("Starting TypeAgent server in the background..."); + } else { + console.log("Starting TypeAgent server in a new window..."); + } const serverPath = getAgentServerEntryPoint(); - spawnAgentServer(serverPath, port); + spawnAgentServer(serverPath, port, hidden, idleTimeout); await waitForServer(url); + console.log("TypeAgent server started."); } } @@ -325,8 +394,9 @@ export async function ensureAndConnectDispatcher( port: number = 8999, options?: DispatcherConnectOptions, onDisconnect?: () => void, + hidden: boolean = false, ): Promise { - await ensureAgentServer(port); + await ensureAgentServer(port, hidden); const url = `ws://localhost:${port}`; return connectDispatcher(clientIO, url, options, onDisconnect); } @@ -336,8 +406,10 @@ export async function ensureAndConnectSession( port: number = 8999, options?: DispatcherConnectOptions, onDisconnect?: () => void, + hidden: boolean = false, + idleTimeout: number = 0, ): Promise { - await ensureAgentServer(port); + await ensureAgentServer(port, hidden, idleTimeout); const url = `ws://localhost:${port}`; const connection = await connectAgentServer(url, onDisconnect); const session = await connection.joinSession(clientIO, options); diff --git a/ts/packages/agentServer/client/src/index.ts b/ts/packages/agentServer/client/src/index.ts index 3a43df52f5..6fe5aa9ba5 100644 --- a/ts/packages/agentServer/client/src/index.ts +++ b/ts/packages/agentServer/client/src/index.ts @@ -10,6 +10,7 @@ export { ensureAndConnectDispatcher, ensureAndConnectSession, stopAgentServer, + isServerRunning, } from "./agentServerClient.js"; export type * from "@typeagent/dispatcher-rpc/types"; export type { diff --git a/ts/packages/agentServer/server/README.md b/ts/packages/agentServer/server/README.md index 94c01982fe..be899f63ed 100644 --- a/ts/packages/agentServer/server/README.md +++ b/ts/packages/agentServer/server/README.md @@ -14,7 +14,8 @@ pnpm --filter agent-server start pnpm --filter agent-server start -- --config test # Stop (sends shutdown via RPC) -pnpm --filter agent-server stop +agent-cli server stop # Client Side command +pnpm --filter agent-server stop # Server Side command ``` ### With node directly @@ -26,7 +27,15 @@ node --disable-warning=DEP0190 packages/agentServer/server/dist/server.js node --disable-warning=DEP0190 packages/agentServer/server/dist/server.js --config test ``` -Listens on `ws://localhost:8999`. The server also starts automatically when clients call `ensureAndConnectDispatcher()`. +Listens on `ws://localhost:8999`. The server also starts automatically when clients call `ensureAgentServer()`. + +### Server flags + +| Flag | Description | +| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `--port ` | Port to listen on (default: 8999) | +| `--config ` | Load `config..json` instead of the default config | +| `--idle-timeout ` | Exit after this many seconds with no connected clients (default: disabled). The CLI passes 600 (10 min) when it auto-spawns the server. | --- @@ -48,6 +57,8 @@ Maintains a pool of per-session `SharedDispatcher` instances. Key behaviors: - **Persistence:** session metadata stored in `~/.typeagent/server-sessions/sessions.json`; each session's data in `~/.typeagent/server-sessions//` - **Lazy init:** each session's `SharedDispatcher` is created on first `joinSession()` and torn down after 5 minutes of inactivity - **Auto-create:** if no session exists and no `sessionId` is provided, a `"default"` session is created automatically +- **Startup sweep:** on server start, sessions prefixed `cli-ephemeral-` or `cli-replay-` are automatically deleted to reclaim any orphaned ephemeral sessions left over from crashed CLI processes +- **Idle shutdown:** when `--idle-timeout ` is passed, the server calls `process.exit(0)` after that many seconds with no WebSocket connections. The timer resets whenever a new client connects. ### `sharedDispatcher.ts` — Routing layer diff --git a/ts/packages/agentServer/server/package.json b/ts/packages/agentServer/server/package.json index 715fcf3881..9ecdc3273f 100644 --- a/ts/packages/agentServer/server/package.json +++ b/ts/packages/agentServer/server/package.json @@ -15,6 +15,10 @@ "exports": { ".": "./dist/server.js" }, + "bin": { + "agent-server-status": "./dist/status.js", + "agent-server-stop": "./dist/stop.js" + }, "files": [ "dist", "!dist/test" diff --git a/ts/packages/agentServer/server/src/server.ts b/ts/packages/agentServer/server/src/server.ts index 9a8754928f..e1bb7243b9 100644 --- a/ts/packages/agentServer/server/src/server.ts +++ b/ts/packages/agentServer/server/src/server.ts @@ -69,9 +69,40 @@ async function main() { const port = portIdx !== -1 ? parseInt(process.argv[portIdx + 1], 10) : 8999; + const idleShutdownIdx = process.argv.indexOf("--idle-timeout"); + const idleShutdownMs = + idleShutdownIdx !== -1 + ? parseInt(process.argv[idleShutdownIdx + 1], 10) * 1000 + : 0; + + let connectionCount = 0; + let idleShutdownTimer: ReturnType | undefined; + + function scheduleIdleShutdown() { + if (idleShutdownMs <= 0 || connectionCount > 0) { + return; + } + idleShutdownTimer = setTimeout(async () => { + console.log( + "No clients connected — idle shutdown after " + + idleShutdownMs / 1000 + + "s. Stopping agent server...", + ); + wss.close(); + await sessionManager.close(); + process.exit(0); + }, idleShutdownMs); + } + const wss = await createWebSocketChannelServer( { port }, (channelProvider: ChannelProvider, closeFn: () => void) => { + connectionCount++; + if (idleShutdownTimer !== undefined) { + clearTimeout(idleShutdownTimer); + idleShutdownTimer = undefined; + } + // Track which sessions this WebSocket connection has joined // sessionId → { dispatcher, connectionId } const joinedSessions = new Map< @@ -205,6 +236,8 @@ async function main() { // Clean up all sessions on WebSocket disconnect channelProvider.on("disconnect", () => { + connectionCount--; + scheduleIdleShutdown(); for (const [ sessionId, { connectionId }, @@ -227,6 +260,7 @@ async function main() { ); console.log(`Agent server started at ws://localhost:${port}`); + scheduleIdleShutdown(); } await main(); diff --git a/ts/packages/agentServer/server/src/sessionManager.ts b/ts/packages/agentServer/server/src/sessionManager.ts index 45abee4126..e51905d639 100644 --- a/ts/packages/agentServer/server/src/sessionManager.ts +++ b/ts/packages/agentServer/server/src/sessionManager.ts @@ -260,6 +260,38 @@ export async function createSessionManager( } } + // Sweep orphaned ephemeral sessions left behind by unclean CLI exits + { + const toSweep: string[] = []; + for (const [id, record] of sessions) { + if ( + record.name.startsWith("cli-ephemeral-") || + record.name.startsWith("cli-replay-") + ) { + toSweep.push(id); + } + } + for (const id of toSweep) { + const record = sessions.get(id)!; + debugSession( + `Sweeping orphaned ephemeral session "${record.name}" (${id})`, + ); + sessions.delete(id); + const persistDir = getSessionPersistDir(id); + try { + await fs.promises.rm(persistDir, { + recursive: true, + force: true, + }); + } catch { + // Best effort — dir may not exist + } + } + if (toSweep.length > 0) { + await saveMetadata(); + } + } + const manager: SessionManager = { async createSession(name: string): Promise { validateSessionName(name); diff --git a/ts/packages/agentServer/server/src/status.ts b/ts/packages/agentServer/server/src/status.ts new file mode 100644 index 0000000000..714c12e9eb --- /dev/null +++ b/ts/packages/agentServer/server/src/status.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { isServerRunning } from "@typeagent/agent-server-client"; + +const portIdx = process.argv.indexOf("--port"); +const port = portIdx !== -1 ? parseInt(process.argv[portIdx + 1]) : 8999; + +const running = await isServerRunning(`ws://localhost:${port}`); +if (running) { + console.log(`TypeAgent server is running on port ${port}.`); +} else { + console.log(`TypeAgent server is not running on port ${port}.`); +} diff --git a/ts/packages/cli/README.md b/ts/packages/cli/README.md index e910ce6c37..49e75477da 100644 --- a/ts/packages/cli/README.md +++ b/ts/packages/cli/README.md @@ -4,7 +4,7 @@ TypeAgent CLI is a command line entry point to **TypeAgent sample code** that explores architectures for building _interactive agents_ with _natural language interfaces_ using structured prompting and LLM. -TypeAgent CLI host multiple subcommands, including the [interactive mode](#interactive-mode), a **personal agent** that takes user request and use an extensible set of agents to perform actions, answer questions, and carry a conversation. [TypeAgent Shell](../shell/) is the UI version of the interactive mode, and both shared the core [dispatcher](../dispatcher/) component. Please read dispatcher's [README.md](../dispatcher/README.md) on example requests and usage. +TypeAgent CLI hosts multiple subcommands, including the [connect mode](#connect-mode) (the default), a **personal agent** that takes user request and use an extensible set of agents to perform actions, answer questions, and carry a conversation. All CLI commands route through the agent server via WebSocket RPC. [TypeAgent Shell](../shell/) is the UI version, and both shared the core [dispatcher](../dispatcher/) component. Please read dispatcher's [README.md](../dispatcher/README.md) on example requests and usage. TypeAgent CLI includes addition commands to help with development. @@ -49,12 +49,12 @@ Other more convenient ways to start the CLI with slightly more overhead: ## Using the CLI -The CLI hosts multiple subcommands. The main one is **_interactive_** +The CLI hosts multiple subcommands. The main one is **_connect_** (and is the default when no subcommand is specified). -### Interactive Mode +### Connect Mode -The **_interactive_** CLI subcommand is a front end to the TypeAgent Dispatcher that takes user request and commands on the console -and send to to the [dispatcher](../dispatcher/). The dispatcher processes user requests and asks LLM to translate +The **_connect_** CLI subcommand is a front end to the TypeAgent Dispatcher that takes user request and commands on the console +and sends them to the [dispatcher](../dispatcher/) via the agent server. The dispatcher processes user requests and asks LLM to translate it into an action. If the user accepts the translation, LLM is asked to **explain** it, i.e. how it transformed the user request into the action, and constructions - parsing grammar/rule - is created and cached so that it can perform the user request translation locally bypassing the LLM. See [dispatcher's README](../dispatcher/README.md) for a list of commands. @@ -62,7 +62,7 @@ translation locally bypassing the LLM. See [dispatcher's README](../dispatcher/R For example: ```bash -$ agent-cli interactive +$ agent-cli [player]🤖> can you play some bach Generating translation using GPT for 'can you play some bach' 🤖: can you play some bach => play({"query":"bach"}) [3.003s] @@ -128,29 +128,60 @@ Explanation: ### `agent-cli run` -`agent-cli run` can be use to run TypeAgent dispatcher without user interactive on the command line. +`agent-cli run` can be used to run TypeAgent dispatcher commands non-interactively on the command line. The agent server is started automatically if it is not already running, hidden by default (no visible window). Use `--show` to start it in a visible window. -There are 3 command under `agent-cli run`: +There are 3 commands under `agent-cli run`: -- `agent-cli run request ` - same with sending a request in the interactive mode, except doesn't ask for confirmation -- `agent-cli run translate ` - only do translation. Same as `@translate` in interactive mode. -- `agent-cli run explain => ` - only do explanation. Same as `@explain` in interactive mode. +- `agent-cli run request ` - same as sending a request in connect mode, except doesn't ask for confirmation +- `agent-cli run translate ` - only do translation. Same as `@translate` in connect mode. +- `agent-cli run explain => ` - only do explanation. Same as `@explain` in connect mode. + +All three commands support the following flags: + +| Flag | Short | Description | +| ---------------- | ----- | ----------------------------------------------------------------------------------- | +| `--port ` | `-p` | Port for the agent server (default: 8999) | +| `--session ` | `-s` | Session ID to use. Defaults to the `'CLI'` session if not specified. | +| `--show` | | Start the server in a visible window if it is not already running (default: hidden) | + +### `agent-cli replay` + +`agent-cli replay ` replays a chat history file against an isolated ephemeral session. Useful for regression testing and generating test files. The ephemeral session is deleted on exit. + +| Flag | Short | Description | +| ----------------------- | ----- | ----------------------------------------------------------------------------------- | +| `--port ` | `-p` | Port for the agent server (default: 8999) | +| `--translate` | | Translate only, do not execute actions | +| `--generateTest ` | | Record actions to generate a test file | +| `--show` | | Start the server in a visible window if it is not already running (default: hidden) | ### `agent-cli connect` -`agent-cli connect` starts the interactive agent in connected mode, attaching to a running (or auto-started) agent server. +`agent-cli connect` is the default command. It starts the interactive agent, attaching to a running (or auto-started) agent server. ```bash agent-cli connect # connect to the 'CLI' session (created if absent) agent-cli connect --resume # resume the last used session agent-cli connect --session # connect to a specific session by ID agent-cli connect --port # connect to a server on a non-default port (default: 8999) +agent-cli connect --hidden # start the server hidden (no visible window) ``` - By default, `connect` targets a session named `"CLI"`. If no such session exists on the server it is created automatically. - Pass `--resume` / `-r` to instead resume the last used session (persisted client-side in `~/.typeagent/cli-state.json`). If that session no longer exists, you will be prompted to join the `"CLI"` session. - Pass `--session` / `-s ` to connect to any specific session by its UUID. Takes priority over `--resume` if both are provided. -- The server is started automatically if it is not already running. +- The server is started automatically if it is not already running. By default it starts in a visible window; pass `--hidden` to suppress the window. + +### `agent-cli server` + +`agent-cli server` provides commands to manage the agent server process. + +```bash +agent-cli server status # show whether the server is running +agent-cli server stop # send a graceful shutdown to the server +agent-cli server status --port # check a non-default port +agent-cli server stop --port # stop a server on a non-default port +``` ### `agent-cli sessions` @@ -250,10 +281,12 @@ Examples: Total Elapsed Time: 23.304s ``` -### Switching Translator and Explainer on command Line +### Switching Translator and Explainer + +The translator and explainer models can be changed at runtime using the `@config` command in connect mode: -Most command on the CLI accept the `--schema` option to select the schema and `--explainer` option to select -the explainer. +- `@config translation model ` +- `@config explainer model ` ## Trademarks diff --git a/ts/packages/cli/package.json b/ts/packages/cli/package.json index e81ea72e76..2ea25696fa 100644 --- a/ts/packages/cli/package.json +++ b/ts/packages/cli/package.json @@ -34,6 +34,7 @@ "oclif": { "bin": "agent-cli", "commands": "./dist/commands", + "default": "connect", "dirname": "agent-cli", "plugins": [ "@oclif/plugin-help" @@ -62,7 +63,6 @@ "chalk": "^5.4.1", "debug": "^4.4.0", "default-agent-provider": "workspace:*", - "dispatcher-node-providers": "workspace:*", "dotenv": "^16.3.1", "html-to-text": "^9.0.5", "interactive-app": "workspace:*", @@ -71,7 +71,6 @@ "open": "^10.1.0", "telemetry": "workspace:*", "ts-node": "^10.9.1", - "typechat": "^0.1.1", "typechat-utils": "workspace:*" }, "devDependencies": { diff --git a/ts/packages/cli/src/commands/connect.ts b/ts/packages/cli/src/commands/connect.ts index 04ed591020..fdc2a12e50 100644 --- a/ts/packages/cli/src/commands/connect.ts +++ b/ts/packages/cli/src/commands/connect.ts @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// TODO: The CLI still depends on agent-dispatcher for types (Dispatcher, ClientIO, +// RequestId, etc.) and helpers (withConsoleClientIO, getStatusSummary). These types +// should eventually be moved to @typeagent/dispatcher-types to avoid the heavyweight +// dependency. See cli-agent-server-deprecation.md Phase 5. + import { Args, Command, Flags } from "@oclif/core"; import { Dispatcher } from "agent-dispatcher"; import { @@ -14,8 +19,10 @@ import { connectAgentServer, ensureAgentServer, ensureAndConnectSession, + AgentServerConnection, } from "@typeagent/agent-server-client"; import { getStatusSummary } from "agent-dispatcher/helpers/status"; +import * as crypto from "crypto"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; @@ -154,6 +161,22 @@ export default class Connect extends Command { "Enable verbose debug output (optional: comma-separated debug namespaces, default: typeagent:*)", required: false, }), + memory: Flags.boolean({ + description: + "Use an ephemeral session that is automatically deleted on exit", + default: false, + exclusive: ["session", "resume"], + }), + hidden: Flags.boolean({ + description: + "Start the agent server without a visible window (background mode). Only applies when the server is not already running.", + default: false, + }), + idleTimeout: Flags.integer({ + description: + "Shut down the agent server after this many seconds with no connected clients. 0 disables (default). Only applies when the server is spawned by this command.", + default: 0, + }), }; static args = { input: Args.file({ @@ -191,6 +214,7 @@ export default class Connect extends Command { // Only intercept "Session not found" when using the client-side default // (no explicit --session flag). Explicit --session errors propagate as-is. const isDefaultSession = flags.session === undefined; + const isEphemeral = flags.memory; await withEnhancedConsoleClientIO(async (clientIO, bindDispatcher) => { const url = `ws://localhost:${flags.port}`; @@ -202,7 +226,11 @@ export default class Connect extends Command { // Helper: find the "CLI" session by name (creating it if absent) and join it. const connectToCliSession = async () => { - await ensureAgentServer(flags.port); + await ensureAgentServer( + flags.port, + flags.hidden, + flags.idleTimeout, + ); const connection = await connectAgentServer(url, onDisconnect); const existing = await connection.listSessions(CLI_SESSION_NAME); @@ -221,49 +249,101 @@ export default class Connect extends Command { session.dispatcher.close = async () => { await connection.close(); }; - return session; + return { session, connection }; }; - // Resolve the session to join: - // 1. explicit --session flag - // 2. persisted last-used session ID (with "not found" recovery) - // 3. default: find-or-create the "CLI" session - let session = - persistedSessionId !== undefined - ? await ensureAndConnectSession( - clientIO, - flags.port, - { sessionId: persistedSessionId }, - onDisconnect, - ).catch(async (err: any) => { - if ( - isDefaultSession && - typeof err?.message === "string" && - err.message.startsWith("Session not found:") - ) { - console.log( - `The last used session no longer exists on the server.`, - ); - const join = await promptYesNo( - `Join the default '${CLI_SESSION_NAME}' session?`, - ); - if (!join) { - clearLastSessionId(); - return null; - } - clearLastSessionId(); - return connectToCliSession(); - } - throw err; - }) - : await connectToCliSession(); + // Helper: create an ephemeral session for --memory flag. + const connectToEphemeralSession = async () => { + await ensureAgentServer( + flags.port, + flags.hidden, + flags.idleTimeout, + ); + const connection = await connectAgentServer(url, onDisconnect); + const ephemeralName = `cli-ephemeral-${crypto.randomUUID()}`; + const created = await connection.createSession(ephemeralName); + const session = await connection.joinSession(clientIO, { + sessionId: created.sessionId, + }); + session.dispatcher.close = async () => { + await connection.close(); + }; + return { + session, + connection, + ephemeralSessionId: created.sessionId, + }; + }; - if (session === null) { - return; + let session: Awaited< + ReturnType + >["session"]; + let connection: AgentServerConnection | undefined; + let ephemeralSessionId: string | undefined; + + if (isEphemeral) { + // --memory: use an ephemeral session, delete on exit + const result = await connectToEphemeralSession(); + session = result.session; + connection = result.connection; + ephemeralSessionId = result.ephemeralSessionId; + } else { + // Resolve the session to join: + // 1. explicit --session flag + // 2. persisted last-used session ID (with "not found" recovery) + // 3. default: find-or-create the "CLI" session + const result = + persistedSessionId !== undefined + ? await ensureAndConnectSession( + clientIO, + flags.port, + { sessionId: persistedSessionId }, + onDisconnect, + flags.hidden, + flags.idleTimeout, + ) + .then((s) => ({ + session: s, + connection: undefined as + | AgentServerConnection + | undefined, + })) + .catch(async (err: any) => { + if ( + isDefaultSession && + typeof err?.message === "string" && + err.message.startsWith( + "Session not found:", + ) + ) { + console.log( + `The last used session no longer exists on the server.`, + ); + const join = await promptYesNo( + `Join the default '${CLI_SESSION_NAME}' session?`, + ); + if (!join) { + clearLastSessionId(); + return null; + } + clearLastSessionId(); + return connectToCliSession(); + } + throw err; + }) + : await connectToCliSession(); + + if (result === null) { + return; + } + session = result.session; + connection = result.connection; } const { dispatcher, name, sessionId: connectedSessionId } = session; - saveLastSessionId(connectedSessionId); + if (!isEphemeral) { + saveLastSessionId(connectedSessionId); + } console.log(`Connected to session '${name}'.`); bindDispatcher(dispatcher); await replayDisplayHistory(dispatcher, clientIO); @@ -295,6 +375,16 @@ export default class Connect extends Command { dispatcher, ); } finally { + if ( + ephemeralSessionId !== undefined && + connection !== undefined + ) { + try { + await connection.deleteSession(ephemeralSessionId); + } catch { + // Best effort cleanup of ephemeral session + } + } if (dispatcher) { await dispatcher.close(); } diff --git a/ts/packages/cli/src/commands/interactive.ts b/ts/packages/cli/src/commands/interactive.ts deleted file mode 100644 index 8ad5f71c51..0000000000 --- a/ts/packages/cli/src/commands/interactive.ts +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { Args, Command, Flags } from "@oclif/core"; -import { createDispatcher, Dispatcher } from "agent-dispatcher"; -import { - getCacheFactory, - getAllActionConfigProvider, -} from "agent-dispatcher/internal"; -import { getTraceId, getInstanceDir } from "agent-dispatcher/helpers/data"; -import { - getDefaultAppAgentProviders, - getDefaultConstructionProvider, - getDefaultAppAgentInstaller, - getIndexingServiceRegistry, -} from "default-agent-provider"; -import inspector from "node:inspector"; -import { getChatModelNames } from "aiclient"; -import { - getEnhancedConsolePrompt, - processCommandsEnhanced, - withEnhancedConsoleClientIO, -} from "../enhancedConsole.js"; -import { isSlashCommand, getSlashCompletions } from "../slashCommands.js"; -import { getStatusSummary } from "agent-dispatcher/helpers/status"; -import { getFsStorageProvider } from "dispatcher-node-providers"; - -const modelNames = await getChatModelNames(); -const instanceDir = getInstanceDir(); -const defaultAppAgentProviders = getDefaultAppAgentProviders(instanceDir); -const { schemaNames } = await getAllActionConfigProvider( - defaultAppAgentProviders, -); - -/** - * Get completions for the current input line using dispatcher's command completion API - */ -// Return completion data including where filtering starts -type CompletionData = { - allCompletions: string[]; // All available completions (just the completion text) - filterStartIndex: number; // Where user typing should filter (after the space/trigger) - prefix: string; // Fixed prefix before completions -}; - -// Architecture: docs/architecture/completion.md — §CLI integration -async function getCompletionsData( - line: string, - dispatcher: Dispatcher, -): Promise { - try { - // Handle slash command completions - if (isSlashCommand(line)) { - const completions = getSlashCompletions(line); - if (completions.length === 0) return null; - return { - allCompletions: completions, - filterStartIndex: 0, - prefix: "", - }; - } - // Send the full input to the backend; the grammar matcher reports - // how much it consumed (matchedPrefixLength → startIndex) so the - // CLI need not split on spaces to find token boundaries. - // CLI tab-completion is always a forward action. - const direction = "forward" as const; - const result = await dispatcher.getCommandCompletion(line, direction); - if (result.completions.length === 0) { - return null; - } - - // Extract just the completion strings - const allCompletions: string[] = []; - for (const group of result.completions) { - for (const completion of group.completions) { - allCompletions.push(completion); - } - } - - const filterStartIndex = result.startIndex; - const prefix = line.substring(0, filterStartIndex); - - // When any group reports a separator-requiring mode between the - // typed prefix and the completion text, prepend a space so the - // readline display doesn't produce "playmusic" for "play" + "music". - const needsSep = result.completions.some( - (g) => - g.separatorMode === "space" || - g.separatorMode === "spacePunctuation", - ); - const separator = needsSep ? " " : ""; - - return { - allCompletions, - filterStartIndex, - prefix: prefix + separator, - }; - } catch (e) { - return null; - } -} - -export default class Interactive extends Command { - static description = "Interactive mode"; - static flags = { - agent: Flags.string({ - description: "Schema names", - options: schemaNames, - multiple: true, - }), - explainer: Flags.string({ - description: - "Explainer name (defaults to the explainer associated with the translator)", - options: getCacheFactory().getExplainerNames(), - }), - model: Flags.string({ - description: "Translation model to use", - options: modelNames, - }), - debug: Flags.boolean({ - description: "Enable debug mode", - default: false, - }), - memory: Flags.boolean({ - description: "In memory session", - default: false, - }), - exit: Flags.boolean({ - description: "Exit after processing input file", - default: true, - allowNo: true, - }), - verbose: Flags.string({ - description: - "Enable verbose debug output (optional: comma-separated debug namespaces, default: typeagent:*)", - required: false, - }), - }; - static args = { - input: Args.file({ - description: - "A text input file containing one interactive command per line", - exists: true, - }), - }; - async run(): Promise { - const { args, flags } = await this.parse(Interactive); - - if (flags.debug) { - inspector.open(undefined, undefined, true); - } - - if (flags.verbose !== undefined) { - const { default: registerDebug } = await import("debug"); - const namespaces = flags.verbose || "typeagent:*"; - registerDebug.enable(namespaces); - process.env.DEBUG = namespaces; - // Also set internal verbose state for prompt indicator - const { enableVerboseFromFlag } = await import( - "../slashCommands.js" - ); - enableVerboseFromFlag(namespaces); - } - - // Install debug interceptor so all stderr debug output - // (whether from /verbose, --verbose, or DEBUG env var) - // renders in the indented panel. - const { installDebugInterceptor } = await import( - "../debugInterceptor.js" - ); - installDebugInterceptor(); - - // Clear screen and move cursor to top for a clean full-height start - if (process.stdout.isTTY) { - process.stdout.write("\x1b[2J\x1b[H"); - } - - await withEnhancedConsoleClientIO(async (clientIO, bindDispatcher) => { - const persistDir = !flags.memory ? instanceDir : undefined; - const indexingServiceRegistry = - await getIndexingServiceRegistry(persistDir); - const dispatcher = await createDispatcher("cli interactive", { - appAgentProviders: defaultAppAgentProviders, - agentInstaller: getDefaultAppAgentInstaller(instanceDir), - agents: flags.agent, - translation: { model: flags.model }, - explainer: { name: flags.explainer }, - persistSession: !flags.memory, - persistDir, - storageProvider: - persistDir !== undefined - ? getFsStorageProvider() - : undefined, - clientIO, - dblogging: true, - indexingServiceRegistry, - traceId: getTraceId(), - constructionProvider: getDefaultConstructionProvider(), - }); - bindDispatcher(dispatcher); - - try { - if (args.input) { - await dispatcher.processCommand(`@run ${args.input}`); - if (flags.exit) { - return; - } - } - - await processCommandsEnhanced( - async (dispatcher: Dispatcher) => - getEnhancedConsolePrompt( - getStatusSummary(await dispatcher.getStatus(), { - showPrimaryName: false, - }), - ), - (command: string, dispatcher: Dispatcher) => - dispatcher.processCommand(command), - dispatcher, - undefined, // inputs - (line: string) => getCompletionsData(line, dispatcher), - dispatcher, - ); - } finally { - await dispatcher.close(); - } - }); - - // Some background network (like mongo) might keep the process live, exit explicitly. - process.exit(0); - } -} diff --git a/ts/packages/cli/src/commands/replay.ts b/ts/packages/cli/src/commands/replay.ts index 9ad5147584..01f241ddc8 100644 --- a/ts/packages/cli/src/commands/replay.ts +++ b/ts/packages/cli/src/commands/replay.ts @@ -2,32 +2,21 @@ // Licensed under the MIT License. import { Args, Command, Flags } from "@oclif/core"; -import { ClientIO, createDispatcher } from "agent-dispatcher"; import { - getDefaultAppAgentProviders, - getIndexingServiceRegistry, -} from "default-agent-provider"; -import { getChatModelNames } from "aiclient"; + connectAgentServer, + ensureAgentServer, +} from "@typeagent/agent-server-client"; import { ChatHistoryInput, isChatHistoryInput, - getAllActionConfigProvider, } from "agent-dispatcher/internal"; import { withConsoleClientIO } from "agent-dispatcher/helpers/console"; -import { getTraceId, getInstanceDir } from "agent-dispatcher/helpers/data"; +import * as crypto from "crypto"; import fs from "node:fs"; import type { TranslateTestFile, TranslateTestStep, } from "default-agent-provider/test"; -import { getFsStorageProvider } from "dispatcher-node-providers"; - -const modelNames = await getChatModelNames(); -const instanceDir = getInstanceDir(); -const defaultAppAgentProviders = getDefaultAppAgentProviders(instanceDir); -const { schemaNames } = await getAllActionConfigProvider( - defaultAppAgentProviders, -); async function readHistoryFile(filePath: string): Promise { if (!fs.existsSync(filePath)) { @@ -35,17 +24,18 @@ async function readHistoryFile(filePath: string): Promise { } const history = await fs.promises.readFile(filePath, "utf8"); + let data: unknown; try { - const data = JSON.parse(history); - if (isChatHistoryInput(data)) { - return data; - } - throw new Error(`Invalid history file format: ${filePath}.`); + data = JSON.parse(history); } catch (e) { throw new Error( `Failed to parse history file: ${filePath}. Error: ${e}`, ); } + if (isChatHistoryInput(data)) { + return data; + } + throw new Error(`Invalid history file format: ${filePath}.`); } export default class ReplayCommand extends Command { @@ -61,112 +51,49 @@ export default class ReplayCommand extends Command { description: "Translate only, do not execute actions", default: false, }), - schema: Flags.string({ - description: "Translator name", - options: schemaNames, - multiple: true, - }), - multiple: Flags.boolean({ - description: "Include multiple action schema", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - model: Flags.string({ - description: "Translation model to use", - options: modelNames, - }), - jsonSchema: Flags.boolean({ - description: "Output JSON schema", - default: false, // follow DispatcherOptions default - }), - jsonSchemaFunction: Flags.boolean({ - description: "Output JSON schema function", - default: false, // follow DispatcherOptions default - exclusive: ["jsonSchema"], - }), - jsonSchemaValidate: Flags.boolean({ - description: "Validate the output when JSON schema is enabled", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - schemaOptimization: Flags.boolean({ - description: "Enable schema optimization", - }), - switchEmbedding: Flags.boolean({ - description: "Use embedding to determine the first schema to use", - default: true, // follow DispatcherOptions default - allowNo: true, + generateTest: Flags.string({ + description: "Record action to generate test file", }), - switchInline: Flags.boolean({ - description: "Use inline switch schema to select schema group", - default: true, // follow DispatcherOptions default - allowNo: true, + port: Flags.integer({ + char: "p", + description: "Port for type agent server", + default: 8999, }), - switchSearch: Flags.boolean({ + show: Flags.boolean({ description: - "Enable second chance full switch schema to find schema group", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - generateTest: Flags.string({ - description: "Record action to generate test file", + "Start the agent server in a visible window if it is not already running. Default is to start it hidden.", + default: false, }), }; - static description = "Translate a request into action"; - static example = [ - `$ <%= config.bin %> <%= command.id %> 'play me some bach'`, - ]; + static description = "Replay a chat history file"; + static example = [`$ <%= config.bin %> <%= command.id %> history.json`]; async run(): Promise { const { args, flags } = await this.parse(ReplayCommand); const history = await readHistoryFile(args.history); - await withConsoleClientIO(async (clientIO: ClientIO) => { - const dispatcher = await createDispatcher("cli run translate", { - appAgentProviders: defaultAppAgentProviders, - agents: { - schemas: flags.schema, - actions: !flags.translate, - commands: ["dispatcher"], - }, - translation: { - model: flags.model, - multiple: { enabled: flags.multiple }, - schema: { - generation: { - jsonSchema: flags.jsonSchema, - jsonSchemaFunction: flags.jsonSchemaFunction, - jsonSchemaValidate: flags.jsonSchemaValidate, - }, - optimize: { - enabled: flags.schemaOptimization, - }, - }, - switch: { - embedding: flags.switchEmbedding, - inline: flags.switchInline, - search: flags.switchSearch, - }, - }, - execution: { history: !flags.translate }, // don't generate chat history, the test manually imports them - explainer: { enabled: false }, - cache: { enabled: false }, - clientIO, - persistDir: instanceDir, - storageProvider: getFsStorageProvider(), - dblogging: true, - traceId: getTraceId(), - indexingServiceRegistry: - await getIndexingServiceRegistry(instanceDir), - collectCommandResult: flags.generateTest !== undefined, - }); + const url = `ws://localhost:${flags.port}`; - const entries = Array.isArray(history) ? history : [history]; - const steps: TranslateTestStep[] = []; - try { + await ensureAgentServer(flags.port, !flags.show, 600); + const connection = await connectAgentServer(url); + + // Create an ephemeral session for replay isolation + const ephemeralName = `cli-replay-${crypto.randomUUID()}`; + const created = await connection.createSession(ephemeralName); + + try { + await withConsoleClientIO(async (clientIO) => { + const session = await connection.joinSession(clientIO, { + sessionId: created.sessionId, + }); + + const entries = Array.isArray(history) ? history : [history]; + const steps: TranslateTestStep[] = []; for (const entry of entries) { - const result = await dispatcher.processCommand(entry.user); + const result = await session.dispatcher.processCommand( + entry.user, + ); steps.push({ request: entry.user, expected: result?.actions, @@ -174,7 +101,7 @@ export default class ReplayCommand extends Command { }); if (flags.translate) { - await dispatcher.processCommand( + await session.dispatcher.processCommand( `@history insert ${JSON.stringify(entry)}`, ); } @@ -191,9 +118,17 @@ export default class ReplayCommand extends Command { `Generated test file '${fileName}' with a test with ${steps.length} steps`, ); } - } finally { - await dispatcher.close(); + }); + } finally { + // Delete the ephemeral session on exit for isolation + try { + await connection.deleteSession(created.sessionId); + } catch { + // Best effort cleanup } - }); + await connection.close(); + } + + process.exit(0); } } diff --git a/ts/packages/cli/src/commands/run/explain.ts b/ts/packages/cli/src/commands/run/explain.ts index 14ad0a93f3..466d020af5 100644 --- a/ts/packages/cli/src/commands/run/explain.ts +++ b/ts/packages/cli/src/commands/run/explain.ts @@ -5,17 +5,11 @@ import { Args, Command, Flags } from "@oclif/core"; import chalk from "chalk"; import { RequestAction, fromJsonActions } from "agent-cache"; import { - getCacheFactory, - getAllActionConfigProvider, -} from "agent-dispatcher/internal"; -import { getTraceId, getInstanceDir } from "agent-dispatcher/helpers/data"; -import { - getDefaultAppAgentProviders, - getIndexingServiceRegistry, -} from "default-agent-provider"; + connectAgentServer, + ensureAgentServer, + AgentServerConnection, +} from "@typeagent/agent-server-client"; import { withConsoleClientIO } from "agent-dispatcher/helpers/console"; -import { ClientIO, createDispatcher } from "agent-dispatcher"; -import { getFsStorageProvider } from "dispatcher-node-providers"; // Default test case, that include multiple phrase action name (out of order) and implicit parameters (context) const testRequest = new RequestAction( @@ -29,9 +23,7 @@ const testRequest = new RequestAction( }), ); -const instanceDir = getInstanceDir(); -const defaultAgentProviders = getDefaultAppAgentProviders(instanceDir); -const { schemaNames } = await getAllActionConfigProvider(defaultAgentProviders); +const CLI_SESSION_NAME = "CLI"; export default class ExplainCommand extends Command { static args = { @@ -41,16 +33,6 @@ export default class ExplainCommand extends Command { }; static flags = { - schema: Flags.string({ - description: "Translator names", - options: schemaNames, - multiple: true, - }), - explainer: Flags.string({ - description: - "Explainer name (defaults to the explainer associated with the translator)", - options: getCacheFactory().getExplainerNames(), - }), repeat: Flags.integer({ description: "Number of times to repeat the explanation", default: 1, @@ -65,6 +47,22 @@ export default class ExplainCommand extends Command { multiple: true, required: false, }), + port: Flags.integer({ + char: "p", + description: "Port for type agent server", + default: 8999, + }), + show: Flags.boolean({ + description: + "Start the agent server in a visible window if it is not already running. Default is to start it hidden.", + default: false, + }), + session: Flags.string({ + char: "s", + description: + "Session ID to use. Defaults to the 'CLI' session if not specified.", + required: false, + }), }; static description = "Explain a request and action"; @@ -76,10 +74,10 @@ export default class ExplainCommand extends Command { const { args, flags } = await this.parse(ExplainCommand); const command = ["@dispatcher explain"]; - if (flags.filter?.includes("refValue")) { + if (flags.filter?.some((f) => f.toLowerCase() === "refvalue")) { command.push("--filterValueInRequest"); } - if (flags.filter?.includes("refList")) { + if (flags.filter?.some((f) => f.toLowerCase() === "reflist")) { command.push("--filterReference"); } if (flags.repeat > 1) { @@ -94,31 +92,41 @@ export default class ExplainCommand extends Command { command.push(testRequest.toString()); } - await withConsoleClientIO(async (clientIO: ClientIO) => { - const dispatcher = await createDispatcher("cli run explain", { - appAgentProviders: defaultAgentProviders, - agents: { - schemas: flags.schema, - actions: false, // We don't need any actions - commands: ["dispatcher"], - }, - explainer: { - name: flags.explainer, - }, - cache: { enabled: false }, - clientIO, - indexingServiceRegistry: - await getIndexingServiceRegistry(instanceDir), - persistDir: instanceDir, - storageProvider: getFsStorageProvider(), - dblogging: true, - traceId: getTraceId(), - }); - try { - await dispatcher.processCommand(command.join(" ")); - } finally { - await dispatcher.close(); + const url = `ws://localhost:${flags.port}`; + + await ensureAgentServer(flags.port, !flags.show, 600); + let connection: AgentServerConnection | undefined; + try { + connection = await connectAgentServer(url); + + // Use --session directly if provided, otherwise find-or-create the "CLI" session + let sessionId: string; + if (flags.session !== undefined) { + sessionId = flags.session; + } else { + const existing = + await connection.listSessions(CLI_SESSION_NAME); + const match = existing.find( + (s) => + s.name.toLowerCase() === CLI_SESSION_NAME.toLowerCase(), + ); + sessionId = + match !== undefined + ? match.sessionId + : (await connection.createSession(CLI_SESSION_NAME)) + .sessionId; } - }); + + await withConsoleClientIO(async (clientIO) => { + const session = await connection!.joinSession(clientIO, { + sessionId, + }); + await session.dispatcher.processCommand(command.join(" ")); + }); + } finally { + await connection?.close(); + } + + process.exit(0); } } diff --git a/ts/packages/cli/src/commands/run/request.ts b/ts/packages/cli/src/commands/run/request.ts index 2d5335fa65..7e110b618d 100644 --- a/ts/packages/cli/src/commands/run/request.ts +++ b/ts/packages/cli/src/commands/run/request.ts @@ -2,27 +2,16 @@ // Licensed under the MIT License. import { Args, Command, Flags } from "@oclif/core"; -import { createDispatcher } from "agent-dispatcher"; import { - getCacheFactory, - getAllActionConfigProvider, -} from "agent-dispatcher/internal"; -import { getTraceId, getInstanceDir } from "agent-dispatcher/helpers/data"; -import { - getDefaultAppAgentProviders, - getIndexingServiceRegistry, -} from "default-agent-provider"; -import chalk from "chalk"; -import { getChatModelNames } from "aiclient"; + connectAgentServer, + ensureAgentServer, + AgentServerConnection, +} from "@typeagent/agent-server-client"; +import { withConsoleClientIO } from "agent-dispatcher/helpers/console"; import { readFileSync, existsSync } from "fs"; -import { getFsStorageProvider } from "dispatcher-node-providers"; -const modelNames = await getChatModelNames(); -const instanceDir = getInstanceDir(); -const defaultAppAgentProviders = getDefaultAppAgentProviders(instanceDir); -const { schemaNames } = await getAllActionConfigProvider( - defaultAppAgentProviders, -); +const CLI_SESSION_NAME = "CLI"; + export default class RequestCommand extends Command { static args = { request: Args.string({ @@ -37,20 +26,21 @@ export default class RequestCommand extends Command { }; static flags = { - schema: Flags.string({ - description: "Schema name", - options: schemaNames, - multiple: true, + port: Flags.integer({ + char: "p", + description: "Port for type agent server", + default: 8999, }), - explainer: Flags.string({ + show: Flags.boolean({ description: - "Explainer name (defaults to the explainer associated with the translator)", - options: getCacheFactory().getExplainerNames(), - required: false, + "Start the agent server in a visible window if it is not already running. Default is to start it hidden.", + default: false, }), - model: Flags.string({ - description: "Translation model to use", - options: modelNames, + session: Flags.string({ + char: "s", + description: + "Session ID to use. Defaults to the 'CLI' session if not specified.", + required: false, }), }; @@ -61,33 +51,46 @@ export default class RequestCommand extends Command { async run(): Promise { const { args, flags } = await this.parse(RequestCommand); - const dispatcher = await createDispatcher("cli run request", { - appAgentProviders: defaultAppAgentProviders, - agents: { - schemas: flags.schema, - actions: flags.schema, - commands: ["dispatcher"], - }, - translation: { model: flags.model }, - explainer: flags.explainer - ? { enabled: true, name: flags.explainer } - : { enabled: false }, - indexingServiceRegistry: - await getIndexingServiceRegistry(instanceDir), - cache: { enabled: false }, - persistDir: instanceDir, - storageProvider: getFsStorageProvider(), - dblogging: true, - traceId: getTraceId(), - }); - await dispatcher.processCommand( - `@dispatcher request ${args.request}`, - undefined, - this.loadAttachment(args.attachment), - ); - await dispatcher.close(); + const url = `ws://localhost:${flags.port}`; - // Some background network (like monogo) might keep the process live, exit explicitly. + await ensureAgentServer(flags.port, !flags.show, 600); + let connection: AgentServerConnection | undefined; + try { + connection = await connectAgentServer(url); + + // Use --session directly if provided, otherwise find-or-create the "CLI" session + let sessionId: string; + if (flags.session !== undefined) { + sessionId = flags.session; + } else { + const existing = + await connection.listSessions(CLI_SESSION_NAME); + const match = existing.find( + (s) => + s.name.toLowerCase() === CLI_SESSION_NAME.toLowerCase(), + ); + sessionId = + match !== undefined + ? match.sessionId + : (await connection.createSession(CLI_SESSION_NAME)) + .sessionId; + } + + await withConsoleClientIO(async (clientIO) => { + const session = await connection!.joinSession(clientIO, { + sessionId, + }); + await session.dispatcher.processCommand( + `@dispatcher request ${args.request}`, + undefined, + this.loadAttachment(args.attachment), + ); + }); + } finally { + await connection?.close(); + } + + // Some background network (like mongo) might keep the process live, exit explicitly. process.exit(0); } @@ -97,11 +100,7 @@ export default class RequestCommand extends Command { } if (!existsSync(fileName)) { - console.error( - chalk.red(`ERROR: The file '${fileName}' does not exist.`), - ); - - throw Error(`ERROR: The file '${fileName}' does not exist.`); + throw Error(`The file '${fileName}' does not exist.`); } let retVal: string[] = new Array(); diff --git a/ts/packages/cli/src/commands/run/translate.ts b/ts/packages/cli/src/commands/run/translate.ts index 306f83519c..0306905d5f 100644 --- a/ts/packages/cli/src/commands/run/translate.ts +++ b/ts/packages/cli/src/commands/run/translate.ts @@ -2,23 +2,14 @@ // Licensed under the MIT License. import { Args, Command, Flags } from "@oclif/core"; -import { ClientIO, createDispatcher } from "agent-dispatcher"; import { - getDefaultAppAgentProviders, - getIndexingServiceRegistry, -} from "default-agent-provider"; -import { getChatModelNames } from "aiclient"; -import { getAllActionConfigProvider } from "agent-dispatcher/internal"; + connectAgentServer, + ensureAgentServer, + AgentServerConnection, +} from "@typeagent/agent-server-client"; import { withConsoleClientIO } from "agent-dispatcher/helpers/console"; -import { getTraceId, getInstanceDir } from "agent-dispatcher/helpers/data"; -import { getFsStorageProvider } from "dispatcher-node-providers"; -const modelNames = await getChatModelNames(); -const instanceDir = getInstanceDir(); -const defaultAppAgentProviders = getDefaultAppAgentProviders(instanceDir); -const { schemaNames } = await getAllActionConfigProvider( - defaultAppAgentProviders, -); +const CLI_SESSION_NAME = "CLI"; export default class TranslateCommand extends Command { static args = { @@ -30,52 +21,21 @@ export default class TranslateCommand extends Command { }; static flags = { - schema: Flags.string({ - description: "Translator name", - options: schemaNames, - multiple: true, + port: Flags.integer({ + char: "p", + description: "Port for type agent server", + default: 8999, }), - multiple: Flags.boolean({ - description: "Include multiple action schema", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - model: Flags.string({ - description: "Translation model to use", - options: modelNames, - }), - jsonSchema: Flags.boolean({ - description: "Output JSON schema", - default: false, // follow DispatcherOptions default - }), - jsonSchemaFunction: Flags.boolean({ - description: "Output JSON schema function", - default: false, // follow DispatcherOptions default - exclusive: ["jsonSchema"], - }), - jsonSchemaValidate: Flags.boolean({ - description: "Validate the output when JSON schema is enabled", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - schemaOptimization: Flags.boolean({ - description: "Enable schema optimization", - }), - switchEmbedding: Flags.boolean({ - description: "Use embedding to determine the first schema to use", - default: true, // follow DispatcherOptions default - allowNo: true, - }), - switchInline: Flags.boolean({ - description: "Use inline switch schema to select schema group", - default: true, // follow DispatcherOptions default - allowNo: true, + show: Flags.boolean({ + description: + "Start the agent server in a visible window if it is not already running. Default is to start it hidden.", + default: false, }), - switchSearch: Flags.boolean({ + session: Flags.string({ + char: "s", description: - "Enable second chance full switch schema to find schema group", - default: true, // follow DispatcherOptions default - allowNo: true, + "Session ID to use. Defaults to the 'CLI' session if not specified.", + required: false, }), }; @@ -86,49 +46,43 @@ export default class TranslateCommand extends Command { async run(): Promise { const { args, flags } = await this.parse(TranslateCommand); - await withConsoleClientIO(async (clientIO: ClientIO) => { - const dispatcher = await createDispatcher("cli run translate", { - appAgentProviders: defaultAppAgentProviders, - agents: { - schemas: flags.schema, - actions: false, - commands: ["dispatcher"], - }, - translation: { - model: flags.model, - multiple: { enabled: flags.multiple }, - schema: { - generation: { - jsonSchema: flags.jsonSchema, - jsonSchemaFunction: flags.jsonSchemaFunction, - jsonSchemaValidate: flags.jsonSchemaValidate, - }, - optimize: { - enabled: flags.schemaOptimization, - }, - }, - switch: { - embedding: flags.switchEmbedding, - inline: flags.switchInline, - search: flags.switchSearch, - }, - }, - cache: { enabled: false }, - clientIO, - persistDir: instanceDir, - storageProvider: getFsStorageProvider(), - dblogging: true, - indexingServiceRegistry: - await getIndexingServiceRegistry(instanceDir), - traceId: getTraceId(), - }); - try { - await dispatcher.processCommand( - `@dispatcher translate ${args.request}`, + const url = `ws://localhost:${flags.port}`; + + await ensureAgentServer(flags.port, !flags.show, 600); + let connection: AgentServerConnection | undefined; + try { + connection = await connectAgentServer(url); + + // Use --session directly if provided, otherwise find-or-create the "CLI" session + let sessionId: string; + if (flags.session !== undefined) { + sessionId = flags.session; + } else { + const existing = + await connection.listSessions(CLI_SESSION_NAME); + const match = existing.find( + (s) => + s.name.toLowerCase() === CLI_SESSION_NAME.toLowerCase(), ); - } finally { - await dispatcher.close(); + sessionId = + match !== undefined + ? match.sessionId + : (await connection.createSession(CLI_SESSION_NAME)) + .sessionId; } - }); + + await withConsoleClientIO(async (clientIO) => { + const session = await connection!.joinSession(clientIO, { + sessionId, + }); + await session.dispatcher.processCommand( + `@dispatcher translate ${args.request}`, + ); + }); + } finally { + await connection?.close(); + } + + process.exit(0); } } diff --git a/ts/packages/cli/src/commands/server/status.ts b/ts/packages/cli/src/commands/server/status.ts new file mode 100644 index 0000000000..1831a59b1b --- /dev/null +++ b/ts/packages/cli/src/commands/server/status.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Command, Flags } from "@oclif/core"; +import { isServerRunning } from "@typeagent/agent-server-client"; + +export default class ServerStatus extends Command { + static description = "Show whether the TypeAgent server is running"; + static flags = { + port: Flags.integer({ + char: "p", + description: "Port to check", + default: 8999, + }), + }; + async run(): Promise { + const { flags } = await this.parse(ServerStatus); + const running = await isServerRunning(`ws://localhost:${flags.port}`); + if (running) { + this.log(`TypeAgent server is running on port ${flags.port}.`); + } else { + this.log(`TypeAgent server is not running on port ${flags.port}.`); + this.exit(1); + } + } +} diff --git a/ts/packages/cli/src/commands/server/stop.ts b/ts/packages/cli/src/commands/server/stop.ts new file mode 100644 index 0000000000..3575dda598 --- /dev/null +++ b/ts/packages/cli/src/commands/server/stop.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Command, Flags } from "@oclif/core"; +import { stopAgentServer } from "@typeagent/agent-server-client"; + +export default class ServerStop extends Command { + static description = "Stop the running TypeAgent server"; + static flags = { + port: Flags.integer({ + char: "p", + description: "Port the agent server is listening on", + default: 8999, + }), + }; + async run(): Promise { + const { flags } = await this.parse(ServerStop); + await stopAgentServer(flags.port); + process.exit(0); + } +} diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 6d91072476..b39dcd8909 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -150,7 +150,7 @@ importers: version: 8.18.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -527,7 +527,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -758,7 +758,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -899,7 +899,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -957,7 +957,7 @@ importers: version: 2.0.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1007,7 +1007,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -1051,7 +1051,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1079,7 +1079,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1330,7 +1330,7 @@ importers: version: 2.4.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -1599,10 +1599,10 @@ importers: version: 11.3.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.23) + version: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) jest-chrome: specifier: ^0.8.0 - version: 0.8.0(jest@29.7.0(@types/node@20.19.23)) + version: 0.8.0(jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5))) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 @@ -1614,7 +1614,7 @@ importers: version: 3.5.0 ts-jest: specifier: ^29.3.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))(esbuild@0.25.12)(jest@29.7.0(@types/node@20.19.23))(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))(esbuild@0.25.12)(jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)))(typescript@5.4.5) ts-loader: specifier: ^9.5.1 version: 9.5.2(typescript@5.4.5)(webpack@5.105.0(esbuild@0.25.12)) @@ -2118,7 +2118,7 @@ importers: version: 2.4.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2209,7 +2209,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -2384,7 +2384,7 @@ importers: version: 9.1.2 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18) + version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -2516,7 +2516,7 @@ importers: version: 2.4.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2588,7 +2588,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2742,7 +2742,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -2827,7 +2827,7 @@ importers: version: 8.18.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2888,7 +2888,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -2949,7 +2949,7 @@ importers: version: 2.0.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2989,7 +2989,7 @@ importers: version: 5.6.3(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3078,9 +3078,6 @@ importers: default-agent-provider: specifier: workspace:* version: link:../defaultAgentProvider - dispatcher-node-providers: - specifier: workspace:* - version: link:../dispatcher/nodeProviders dotenv: specifier: ^16.3.1 version: 16.5.0 @@ -3105,9 +3102,6 @@ importers: ts-node: specifier: ^10.9.1 version: 10.9.2(@types/node@25.5.2)(typescript@5.4.5) - typechat: - specifier: ^0.1.1 - version: 0.1.1(typescript@5.4.5)(zod@3.25.76) typechat-utils: specifier: workspace:* version: link:../utils/typechatUtils @@ -3301,7 +3295,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -3310,7 +3304,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@25.5.2))(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@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)))(typescript@5.4.5) typescript: specifier: ~5.4.5 version: 5.4.5 @@ -3482,7 +3476,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3630,7 +3624,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3670,7 +3664,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3701,7 +3695,7 @@ importers: version: 22.15.18 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.15.18) + version: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3785,7 +3779,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3880,7 +3874,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3920,7 +3914,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -3947,7 +3941,7 @@ importers: dependencies: '@anthropic-ai/claude-agent-sdk': specifier: ^0.2.92 - version: 0.2.92(zod@4.1.13) + version: 0.2.92 aiclient: specifier: workspace:* version: link:../aiclient @@ -4064,7 +4058,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4180,7 +4174,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.2.5 version: 3.5.3 @@ -4274,7 +4268,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4435,7 +4429,7 @@ importers: version: 4.0.1(vite@6.4.2(@types/node@25.5.2)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(yaml@2.8.3)) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) less: specifier: ^4.2.0 version: 4.3.0 @@ -4484,7 +4478,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4515,7 +4509,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4583,7 +4577,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4611,7 +4605,7 @@ importers: version: 0.25.11 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4642,7 +4636,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -4691,7 +4685,7 @@ importers: version: 29.5.14 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.5.2) + version: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -14744,6 +14738,24 @@ snapshots: '@antfu/utils@8.1.1': {} + '@anthropic-ai/claude-agent-sdk@0.2.92': + dependencies: + '@anthropic-ai/sdk': 0.80.0(zod@4.1.13) + '@modelcontextprotocol/sdk': 1.29.0 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + transitivePeerDependencies: + - '@cfworker/json-schema' + - supports-color + '@anthropic-ai/claude-agent-sdk@0.2.92(zod@4.1.13)': dependencies: '@anthropic-ai/sdk': 0.80.0(zod@4.1.13) @@ -17032,6 +17044,111 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.39 + 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@20.19.39)(ts-node@10.9.2(@types/node@20.19.23)(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.39)(typescript@5.4.5))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.39 + 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@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(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.15.18)(typescript@5.4.5))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.39 + 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@20.19.39)(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@25.5.2)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 @@ -17736,6 +17853,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@modelcontextprotocol/sdk@1.29.0': + dependencies: + '@hono/node-server': 1.19.13(hono@4.12.12) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 8.3.1(express@5.2.1) + hono: 4.12.12 + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod-to-json-schema: 3.25.1(zod@4.3.6) + transitivePeerDependencies: + - supports-color + '@modelcontextprotocol/sdk@1.29.0(zod@4.1.13)': dependencies: '@hono/node-server': 1.19.13(hono@4.12.12) @@ -20671,13 +20809,28 @@ snapshots: dependencies: buffer: 5.7.1 - create-jest@29.7.0(@types/node@20.19.23): + create-jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(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.23)(ts-node@10.9.2(@types/node@20.19.23)(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.39)(ts-node@10.9.2(@types/node@20.19.39)(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.23) + jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -20686,13 +20839,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@22.15.18): + create-jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(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) + 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 prompts: 2.4.2 transitivePeerDependencies: @@ -22955,10 +23108,10 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 - jest-chrome@0.8.0(jest@29.7.0(@types/node@20.19.23)): + jest-chrome@0.8.0(jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5))): dependencies: '@types/chrome': 0.0.114 - jest: 29.7.0(@types/node@20.19.23) + jest: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) jest-circus@29.7.0: dependencies: @@ -22986,16 +23139,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.23): + jest-cli@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.23)(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.23) + create-jest: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.23) + jest-config: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -23005,16 +23158,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@22.15.18): + jest-cli@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(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) + create-jest: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@22.15.18) + jest-config: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -23024,16 +23177,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@25.5.2): + jest-cli@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.15.18)(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@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + 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.2 @@ -23062,7 +23215,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.23): + jest-config@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)): dependencies: '@babel/core': 7.28.4 '@jest/test-sequencer': 29.7.0 @@ -23088,6 +23241,100 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.19.23 + ts-node: 10.9.2(@types/node@20.19.23)(typescript@5.4.5) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)): + dependencies: + '@babel/core': 7.28.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.4) + 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.39 + ts-node: 10.9.2(@types/node@20.19.23)(typescript@5.4.5) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)): + dependencies: + '@babel/core': 7.28.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.4) + 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.39 + ts-node: 10.9.2(@types/node@20.19.39)(typescript@5.4.5) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): + dependencies: + '@babel/core': 7.28.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.4) + 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.39 + ts-node: 10.9.2(@types/node@22.15.18)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -23123,7 +23370,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.15.18): + 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.28.4 '@jest/test-sequencer': 29.7.0 @@ -23149,6 +23396,7 @@ snapshots: 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 @@ -23420,36 +23668,36 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.23): + jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.23) + jest-cli: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@22.15.18): + jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.15.18) + jest-cli: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest@29.7.0(@types/node@25.5.2): + jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@25.5.2) + jest-cli: 29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -26661,12 +26909,12 @@ snapshots: ts-deepmerge@7.0.2: {} - 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))(esbuild@0.25.12)(jest@29.7.0(@types/node@20.19.23))(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))(esbuild@0.25.12)(jest@29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(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@20.19.23) + jest: 29.7.0(@types/node@20.19.23)(ts-node@10.9.2(@types/node@20.19.23)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -26682,12 +26930,12 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.28.4) esbuild: 0.25.12 - 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@25.5.2))(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@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(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@25.5.2) + jest: 29.7.0(@types/node@25.5.2)(ts-node@10.9.2(@types/node@25.5.2)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -26742,6 +26990,63 @@ snapshots: typescript: 5.4.5 webpack: 5.105.0(webpack-cli@5.1.4) + ts-node@10.9.2(@types/node@20.19.23)(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': 20.19.23 + acorn: 8.11.1 + acorn-walk: 8.3.0 + 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.39)(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': 20.19.39 + acorn: 8.11.1 + acorn-walk: 8.3.0 + 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): + 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 + acorn: 8.11.1 + acorn-walk: 8.3.0 + 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@25.5.2)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1