From d064afde6cc0771c18ffde5bb919e1d0b81f1eaa Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 17 Jul 2026 13:31:46 -0700 Subject: [PATCH 01/12] Updated session lock logic to include pid and update lock to include a .held so you know if the lock is alive or stale. --- .../dispatcher/src/utils/fsUtils.ts | 164 +++++++++++++++--- .../dispatcher/test/instanceLock.spec.ts | 123 +++++++++++++ 2 files changed, 267 insertions(+), 20 deletions(-) create mode 100644 ts/packages/dispatcher/dispatcher/test/instanceLock.spec.ts diff --git a/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts b/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts index fe78a3f97..793b6f292 100644 --- a/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts +++ b/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts @@ -41,16 +41,67 @@ export function getUniqueFileName( return dir; } +// Lock the instance (profile) directory so only one process (shell or +// agent-server) uses it at a time. proper-lockfile provides the atomic +// acquisition, an mtime "heartbeat", and stale-lock breaking; on top of it we +// add two things: +// 1. A human-readable owner marker so you can tell WHICH process holds the +// lock (previously there was no way to know). +// 2. Recovery when the lock folder is deleted out from under a running +// process: the heartbeat notices the lock is gone and recreates it so a +// second process can't grab the same profile. +// +// Layout (for instanceDir = ".../profiles/dev"): +// .../profiles/dev.lock/ <- the lock folder (what a user might delete) +// .../profiles/dev.lock/held <- proper-lockfile's tracked lock dir +// .../profiles/dev.lock/pid-1234 <- owner marker (name = pid, body = details) +// +// proper-lockfile tracks the nested "held" dir, not the lock folder itself, so +// writing the marker into the folder does not bump the tracked mtime and cause +// a false "compromised" event. The pid can't go in the lock folder's own name: +// that name must be fixed for the atomic mkdir to provide mutual exclusion (a +// per-pid name would let every process create its own folder and never race). export async function lockInstanceDir(instanceDir: string) { ensureDirectory(instanceDir); - try { - let isExiting = false; - process.on("exit", () => { - isExiting = true; - }); - return await lockfile.lock(instanceDir, { - // Retry for up to ~15 seconds to handle the case where a previous - // process was forcibly killed and its lock file is not yet stale. + + const lockFolder = `${instanceDir}.lock`; + const heldPath = path.join(lockFolder, "held"); + const ownerMarkerName = `pid-${process.pid}`; + + let isExiting = false; + process.on("exit", () => { + isExiting = true; + }); + + let release!: () => Promise; + + // Drop a marker named after our pid (with host + start time in the body) so + // `ls .lock` reveals the owner. Also clear any marker left by a + // crashed prior holder whose stale lock we just broke, keeping exactly one. + const writeOwnerMarker = () => { + try { + for (const entry of fs.readdirSync(lockFolder)) { + if (entry.startsWith("pid-") && entry !== ownerMarkerName) { + fs.rmSync(path.join(lockFolder, entry), { force: true }); + } + } + fs.writeFileSync( + path.join(lockFolder, ownerMarkerName), + `pid: ${process.pid}\nhost: ${os.hostname()}\nstarted: ${new Date().toISOString()}\ninstanceDir: ${instanceDir}\n`, + ); + } catch { + // Best effort: the marker is diagnostic only. + } + }; + + // proper-lockfile mkdir's heldPath non-recursively, so the lock folder must + // exist first. + const acquire = () => { + ensureDirectory(lockFolder); + return lockfile.lock(instanceDir, { + lockfilePath: heldPath, + // Retry for up to ~30 seconds to handle the case where a previous + // process was forcibly killed and its lock is not yet stale. retries: { retries: 30, minTimeout: 1000, maxTimeout: 1000 }, // Break locks whose mtime heartbeat hasn't fired in 10s. proper-lockfile // updates the mtime every stale/2 ms (5s) while the holder is alive, so a @@ -58,19 +109,55 @@ export async function lockInstanceDir(instanceDir: string) { // freezes and its orphaned lock gets broken here. stale must be < total // retry window (30s) so a freshly-orphaned lock can be recovered. stale: 10000, - onCompromised: (err) => { - if (isExiting) { - // We are exiting, just ignore the error - return; - } - // Log but do not exit — on Windows, proper-lockfile's PID liveness - // check can incorrectly mark a live lock as stale, causing false - // compromised events when running multiple concurrent server processes. - console.error( - `\nWARN: User instance directory lock ${instanceDir} reported as compromised — continuing.\n ${err}`, - ); - }, + onCompromised, }); + }; + + let recovering = false; + const reacquire = async () => { + if (isExiting || recovering) { + return; + } + recovering = true; + try { + release = await acquire(); + writeOwnerMarker(); + console.error( + `\nWARN: Instance directory lock ${lockFolder} was missing — recreated by pid ${process.pid}.\n`, + ); + } catch (e) { + console.error( + `\nWARN: Failed to recreate instance directory lock ${lockFolder}: ${e}\n`, + ); + } finally { + recovering = false; + } + }; + + function onCompromised(err: Error) { + if (isExiting) { + // We are exiting, just ignore the error + return; + } + if (fs.existsSync(heldPath)) { + // The lock is still present: either still ours (on Windows, + // proper-lockfile's mtime check can spuriously fire for a live lock + // when multiple processes are active) or another process legitimately + // reclaimed our stale lock. Either way don't recreate it (that would + // thrash or steal); log and continue, matching prior tolerant behavior. + console.error( + `\nWARN: User instance directory lock ${lockFolder} reported as compromised — continuing.\n ${err}`, + ); + return; + } + // The lock was removed out from under us (e.g. someone deleted the lock + // folder while the server was running). Recreate it during this + // heartbeat so a second process can't grab the profile. + void reacquire(); + } + + try { + release = await acquire(); } catch (e: any) { if (e?.code === "ELOCKED") { const err: any = new Error( @@ -84,6 +171,43 @@ export async function lockInstanceDir(instanceDir: string) { `Unable to lock instance directory ${instanceDir}. Only one client per instance directory can be active at a time. Cause: ${e?.code ?? "unknown"} — ${e?.message ?? e}`, ); } + + writeOwnerMarker(); + + // Returned unlock: release proper-lockfile's hold, then tear down the lock + // folder we created. All best effort. + return async () => { + isExiting = true; + let released = false; + try { + await release(); + released = true; + } catch { + // Already released or compromised: nothing to unlock. + } + if (released) { + // We released our own hold (proper-lockfile removed "held"), so the + // folder is ours to remove. Recursive + force so the pid marker (and + // the "held" dir, and any stray entry) go with it rather than + // leaking a non-empty directory. + try { + fs.rmSync(lockFolder, { recursive: true, force: true }); + } catch { + // Best effort. + } + } else { + // Release failed: another process may have reclaimed "held" after we + // were compromised. Only drop our own marker; don't recursively + // delete a lock folder we may no longer own. + try { + fs.rmSync(path.join(lockFolder, ownerMarkerName), { + force: true, + }); + } catch { + // Best effort. + } + } + }; } export function ensureCacheDir(instanceDir: string) { const dir = path.join(instanceDir, "cache"); diff --git a/ts/packages/dispatcher/dispatcher/test/instanceLock.spec.ts b/ts/packages/dispatcher/dispatcher/test/instanceLock.spec.ts new file mode 100644 index 000000000..20c1b4f1e --- /dev/null +++ b/ts/packages/dispatcher/dispatcher/test/instanceLock.spec.ts @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { lockInstanceDir } from "../src/utils/fsUtils.js"; + +function makeInstanceDir(): string { + const base = fs.mkdtempSync(path.join(os.tmpdir(), "instancelock-")); + return path.join(base, "profile"); +} + +function lockPaths(instanceDir: string) { + const lockFolder = `${instanceDir}.lock`; + return { + lockFolder, + held: path.join(lockFolder, "held"), + marker: path.join(lockFolder, `pid-${process.pid}`), + }; +} + +async function waitFor( + predicate: () => boolean, + timeoutMs: number, +): Promise { + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (predicate()) return true; + await new Promise((r) => setTimeout(r, 250)); + } + return predicate(); +} + +describe("lockInstanceDir", () => { + it("drops a pid-named owner marker inside the lock folder", async () => { + const instanceDir = makeInstanceDir(); + const { held, marker } = lockPaths(instanceDir); + + const unlock = await lockInstanceDir(instanceDir); + try { + expect(fs.existsSync(held)).toBe(true); + expect(fs.existsSync(marker)).toBe(true); + // The body carries enough to identify the owner. + const body = fs.readFileSync(marker, "utf8"); + expect(body).toContain(`pid: ${process.pid}`); + expect(body).toContain(`host: ${os.hostname()}`); + } finally { + await unlock(); + } + }); + + it("removes the marker and lock folder on unlock", async () => { + const instanceDir = makeInstanceDir(); + const { lockFolder } = lockPaths(instanceDir); + + const unlock = await lockInstanceDir(instanceDir); + expect(fs.existsSync(lockFolder)).toBe(true); + await unlock(); + + expect(fs.existsSync(lockFolder)).toBe(false); + }); + + it("recursively removes a non-empty lock folder on unlock", async () => { + const instanceDir = makeInstanceDir(); + const { lockFolder } = lockPaths(instanceDir); + + const unlock = await lockInstanceDir(instanceDir); + // A stray entry (and a stray subdir) must not defeat cleanup: a plain + // rmdir would throw ENOTEMPTY and leak the lock folder. + fs.writeFileSync(path.join(lockFolder, "stray.txt"), "x"); + fs.mkdirSync(path.join(lockFolder, "stray-dir")); + await unlock(); + + expect(fs.existsSync(lockFolder)).toBe(false); + }); + + it("clears a stale pid marker left by a crashed prior holder", async () => { + const instanceDir = makeInstanceDir(); + const { lockFolder } = lockPaths(instanceDir); + + // Simulate a crashed holder: a lock folder with a foreign pid marker + // but no live "held" lock (its stale lock will be broken on acquire). + fs.mkdirSync(lockFolder, { recursive: true }); + fs.writeFileSync( + path.join(lockFolder, "pid-99999999"), + "pid: 99999999\n", + ); + + const unlock = await lockInstanceDir(instanceDir); + try { + const entries = fs.readdirSync(lockFolder).sort(); + expect(entries).toContain(`pid-${process.pid}`); + expect(entries).not.toContain("pid-99999999"); + } finally { + await unlock(); + } + }); + + it("recreates the lock folder when it is deleted while held", async () => { + const instanceDir = makeInstanceDir(); + const { lockFolder, held, marker } = lockPaths(instanceDir); + + const unlock = await lockInstanceDir(instanceDir); + try { + expect(fs.existsSync(held)).toBe(true); + + // Someone deletes the whole lock folder out from under the holder. + fs.rmSync(lockFolder, { recursive: true, force: true }); + expect(fs.existsSync(lockFolder)).toBe(false); + + // proper-lockfile's mtime heartbeat (update = stale/2 = 5s) notices + // the lock is gone and reacquire recreates it. Poll generously. + const recovered = await waitFor( + () => fs.existsSync(held) && fs.existsSync(marker), + 20000, + ); + expect(recovered).toBe(true); + } finally { + await unlock(); + } + }, 30000); +}); From ab698ac8a5fec9bc1d06965416cff410fcb39731 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 17 Jul 2026 14:09:19 -0700 Subject: [PATCH 02/12] updated lock file logic --- .../dispatcher/dispatcher/src/utils/fsUtils.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts b/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts index 793b6f292..f9f411bf9 100644 --- a/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts +++ b/ts/packages/dispatcher/dispatcher/src/utils/fsUtils.ts @@ -69,9 +69,13 @@ export async function lockInstanceDir(instanceDir: string) { const ownerMarkerName = `pid-${process.pid}`; let isExiting = false; - process.on("exit", () => { + // Named so the returned unlock can remove it: lockInstanceDir may run once + // per conversation over a long-running server, and an anonymous per-call + // listener would accumulate toward Node's max-listeners warning. + const onProcessExit = () => { isExiting = true; - }); + }; + process.on("exit", onProcessExit); let release!: () => Promise; @@ -123,7 +127,7 @@ export async function lockInstanceDir(instanceDir: string) { release = await acquire(); writeOwnerMarker(); console.error( - `\nWARN: Instance directory lock ${lockFolder} was missing — recreated by pid ${process.pid}.\n`, + `\nWARN: Instance directory lock ${lockFolder} was missing; recreated by pid ${process.pid}.\n`, ); } catch (e) { console.error( @@ -146,7 +150,7 @@ export async function lockInstanceDir(instanceDir: string) { // reclaimed our stale lock. Either way don't recreate it (that would // thrash or steal); log and continue, matching prior tolerant behavior. console.error( - `\nWARN: User instance directory lock ${lockFolder} reported as compromised — continuing.\n ${err}`, + `\nWARN: User instance directory lock ${lockFolder} reported as compromised; continuing.\n ${err}`, ); return; } @@ -168,7 +172,7 @@ export async function lockInstanceDir(instanceDir: string) { throw err; } throw new Error( - `Unable to lock instance directory ${instanceDir}. Only one client per instance directory can be active at a time. Cause: ${e?.code ?? "unknown"} — ${e?.message ?? e}`, + `Unable to lock instance directory ${instanceDir}. Only one client per instance directory can be active at a time. Cause: ${e?.code ?? "unknown"} (${e?.message ?? e})`, ); } @@ -178,6 +182,7 @@ export async function lockInstanceDir(instanceDir: string) { // folder we created. All best effort. return async () => { isExiting = true; + process.removeListener("exit", onProcessExit); let released = false; try { await release(); From b1b5bcafdde8ba2387d669eec198eb70e8fa5986 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 17 Jul 2026 16:43:46 -0700 Subject: [PATCH 03/12] server now notifies when it's stale. server can be restarted via command. clients get a persistent message indicating stale server --- .../client/src/agentServerClient.ts | 11 + .../test/conversation-stubConnection.ts | 1 + .../agentServer/protocol/src/protocol.ts | 7 + .../server/src/connectionHandler.ts | 68 ++ ts/packages/agentServer/server/src/server.ts | 38 +- .../server/src/sharedDispatcher.ts | 9 + .../agentServer/server/src/staleBuild.ts | 150 +++++ .../serviceWorker/dispatcherConnection.ts | 1 + .../chat-ui/mockups/stale-server-notice.html | 635 ++++++++++++++++++ ts/packages/chat-ui/src/chatPanel.ts | 143 ++++ ts/packages/chat-ui/src/index.ts | 7 + ts/packages/chat-ui/src/statusNotice.ts | 73 ++ ts/packages/chat-ui/styles/chat.css | 162 +++++ ts/packages/cli/src/enhancedConsole.ts | 24 + ts/packages/cli/src/slashCommands.ts | 36 +- .../system/handlers/notifyCommandHandler.ts | 70 ++ .../src/context/system/systemAgent.ts | 25 + ts/packages/dispatcher/types/src/clientIO.ts | 7 + .../shell/src/renderer/src/chatPanelBridge.ts | 12 + ts/packages/vscode-shell/src/webview/main.ts | 7 + 20 files changed, 1481 insertions(+), 5 deletions(-) create mode 100644 ts/packages/agentServer/server/src/staleBuild.ts create mode 100644 ts/packages/chat-ui/mockups/stale-server-notice.html create mode 100644 ts/packages/chat-ui/src/statusNotice.ts diff --git a/ts/packages/agentServer/client/src/agentServerClient.ts b/ts/packages/agentServer/client/src/agentServerClient.ts index 5beadf58b..e5388b6c9 100644 --- a/ts/packages/agentServer/client/src/agentServerClient.ts +++ b/ts/packages/agentServer/client/src/agentServerClient.ts @@ -120,6 +120,12 @@ export type AgentServerConnection = { ): Promise; deleteConversation(conversationId: string): Promise; shutdown(): Promise; + /** + * Relaunch the agent-server process so it loads rebuilt code. The server + * exits and a successor takes its place - this connection drops, so the + * caller must reconnect (the port is reused). Standalone server only. + */ + restart(): Promise; /** * Request a short-lived Azure Speech authorization token from the server * (which owns the `speech:` config). Resolves to `undefined` when speech @@ -323,6 +329,11 @@ export function createAgentServerConnection( await rpc.invoke("shutdown"); }, + async restart(): Promise { + debug("Requesting server restart via existing connection"); + await rpc.invoke("restart"); + }, + async getSpeechToken(): Promise { return rpc.invoke("getSpeechToken"); }, diff --git a/ts/packages/agentServer/client/test/conversation-stubConnection.ts b/ts/packages/agentServer/client/test/conversation-stubConnection.ts index e377976c4..3b257cc0b 100644 --- a/ts/packages/agentServer/client/test/conversation-stubConnection.ts +++ b/ts/packages/agentServer/client/test/conversation-stubConnection.ts @@ -202,6 +202,7 @@ export function makeStubConnection( }, async shutdown() {}, + async restart() {}, async getSpeechToken() { return undefined; }, diff --git a/ts/packages/agentServer/protocol/src/protocol.ts b/ts/packages/agentServer/protocol/src/protocol.ts index 3967f2e07..94179335e 100644 --- a/ts/packages/agentServer/protocol/src/protocol.ts +++ b/ts/packages/agentServer/protocol/src/protocol.ts @@ -109,6 +109,13 @@ export type AgentServerInvokeFunctions = { ) => Promise; deleteConversation: (conversationId: string) => Promise; shutdown: () => Promise; + /** + * Relaunch the server process so it loads freshly-rebuilt code. Supported + * only by the standalone agent-server; the in-process (embedded) server + * rejects this call. Like shutdown, the current process exits - callers + * lose their connection and must reconnect to the successor. + */ + restart: () => Promise; getUserIdentity: () => Promise; /** * Vend a short-lived Azure Speech authorization token from the server's diff --git a/ts/packages/agentServer/server/src/connectionHandler.ts b/ts/packages/agentServer/server/src/connectionHandler.ts index 2ee19f4c6..bd347a98f 100644 --- a/ts/packages/agentServer/server/src/connectionHandler.ts +++ b/ts/packages/agentServer/server/src/connectionHandler.ts @@ -42,6 +42,20 @@ export type ConnectionHandlerDeps = { * embedded in-process server this typically quits the host app. */ shutdown: () => void | Promise; + /** + * Optional: relaunch the server process so it loads rebuilt code. Only the + * standalone agent-server supplies this - the in-process (embedded) server + * leaves it undefined, so `@server restart` and the restart RPC report that + * restart isn't supported there. + */ + restart?: () => void | Promise; + /** + * Optional: returns true when this server is running an out-of-date build + * (its code was rebuilt on disk after it started). When set, each joining + * client is warned once so the user knows to restart the server. Only the + * standalone agent-server supplies this. + */ + isStale?: () => boolean; /** Returns the current resolved user identity. */ getUserIdentity: () => UserIdentity; /** @@ -71,6 +85,8 @@ export function createAgentServerConnectionHandler( const { conversationManager, shutdown, + restart, + isStale, getUserIdentity, portRegistrar, onConnect, @@ -87,6 +103,10 @@ export function createAgentServerConnectionHandler( { dispatcher: Dispatcher; connectionId: string } >(); + // Warn this connection about a stale server build at most once, even + // if it joins several conversations. + let notifiedStale = false; + const invokeFunctions: AgentServerInvokeFunctions = { joinConversation: async (options?: DispatcherConnectOptions) => { // Resolve conversation ID first (may auto-create default) @@ -117,6 +137,16 @@ export function createAgentServerConnectionHandler( shutdown: () => { void shutdown(); }, + // Only expose restart when the host supports it (the + // standalone server). Left off for the in-process + // server so `@server restart` reports "not supported". + ...(restart !== undefined + ? { + restart: () => { + void restart(); + }, + } + : {}), }; const result = await conversationManager.joinConversation( @@ -154,6 +184,36 @@ export function createAgentServerConnectionHandler( connectionId: result.connectionId, }); + // If this server is serving out-of-date code, warn the + // freshly-joined client once so the user knows to restart + // it. Sent as chat-ui's STATUS_NOTICE_EVENT ("statusNotice") + // with a structured payload: the shells render a persistent + // toast/pill (with a Restart button) and the CLI prints a + // yellow line. Kept as a literal so the server needn't + // depend on the chat-ui (DOM) package. + if (isStale?.() === true && !notifiedStale) { + notifiedStale = true; + try { + clientIORpcClient.notify( + undefined, + "statusNotice", + { + id: "stale-build", + level: "warning", + title: "Server out of date", + message: + "This agent server was rebuilt on disk after it started, so it's running the old code.", + actionLabel: "Restart server", + actionCommand: "@server restart", + }, + "agent-server", + ); + } catch { + // Best effort - never fail a join because the + // stale-build warning couldn't be delivered. + } + } + const joinResult: JoinConversationResult = { connectionId: result.connectionId, conversationId, @@ -218,6 +278,14 @@ export function createAgentServerConnectionHandler( shutdown: async () => { await shutdown(); }, + restart: async () => { + if (restart === undefined) { + throw new Error( + "Restart is not supported for the in-process agent server.", + ); + } + await restart(); + }, getUserIdentity: async () => getUserIdentity(), getSpeechToken: async () => getSpeechToken(), }; diff --git a/ts/packages/agentServer/server/src/server.ts b/ts/packages/agentServer/server/src/server.ts index 7584713dd..cd665af27 100644 --- a/ts/packages/agentServer/server/src/server.ts +++ b/ts/packages/agentServer/server/src/server.ts @@ -7,6 +7,7 @@ import { ConversationManager, } from "./conversationManager.js"; import { createAgentServerConnectionHandler } from "./connectionHandler.js"; +import { startStaleBuildWatcher, isStaleBuild } from "./staleBuild.js"; import { getInstanceDirAsync, getTraceIdAsync, @@ -31,6 +32,7 @@ import { } from "@typeagent/agent-server-client"; import registerDebug from "debug"; import os from "node:os"; +import { spawn } from "node:child_process"; import { DefaultAzureCredential } from "@azure/identity"; // Load config from YAML layers + Key Vault (replacing legacy dotenv). @@ -229,11 +231,38 @@ async function main() { // Shared shutdown logic — used by RPC handler, idle timer, and clientIO intercept. // The wss variable is assigned after createWebSocketChannelServer resolves below. let wss: Awaited>; - async function shutdownServer() { - console.log("Shutdown requested, stopping agent server..."); + + // Stop listening, close conversations (which releases the instance-dir + // lock), and drop the PID file. Shared by shutdown and restart so a + // relaunched successor finds the port free and the lock released. + async function teardownServer() { wss.close(); await conversationManager.close(); removeServerPid(port); + } + + async function shutdownServer() { + console.log("Shutdown requested, stopping agent server..."); + await teardownServer(); + process.exit(0); + } + + // Restart in place: tear this process down, then relaunch an identical + // successor (same node flags + argv) that loads freshly-rebuilt code. The + // successor inherits this console, and `detached` + `unref` let it outlive + // us. Releasing the port/lock *before* spawning keeps the successor's bind + // and lock acquisition from racing this process. + async function restartServer() { + console.log( + "\x1b[1;30;43m Restart requested - relaunching agent server... \x1b[0m", + ); + await teardownServer(); + const child = spawn( + process.execPath, + [...process.execArgv, ...process.argv.slice(1)], + { detached: true, stdio: "inherit", windowsHide: false }, + ); + child.unref(); process.exit(0); } @@ -269,6 +298,8 @@ async function main() { const connectionHandler = createAgentServerConnectionHandler({ conversationManager, shutdown: shutdownServer, + restart: restartServer, + isStale: isStaleBuild, getUserIdentity: () => userIdentity, portRegistrar, onConnect: () => { @@ -304,6 +335,9 @@ async function main() { console.log(`Agent server started at ws://localhost:${port}`); writeServerPid(port, process.pid); + // Warn (once) in this console if the server's own build changes on disk + // while this process keeps running the old code. + startStaleBuildWatcher(import.meta.url); scheduleIdleShutdown(); } diff --git a/ts/packages/agentServer/server/src/sharedDispatcher.ts b/ts/packages/agentServer/server/src/sharedDispatcher.ts index 6392a1b8a..bfcf9142a 100644 --- a/ts/packages/agentServer/server/src/sharedDispatcher.ts +++ b/ts/packages/agentServer/server/src/sharedDispatcher.ts @@ -130,6 +130,15 @@ export async function createSharedDispatcher( callback(requestId, (clientIO) => clientIO.shutdown(requestId, ...args), ), + restart: (requestId, ...args) => + callback(requestId, (clientIO) => { + if (clientIO.restart === undefined) { + throw new Error( + "The connected host does not support restart.", + ); + } + return clientIO.restart(requestId, ...args); + }), setUserRequest: (requestId, ...args) => { broadcast("setUserRequest", requestId, (clientIO) => clientIO.setUserRequest(requestId, ...args), diff --git a/ts/packages/agentServer/server/src/staleBuild.ts b/ts/packages/agentServer/server/src/staleBuild.ts new file mode 100644 index 000000000..f56e31188 --- /dev/null +++ b/ts/packages/agentServer/server/src/staleBuild.ts @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { watch, statSync, type FSWatcher } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import registerDebug from "debug"; + +const debug = registerDebug("agent-server:stale-build"); + +// Set once a newer build is detected on disk. Read by the connection handler +// to warn clients (on join) that this process is serving out-of-date code. +let staleDetected = false; + +/** + * True once the running server's own `dist/` has been rebuilt on disk after + * this process started (i.e. it is now serving out-of-date code). Latches on + * and never clears - a relaunched successor starts a fresh process with its + * own baseline. + */ +export function isStaleBuild(): boolean { + return staleDetected; +} + +// Black text on a yellow background, bold - only emitted to a TTY so +// redirected logs (files, pipes) stay readable. +const YELLOW_BG = "\x1b[1;30;43m"; +const RESET = "\x1b[0m"; + +/** + * Print a hard-to-miss yellow banner announcing that the code on disk was + * rebuilt while this process kept running the old build. One-shot: called + * once, when staleness is first detected. + */ +function printStaleBanner(): void { + const lines = [ + "STALE BUILD", + "agent-server code on disk was rebuilt after this process started;", + "this window is still running the OLD build.", + "Restart to load the new code: @server restart (CLI: /restart)", + ]; + + if (process.stdout.isTTY !== true) { + // No colors/box for non-interactive logs - just make it greppable. + console.log(""); + for (const line of lines) { + console.log(`[stale-build] ${line}`); + } + console.log(""); + return; + } + + const width = Math.min(Math.max(process.stdout.columns ?? 80, 40), 100); + const inner = width - 4; // "| " + " |" + const bar = "-".repeat(width - 2); + const pad = (s: string) => { + const text = s.length > inner ? s.slice(0, inner - 3) + "..." : s; + return "| " + text + " ".repeat(inner - text.length) + " |"; + }; + const box = ["+" + bar + "+", ...lines.map(pad), "+" + bar + "+"]; + process.stdout.write( + "\n" + box.map((l) => `${YELLOW_BG}${l}${RESET}`).join("\n") + "\n\n", + ); +} + +function tryWatch( + dir: string, + recursive: boolean, + onChange: (event: string, filename: string | null) => void, +): FSWatcher | undefined { + try { + // persistent:false so this watcher never keeps the event loop alive. + return watch(dir, { recursive, persistent: false }, onChange); + } catch (e) { + debug(`watch(${dir}, recursive=${recursive}) failed: ${e}`); + return undefined; + } +} + +/** + * Watch the running server's own `dist/` directory and print a one-shot + * yellow banner the first time a newer build appears on disk. This catches + * the common dev loop: rebuild the agent-server, but its already-running + * process keeps serving the previously-loaded code. + * + * Scope: only the running package's own compiled output is watched. A rebuild + * of a *dependency* package that does not also re-emit this package's `dist/` + * won't trip the banner - matching "the agent server has been rebuilt". + * + * Best-effort: any failure to set up the watch is logged under + * `agent-server:stale-build` and otherwise ignored - it must never take the + * server down. + * + * @param entryUrl `import.meta.url` of the running entry module. + */ +export function startStaleBuildWatcher(entryUrl: string): void { + let distDir: string; + try { + distDir = path.dirname(fileURLToPath(entryUrl)); + } catch (e) { + debug(`could not resolve entry dir from ${entryUrl}: ${e}`); + return; + } + + // Baseline is "now", not the entry file's mtime: an incremental build that + // re-emits a sibling file without touching the entry still counts as a + // rebuild of code this process already loaded. + const startMs = Date.now(); + let fired = false; + let watcher: FSWatcher | undefined; + + const onChange = (_event: string, filename: string | null) => { + if (fired || filename === null) { + return; + } + const name = filename.toString(); + // Only compiled JS indicates rebuilt runtime code; ignore + // .map/.d.ts/.tsbuildinfo churn. + if (!name.endsWith(".js")) { + return; + } + let mtimeMs: number; + try { + mtimeMs = statSync(path.join(distDir, name)).mtimeMs; + } catch { + // File may have been renamed/removed mid-build. + return; + } + if (mtimeMs <= startMs) { + return; + } + fired = true; + staleDetected = true; + debug( + `stale build detected via ${name} (mtime ${mtimeMs} > ${startMs})`, + ); + printStaleBanner(); + watcher?.close(); + }; + + // Recursive first (covers dist subfolders); fall back to a flat watch on + // hosts/older runtimes where recursive watching isn't supported. + watcher = + tryWatch(distDir, true, onChange) ?? tryWatch(distDir, false, onChange); + if (watcher === undefined) { + debug(`stale-build watcher not started for ${distDir}`); + return; + } + debug(`watching ${distDir} for rebuilds (baseline ${startMs})`); +} diff --git a/ts/packages/agents/browser/src/extension/serviceWorker/dispatcherConnection.ts b/ts/packages/agents/browser/src/extension/serviceWorker/dispatcherConnection.ts index e07d9d879..1a068bcf3 100644 --- a/ts/packages/agents/browser/src/extension/serviceWorker/dispatcherConnection.ts +++ b/ts/packages/agents/browser/src/extension/serviceWorker/dispatcherConnection.ts @@ -569,6 +569,7 @@ function makeConnectionAdapter(): AgentServerConnection { return rpc.invoke("deleteConversation", id) as Promise; }, shutdown: () => notSupported("shutdown"), + restart: () => notSupported("restart"), getSpeechToken: () => { const { rpc } = requireFresh(); return rpc.invoke("getSpeechToken"); diff --git a/ts/packages/chat-ui/mockups/stale-server-notice.html b/ts/packages/chat-ui/mockups/stale-server-notice.html new file mode 100644 index 000000000..28ce2c7da --- /dev/null +++ b/ts/packages/chat-ui/mockups/stale-server-notice.html @@ -0,0 +1,635 @@ + + + + + + + + + stale-server notice - design options + + + +

Stale-server notice - design options

+

+ The server pushes this on connect when it's running out-of-date code. You + want it persistent (visible until dismissed) and are weighing + whether to pin it. Compare the treatments below - buttons are live + (dismiss, restart, minimize). "Replay" re-sends the notice, mimicking a + reconnect to a still-stale server. +

+
+ + +
+ +
+ + + + diff --git a/ts/packages/chat-ui/src/chatPanel.ts b/ts/packages/chat-ui/src/chatPanel.ts index f6909af53..e2f28c11a 100644 --- a/ts/packages/chat-ui/src/chatPanel.ts +++ b/ts/packages/chat-ui/src/chatPanel.ts @@ -34,6 +34,7 @@ import { type ConnectionStatus, type ConnectionActionHandler, } from "./connectionStatus.js"; +import { type StatusNotice, type StatusNoticeLevel } from "./statusNotice.js"; // Restrictive sanitize config used at .innerHTML sinks below. The HTML // passed in is built from values that, while in practice come from @@ -596,6 +597,14 @@ export class ChatPanel { * chat in rootElement, lazily created on first toast. */ private toastStack: HTMLDivElement | undefined; + /** + * Bottom-right overlay hosting persistent status notices (see + * showStatusNotice). Lazily created on first notice; distinct from the + * transient toastStack so the two never fight for the same corner. + */ + private statusNoticeLayer: HTMLDivElement | undefined; + /** Active status notices keyed by id -> their root element. */ + private statusNotices = new Map(); private commandHistory: string[] = []; private historyIndex = -1; /** Local user's display name + initial used in user-bubble headers. */ @@ -2635,6 +2644,137 @@ export class ChatPanel { this.toastStack.appendChild(toast); } + /** + * Show a persistent status notice: a bottom-right toast that stays until + * dismissed (unlike showToast's 5s auto-hide). The dismiss (×) collapses + * it to a small pinned pill in the same corner; clicking the pill + * re-expands it. Re-calling with the same `id` replaces the notice (e.g. a + * reconnect re-sending it). An optional action button runs `actionCommand` + * through the chat input, so the affordance works identically in every + * host with no extra wiring. + */ + public showStatusNotice(notice: StatusNotice): void { + if (!notice || !notice.id || !notice.title) { + return; + } + // Replace any existing notice with this id. + this.statusNotices.get(notice.id)?.remove(); + + const layer = this.ensureStatusNoticeLayer(); + const level: StatusNoticeLevel = notice.level ?? "info"; + + const root = document.createElement("div"); + root.className = `chat-status-notice level-${level}`; + root.dataset.id = notice.id; + + // Expanded toast. + const toast = document.createElement("div"); + toast.className = "csn-toast"; + + const head = document.createElement("div"); + head.className = "csn-head"; + const icon = document.createElement("span"); + icon.className = "csn-icon-wrap"; + // Fixed, non-user SVG literal — safe to assign as innerHTML. + icon.innerHTML = this.statusNoticeIconSvg(level); + head.appendChild(icon); + const titleEl = document.createElement("span"); + titleEl.className = "csn-title"; + titleEl.textContent = notice.title; + head.appendChild(titleEl); + const minBtn = document.createElement("button"); + minBtn.type = "button"; + minBtn.className = "csn-min"; + minBtn.title = "Minimize"; + minBtn.setAttribute("aria-label", "Minimize"); + minBtn.textContent = "×"; + head.appendChild(minBtn); + toast.appendChild(head); + + if (notice.message) { + const body = document.createElement("div"); + body.className = "csn-body"; + body.textContent = notice.message; + toast.appendChild(body); + } + + if (notice.actionLabel && notice.actionCommand) { + const actions = document.createElement("div"); + actions.className = "csn-actions"; + const actionBtn = document.createElement("button"); + actionBtn.type = "button"; + actionBtn.className = "csn-action"; + actionBtn.textContent = notice.actionLabel; + const command = notice.actionCommand; + actionBtn.addEventListener("click", () => { + this.injectCommand(command); + }); + actions.appendChild(actionBtn); + toast.appendChild(actions); + } + + // Collapsed pill (hidden until minimized via the .collapsed class). + const pill = document.createElement("button"); + pill.type = "button"; + pill.className = "csn-pill"; + pill.title = `${notice.title} — click to expand`; + pill.setAttribute("aria-label", `${notice.title} — click to expand`); + const dot = document.createElement("span"); + dot.className = "csn-dot"; + pill.appendChild(dot); + const pillLabel = document.createElement("span"); + pillLabel.className = "csn-pill-label"; + pillLabel.textContent = notice.title; + pill.appendChild(pillLabel); + + minBtn.addEventListener("click", () => root.classList.add("collapsed")); + pill.addEventListener("click", () => + root.classList.remove("collapsed"), + ); + + root.appendChild(toast); + root.appendChild(pill); + layer.appendChild(root); + this.statusNotices.set(notice.id, root); + } + + /** Remove a status notice by id, or all of them when `id` is omitted. */ + public clearStatusNotice(id?: string): void { + if (id === undefined) { + for (const el of this.statusNotices.values()) { + el.remove(); + } + this.statusNotices.clear(); + } else { + this.statusNotices.get(id)?.remove(); + this.statusNotices.delete(id); + } + if (this.statusNotices.size === 0 && this.statusNoticeLayer) { + this.statusNoticeLayer.remove(); + this.statusNoticeLayer = undefined; + } + } + + private ensureStatusNoticeLayer(): HTMLDivElement { + if (!this.statusNoticeLayer) { + const layer = document.createElement("div"); + layer.className = "chat-status-notice-layer"; + (this.messageDiv.parentElement ?? this.rootElement).appendChild( + layer, + ); + this.statusNoticeLayer = layer; + } + return this.statusNoticeLayer; + } + + private statusNoticeIconSvg(level: StatusNoticeLevel): string { + if (level === "info") { + return ''; + } + // warning + error share the triangle glyph; color comes from the level. + return ''; + } + /** * Show a compact inline row in the chat scroll (no bubble chrome, * single line, dim styling). Persists in scroll history. Does NOT @@ -3081,6 +3221,9 @@ export class ChatPanel { if (this.toastStack) { this.toastStack.replaceChildren(); } + // Drop persistent status notices too; they're re-pushed by the server + // on the next connect if the condition still holds. + this.clearStatusNotice(); // Reset scroll state and pill this.userHasManuallyScrolled = false; this.hideNewMessagesPill(); diff --git a/ts/packages/chat-ui/src/index.ts b/ts/packages/chat-ui/src/index.ts index 49fb3b67d..b56d0880f 100644 --- a/ts/packages/chat-ui/src/index.ts +++ b/ts/packages/chat-ui/src/index.ts @@ -54,6 +54,13 @@ export { type ConnectionActionHandler, } from "./connectionStatus.js"; +export { + STATUS_NOTICE_EVENT, + parseStatusNotice, + type StatusNotice, + type StatusNoticeLevel, +} from "./statusNotice.js"; + export { ChatContextMenu, ContextMenuTargetOptions } from "./contextMenu.js"; export type { diff --git a/ts/packages/chat-ui/src/statusNotice.ts b/ts/packages/chat-ui/src/statusNotice.ts new file mode 100644 index 000000000..a4b74aaaa --- /dev/null +++ b/ts/packages/chat-ui/src/statusNotice.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Host-neutral model for a persistent, dismissible status notice. + * + * Rendered by {@link ChatPanel.showStatusNotice} as a corner toast that the + * user can collapse to a small pinned pill (and re-expand) - it stays until + * dismissed rather than auto-hiding like {@link ChatPanel.showToast}. Used for + * durable, state-y conditions such as "the agent server is running + * out-of-date code" (pushed by the server on connect). Shared by every chat + * host (Electron shell, VS Code webview) so the UX is identical. + * + * Delivered over `ClientIO.notify` as `event === STATUS_NOTICE_EVENT` with a + * {@link StatusNotice} as `data`. Kept a plain serializable object so it + * crosses the IPC / postMessage boundary unchanged; the action button runs + * {@link StatusNotice.actionCommand} back through the chat input, so no + * host-specific callback wiring is needed. + */ + +/** + * `ClientIO.notify` event name that carries a {@link StatusNotice}. The event + * string is the wire contract with emitters (e.g. the agent-server), which + * send the same literal. + */ +export const STATUS_NOTICE_EVENT = "statusNotice"; + +export type StatusNoticeLevel = "info" | "warning" | "error"; + +export interface StatusNotice { + /** + * Stable identity for this notice. Re-showing the same `id` replaces the + * existing one (so a reconnect that re-sends it doesn't stack duplicates). + */ + id: string; + /** Severity accent; defaults to `info`. */ + level?: StatusNoticeLevel; + title: string; + message?: string; + /** Label for an optional action button. Requires {@link actionCommand}. */ + actionLabel?: string; + /** Command run (via the chat input) when the action button is clicked. */ + actionCommand?: string; +} + +/** + * Narrow untrusted `notify` `data` into a {@link StatusNotice}. Returns + * `undefined` when the payload isn't a well-formed notice, so hosts can guard + * at the RPC boundary. Only `id` and `title` are required. + */ +export function parseStatusNotice(data: unknown): StatusNotice | undefined { + if (typeof data !== "object" || data === null) { + return undefined; + } + const d = data as Record; + if (typeof d.id !== "string" || typeof d.title !== "string") { + return undefined; + } + const notice: StatusNotice = { id: d.id, title: d.title }; + if (d.level === "info" || d.level === "warning" || d.level === "error") { + notice.level = d.level; + } + if (typeof d.message === "string") { + notice.message = d.message; + } + if (typeof d.actionLabel === "string") { + notice.actionLabel = d.actionLabel; + } + if (typeof d.actionCommand === "string") { + notice.actionCommand = d.actionCommand; + } + return notice; +} diff --git a/ts/packages/chat-ui/styles/chat.css b/ts/packages/chat-ui/styles/chat.css index 5247f0af1..b89195641 100644 --- a/ts/packages/chat-ui/styles/chat.css +++ b/ts/packages/chat-ui/styles/chat.css @@ -2398,6 +2398,168 @@ span.ansi-bright-white-fg { transform: translateX(-50%) scale(0.98); } +/* ========================================================================= + Status Notice (persistent, dismissible corner toast <-> pinned pill) + Rendered by ChatPanel.showStatusNotice(). Self-contained colors (no theme + vars) so it reads on both light and dark host themes, matching the + .chat-reconnect-banner approach. + ========================================================================= */ + +.chat-status-notice-layer { + position: absolute; + right: 12px; + bottom: 70px; + z-index: 120; + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 8px; + pointer-events: none; + max-width: 320px; +} + +.chat-status-notice { + pointer-events: auto; +} + +.chat-status-notice.level-warning { + --csn-bg: #fff4ce; + --csn-fg: #5c3c00; + --csn-line: #e0a010; +} + +.chat-status-notice.level-error { + --csn-bg: #fde7e9; + --csn-fg: #6f1720; + --csn-line: #e5484d; +} + +.chat-status-notice.level-info { + --csn-bg: #e7f2ff; + --csn-fg: #0a3a66; + --csn-line: #3b82f6; +} + +.csn-toast { + background: var(--csn-bg); + color: var(--csn-fg); + border: 1px solid var(--csn-line); + border-left-width: 4px; + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28); + padding: 10px 12px; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + font-size: 12.5px; + line-height: 1.45; + width: 300px; + max-width: 78vw; +} + +.chat-status-notice.collapsed .csn-toast { + display: none; +} + +.csn-head { + display: flex; + align-items: center; + gap: 7px; + font-weight: 600; +} + +.csn-icon-wrap { + display: inline-flex; + color: var(--csn-line); + flex: 0 0 auto; +} + +.csn-title { + flex: 1 1 auto; + min-width: 0; +} + +.csn-min { + flex: 0 0 auto; + background: transparent; + border: none; + color: var(--csn-fg); + opacity: 0.7; + font-size: 16px; + line-height: 1; + cursor: pointer; + padding: 0 2px; +} + +.csn-min:hover { + opacity: 1; +} + +.csn-body { + margin-top: 5px; + opacity: 0.95; +} + +.csn-actions { + margin-top: 9px; + display: flex; + gap: 8px; +} + +.csn-action { + background: var(--csn-line); + color: #201800; + border: none; + border-radius: 5px; + padding: 5px 11px; + font: inherit; + font-size: 12px; + font-weight: 600; + cursor: pointer; +} + +.chat-status-notice.level-error .csn-action, +.chat-status-notice.level-info .csn-action { + color: #ffffff; +} + +.csn-action:hover { + filter: brightness(1.06); +} + +.csn-pill { + display: none; + align-items: center; + gap: 6px; + background: var(--csn-bg); + color: var(--csn-fg); + border: 1px solid var(--csn-line); + border-radius: 13px; + padding: 3px 10px 3px 8px; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + font-size: 11.5px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.22); +} + +.chat-status-notice.collapsed .csn-pill { + display: inline-flex; +} + +.csn-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--csn-line); + flex: 0 0 auto; +} + +.csn-pill-label { + white-space: nowrap; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; +} + /* Transient "Copied!" confirmation shown when a click-to-copy status badge (see setContent.ts) writes its message to the clipboard. The caller sets `left`/`top` in viewport coordinates; the transform centers it just above diff --git a/ts/packages/cli/src/enhancedConsole.ts b/ts/packages/cli/src/enhancedConsole.ts index 2dd4689ef..1b9f3250f 100644 --- a/ts/packages/cli/src/enhancedConsole.ts +++ b/ts/packages/cli/src/enhancedConsole.ts @@ -1096,6 +1096,30 @@ export function createEnhancedClientIO( displayToastNotification(data, source, timestamp); break; + case "statusNotice": { + // Persistent status notice (chat-ui STATUS_NOTICE_EVENT). + // The shells render a toast/pill; the console prints one + // yellow line, preserving the pre-existing behavior. + const notice = (data ?? {}) as { + title?: string; + message?: string; + actionCommand?: string; + }; + const text = [notice.title, notice.message] + .filter(Boolean) + .join(" \u2014 "); + const hint = notice.actionCommand + ? ` (${notice.actionCommand})` + : ""; + const line = `\u26a0 ${text}${hint}`; + if (currentSpinner?.isActive()) { + currentSpinner.writeAbove(chalk.yellow(line)); + } else { + console.warn(chalk.yellow(line)); + } + break; + } + case "grammarRule": // Grammar rule notifications - log to file to avoid disrupting prompt // View with: cat ~/.typeagent/grammar.log diff --git a/ts/packages/cli/src/slashCommands.ts b/ts/packages/cli/src/slashCommands.ts index c86bfd2e9..692136189 100644 --- a/ts/packages/cli/src/slashCommands.ts +++ b/ts/packages/cli/src/slashCommands.ts @@ -54,7 +54,9 @@ export function getConversationCommandContext(): // Late-binding server port for shutdown command. // Set from connect.ts after the connection is established. let serverPort: number | undefined; -let serverConnection: { shutdown(): Promise } | undefined; +let serverConnection: + | { shutdown(): Promise; restart(): Promise } + | undefined; export function setServerPort(port: number): void { serverPort = port; @@ -65,13 +67,13 @@ export function getServerPort(): number | undefined { } export function setServerConnection( - conn: { shutdown(): Promise } | undefined, + conn: { shutdown(): Promise; restart(): Promise } | undefined, ): void { serverConnection = conn; } export function getServerConnection(): - | { shutdown(): Promise } + | { shutdown(): Promise; restart(): Promise } | undefined { return serverConnection; } @@ -371,6 +373,34 @@ const slashCommands: SlashCommand[] = [ return { exit: true }; }, }, + { + name: "restart", + description: + "Restart the agent server (reload rebuilt code); this CLI disconnects", + handler: async () => { + const port = serverPort ?? AGENT_SERVER_DEFAULT_PORT; + if (!serverConnection) { + console.log( + chalk.red( + "Not connected to an agent server; cannot restart.", + ), + ); + return; + } + console.log( + chalk.dim( + `Requesting restart of server on port ${port}; it will relaunch and this CLI will disconnect. Reconnect with 'agent-cli connect'.`, + ), + ); + try { + await serverConnection.restart(); + } catch { + // Expected: the connection drops as the server exits to + // relaunch, so the restart RPC never returns cleanly. + } + return { exit: true }; + }, + }, { name: "queue", description: diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/notifyCommandHandler.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/notifyCommandHandler.ts index 8f8d72a74..a6264c16d 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/notifyCommandHandler.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/notifyCommandHandler.ts @@ -145,6 +145,75 @@ class NotifyTestCommandHandler implements CommandHandler { } } +class NotifyStatusTestCommandHandler implements CommandHandler { + public readonly description = + "Fire a persistent status notice (a toast that collapses to a pinned pill) to verify the chat-ui affordance without a stale server"; + public readonly parameters = { + args: { + message: { + description: "Notice body text", + implicitQuotes: true, + optional: true, + }, + }, + flags: { + level: { + description: "Severity accent: info | warning | error", + default: "warning", + }, + restart: { + description: + "Include a 'Restart server' action button (runs @server restart when clicked)", + type: "boolean", + default: false, + }, + }, + } as const; + + public async run( + context: ActionContext, + params: ParsedCommandParams, + ): Promise { + const systemContext = context.sessionContext.agentContext; + const levelArg = params.flags.level as string; + const level = ["info", "warning", "error"].includes(levelArg) + ? levelArg + : "warning"; + if (level !== levelArg) { + context.actionIO.appendDisplay( + { + type: "text", + content: `Unknown level '${levelArg}', using 'warning'.`, + kind: "warning", + }, + "block", + ); + } + const notice: Record = { + id: "notify-test-status", + level, + title: "Test status notice", + message: + params.args.message ?? + "Dismissing this collapses it to a pinned pill; click the pill to re-expand.", + }; + if (params.flags.restart) { + notice.actionLabel = "Restart server"; + notice.actionCommand = "@server restart"; + } + // "statusNotice" is chat-ui's STATUS_NOTICE_EVENT, kept as a literal so + // the dispatcher needn't depend on the chat-ui (DOM) package. Broadcast + // (notificationId undefined) so every connected client renders it: the + // shells show the toast/pill, the CLI prints a yellow line. + systemContext.clientIO.notify( + undefined, + "statusNotice", + notice, + "notify-test", + ); + } +} + export function getNotifyCommandHandlers(): CommandHandlerTable { return { description: "Notify commands", @@ -153,6 +222,7 @@ export function getNotifyCommandHandlers(): CommandHandlerTable { info: new NotifyInfoCommandHandler(), clear: new NotifyClearCommandHandler(), test: new NotifyTestCommandHandler(), + status: new NotifyStatusTestCommandHandler(), show: { description: "Show notifications", defaultSubCommand: "unread", diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/systemAgent.ts b/ts/packages/dispatcher/dispatcher/src/context/system/systemAgent.ts index 2b3d7ea1f..600c679ce 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/systemAgent.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/systemAgent.ts @@ -133,6 +133,31 @@ export const systemHandlers: CommandHandlerTable = { systemContext.clientIO.shutdown(getRequestId(systemContext)); }, }, + server: { + description: "Manage the agent server", + commands: { + restart: { + description: + "Restart the agent server so it loads rebuilt code", + async run(context: ActionContext) { + const systemContext = + context.sessionContext.agentContext; + // The routing clientIO always defines restart but + // throws when the connected host can't self-restart + // (e.g. the in-process shell). A host whose clientIO + // omits restart entirely lands here as undefined. + if (systemContext.clientIO.restart === undefined) { + throw new Error( + "Restart is only available when connected to a standalone agent server.", + ); + } + systemContext.clientIO.restart( + getRequestId(systemContext), + ); + }, + }, + }, + }, random: getRandomCommandHandlers(), notify: getNotifyCommandHandlers(), token: getTokenCommandHandlers(), diff --git a/ts/packages/dispatcher/types/src/clientIO.ts b/ts/packages/dispatcher/types/src/clientIO.ts index db1cd808a..26c7e572d 100644 --- a/ts/packages/dispatcher/types/src/clientIO.ts +++ b/ts/packages/dispatcher/types/src/clientIO.ts @@ -66,6 +66,13 @@ export interface ClientIO { clear(requestId: RequestId): void; exit(requestId: RequestId): void; shutdown(requestId: RequestId): void; + /** + * Relaunch the host process (agent-server) so it loads rebuilt code. + * Optional: only the standalone agent-server implements it. Hosts that + * can't self-restart (embedded/in-process) leave it undefined, and + * `@server restart` reports that restart isn't available. + */ + restart?(requestId: RequestId): void; // Display setUserRequest(requestId: RequestId, command: string, seq?: number): void; diff --git a/ts/packages/shell/src/renderer/src/chatPanelBridge.ts b/ts/packages/shell/src/renderer/src/chatPanelBridge.ts index 09adbcdee..bfef20070 100644 --- a/ts/packages/shell/src/renderer/src/chatPanelBridge.ts +++ b/ts/packages/shell/src/renderer/src/chatPanelBridge.ts @@ -22,6 +22,8 @@ import { SettingsPanelSchema, HelpPanelContent, formatHistorySeparatorLabel, + STATUS_NOTICE_EVENT, + parseStatusNotice, type TemplateEditServices, type ConnectionStatus, } from "chat-ui"; @@ -908,6 +910,16 @@ export function createChatPanelClient( case "showNotifications": handleShowNotifications(data); break; + case STATUS_NOTICE_EVENT: { + // Persistent, dismissible server/status notice rendered as + // a toast that collapses to a pinned pill (chat-ui owns the + // behavior). Used e.g. for the stale-build warning. + const notice = parseStatusNotice(data); + if (notice) { + chatPanel.showStatusNotice(notice); + } + break; + } case AppAgentEvent.Error: case AppAgentEvent.Warning: case AppAgentEvent.Info: diff --git a/ts/packages/vscode-shell/src/webview/main.ts b/ts/packages/vscode-shell/src/webview/main.ts index 0dd1aff55..f8d4215a4 100644 --- a/ts/packages/vscode-shell/src/webview/main.ts +++ b/ts/packages/vscode-shell/src/webview/main.ts @@ -12,6 +12,8 @@ import { ConversationBar, HistoryEntry, formatHistorySeparatorLabel, + STATUS_NOTICE_EVENT, + parseStatusNotice, type ConnectionStatus, } from "chat-ui"; import type { TemplateEditServices, DynamicDisplayResult } from "chat-ui"; @@ -899,6 +901,11 @@ window.addEventListener("message", (event) => { chatPanel.showInline(msg.data, msg.source); } else if (msg.event === "toast") { chatPanel.showToast(msg.data, msg.source); + } else if (msg.event === STATUS_NOTICE_EVENT) { + const notice = parseStatusNotice(msg.data); + if (notice) { + chatPanel.showStatusNotice(notice); + } } else { chatPanel.addSystemMessage(`[${msg.source}] ${msg.event}`); } From 4e8fc845ee61cfcc2a6fb5a0bae2efd6629b6a32 Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Fri, 17 Jul 2026 23:54:52 +0000 Subject: [PATCH 04/12] docs: regenerate README.AUTOGEN.md for changed packages --- .../agentServer/client/README.AUTOGEN.md | 77 ++++++++++--------- .../agentServer/protocol/README.AUTOGEN.md | 61 ++++++++------- .../agentServer/server/README.AUTOGEN.md | 73 ++++++++++++------ ts/packages/agents/browser/README.AUTOGEN.md | 30 ++++---- ts/packages/chat-ui/README.AUTOGEN.md | 20 ++--- ts/packages/cli/README.AUTOGEN.md | 14 ++-- .../dispatcher/dispatcher/README.AUTOGEN.md | 12 +-- .../dispatcher/types/README.AUTOGEN.md | 28 +++---- ts/packages/shell/README.AUTOGEN.md | 8 +- ts/packages/vscode-shell/README.AUTOGEN.md | 8 +- 10 files changed, 182 insertions(+), 149 deletions(-) diff --git a/ts/packages/agentServer/client/README.AUTOGEN.md b/ts/packages/agentServer/client/README.AUTOGEN.md index 7d68939ad..4299cec39 100644 --- a/ts/packages/agentServer/client/README.AUTOGEN.md +++ b/ts/packages/agentServer/client/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # @typeagent/agent-server-client — AI-generated documentation @@ -12,60 +12,65 @@ ## Overview -The `@typeagent/agent-server-client` package is a TypeScript library designed to facilitate communication with a running `agentServer`. It provides tools for managing server connections, handling conversations, and ensuring the server's availability. This package is a key component of the TypeAgent ecosystem, used by various clients such as the Shell, CLI, and other integrations. +The `@typeagent/agent-server-client` package is a TypeScript library that provides tools for interacting with a running `agentServer`. It enables clients to manage server connections, handle conversations, and ensure the server's availability. This package is a core component of the TypeAgent ecosystem and is utilized by various clients, including the Shell, CLI, and other integrations. ## What it does -The `@typeagent/agent-server-client` package provides the following core functionalities: +The `@typeagent/agent-server-client` package offers the following key functionalities: -- **Connection Management**: The `connectAgentServer` function establishes WebSocket connections to an `agentServer` and returns an `AgentServerConnection` object. This object enables interaction with the server and provides methods for managing conversations. -- **Conversation Management**: The `AgentServerConnection` object includes methods for creating, listing, renaming, and deleting conversations. It also allows clients to join and leave conversations. -- **Server Management**: Functions like `ensureAgentServer` and `isServerRunning` help ensure that the `agentServer` is operational, spawn it if necessary, and verify its status. -- **Convenience Wrappers**: Methods such as `ensureAndConnectConversation` combine multiple steps (e.g., ensuring the server is running, connecting, and joining a conversation) into a single operation. -- **Discovery**: The `discovery` module provides tools for locating the dynamically assigned port of an in-process agent, which is particularly useful for external clients like browser extensions or IDE plugins. +- **Server Connection Management**: + + - The `connectAgentServer` function establishes a WebSocket connection to an `agentServer` and returns an `AgentServerConnection` object. This object provides methods for managing conversations and interacting with the server. + - The `isServerRunning` function checks if an `agentServer` is already running at a specified WebSocket URL. + - The `ensureAgentServer` function ensures that the `agentServer` is running, spawning it if necessary. + +- **Conversation Management**: + + - The `AgentServerConnection` object includes methods for creating, listing, renaming, and deleting conversations. It also allows clients to join and leave conversations. + - The `conversation` module provides additional utilities for managing conversation lifecycles, such as finding or creating conversations, switching between conversations, and handling conversation names. + +- **Convenience Wrappers**: + + - The `ensureAndConnectConversation` function combines multiple steps into a single operation, including ensuring the server is running, connecting to it, and joining a conversation. + +- **Discovery**: + - The `discovery` module helps external clients locate the dynamically assigned port of an in-process agent, which is useful for browser extensions, IDE plugins, and other external integrations. These features make the package essential for applications that need to interact with the `agentServer` for conversation and server lifecycle management. ## Setup -To use this package, you need to configure the following: +To use this package, follow these steps: + +1. **Install the package**: + Use `pnpm` to install the package and its dependencies: -- **Environment Variable**: + ```bash + pnpm install + ``` - - `TYPEAGENT_TUNNEL_TOKEN`: This token is required for certain server interactions. Refer to the hand-written README for guidance on obtaining and setting this value. +2. **Set up environment variables**: -- **Installation**: - Install the package and its dependencies using `pnpm`: - ```bash - pnpm install - ``` + - `TYPEAGENT_TUNNEL_TOKEN`: This environment variable is required for certain server interactions. Refer to the hand-written README for instructions on obtaining and setting this value. -Ensure that the `TYPEAGENT_TUNNEL_TOKEN` environment variable is set in your shell or `.env` file before running any code that interacts with the `agentServer`. +3. **Configure your environment**: + Ensure that the `TYPEAGENT_TUNNEL_TOKEN` environment variable is set in your shell or `.env` file before running any code that interacts with the `agentServer`. ## Key Files -The package is structured into several key files, each responsible for specific functionality: +The package is organized into several key files, each with specific responsibilities: - **[index.ts](./src/index.ts)**: The main entry point of the package, exporting core functions and types for external use. -- **[agentServerClient.ts](./src/agentServerClient.ts)**: Contains the primary logic for connecting to the `agentServer`, managing conversations, and ensuring the server is running. -- **[discovery.ts](./src/discovery.ts)**: Implements functionality for discovering the dynamically assigned port of an in-process agent, which is useful for external clients. +- **[agentServerClient.ts](./src/agentServerClient.ts)**: Implements the primary logic for connecting to the `agentServer`, managing conversations, and ensuring the server is running. +- **[discovery.ts](./src/discovery.ts)**: Provides functionality for discovering the dynamically assigned port of an in-process agent, useful for external clients like browser extensions or IDE plugins. - **[conversation/index.ts](./src/conversation/index.ts)**: Aggregates shared conversation-lifecycle helpers for clients of the `agentServer`. -- **[conversation/lifecycle.ts](./src/conversation/lifecycle.ts)**: Provides connection-level lifecycle helpers, such as joining or creating conversations safely. +- **[conversation/lifecycle.ts](./src/conversation/lifecycle.ts)**: Contains connection-level lifecycle helpers, such as joining or creating conversations safely. - **[conversation/manage.ts](./src/conversation/manage.ts)**: Implements the `manage-conversation` client-action surface, including subcommands like `new`, `list`, `rename`, and `delete`. -- **[conversation/naming.ts](./src/conversation/naming.ts)**: Offers utilities for handling conversation names, including normalization and uniqueness checks. - -### Key Functions and Classes - -- **`connectAgentServer(url, onDisconnect?)`**: Establishes a WebSocket connection to the `agentServer` and returns an `AgentServerConnection` object. -- **`AgentServerConnection`**: Provides methods for managing conversations, such as `joinConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. -- **`ensureAgentServer(port?, hidden?, idleTimeout?)`**: Ensures the `agentServer` is running, spawning it if necessary. -- **`isServerRunning(url)`**: Checks if a server is already listening at the specified WebSocket URL. -- **`stopAgentServer(port?)`**: Sends a shutdown RPC to the running server. -- **`ensureAndConnectConversation(clientIO, port?, options?, onDisconnect?, hidden?, idleTimeout?)`**: Combines server management, connection, and conversation joining into a single call. +- **[conversation/naming.ts](./src/conversation/naming.ts)**: Provides utilities for handling conversation names, including normalization and uniqueness checks. ## How to extend -To extend the functionality of this package, follow these steps: +To extend the functionality of the `@typeagent/agent-server-client` package, follow these steps: 1. **Identify the area to extend**: @@ -97,9 +102,9 @@ By following these steps, you can effectively extend the capabilities of the `@t ### Entry points -- default → `./dist/index.js` _(not found on disk)_ -- `./conversation` → `./dist/conversation/index.js` _(not found on disk)_ -- `./discovery` → `./dist/discovery.js` _(not found on disk)_ +- default → [./dist/index.js](./dist/index.js) +- `./conversation` → [./dist/conversation/index.js](./dist/conversation/index.js) +- `./discovery` → [./dist/discovery.js](./dist/discovery.js) ### Dependencies @@ -145,6 +150,6 @@ _1 environment variable referenced from `./src/` (set in `ts/.env` or your shell --- -_Auto-generated against commit `463e6bf5c6f8eeaf9cc7512e33f3976761eece62` on `2026-07-10T09:05:05.791Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/agent-server-client docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/agent-server-client docs:verify-links` to spot-check._ diff --git a/ts/packages/agentServer/protocol/README.AUTOGEN.md b/ts/packages/agentServer/protocol/README.AUTOGEN.md index e6bb0e306..b79c03f0e 100644 --- a/ts/packages/agentServer/protocol/README.AUTOGEN.md +++ b/ts/packages/agentServer/protocol/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # @typeagent/agent-server-protocol — AI-generated documentation @@ -12,36 +12,36 @@ ## Overview -The `@typeagent/agent-server-protocol` package defines the WebSocket RPC contract between the agentServer and its clients. It provides the foundational types, channel names, and methods required for managing conversations, client connections, and discovery mechanisms within the Type Agent Server ecosystem. +The `@typeagent/agent-server-protocol` package defines the WebSocket RPC contract between the agentServer and its clients. It provides the necessary types, channel names, and methods for managing conversations, client connections, and discovery mechanisms within the TypeAgent Server ecosystem. ## What it does -This package serves as the protocol layer for communication between the agentServer and its clients. It includes: +This package acts as the communication protocol layer for the agentServer and its clients. It standardizes the interaction between components in the TypeAgent ecosystem by defining: -- **Channel Definitions**: Fixed and session-namespaced channel names for conversation lifecycle and discovery RPC. -- **Conversation Metadata**: Types and structures for describing conversations, including their lifecycle and participants. -- **RPC Methods**: A set of methods exposed on the `agent-server` channel for managing conversations, such as creating, joining, renaming, and deleting conversations. -- **Client-Type Registry**: Utilities for registering and retrieving client types based on connection IDs, enabling client-specific behavior. -- **Discovery Mechanism**: A read-only RPC channel for external clients to look up the live port of in-process app-agents. +- **Channel Definitions**: Includes fixed channel names for conversation lifecycle and discovery RPC, as well as session-namespaced channel names for dispatcher and client IO communication. +- **Conversation Metadata**: Provides types and structures such as `ConversationInfo` and `JoinConversationResult` to describe conversations and their participants. +- **RPC Methods**: Implements methods for managing conversations, including `joinConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. +- **Client-Type Registry**: Offers utilities to register, retrieve, and manage client types based on connection IDs, enabling client-specific behavior. +- **Discovery Mechanism**: Exposes a read-only RPC channel for external clients to discover live ports of in-process app-agents. -The package is used by various components in the TypeAgent ecosystem, including the `agent-server` itself, client libraries, and extensions like VS Code and browser-based agents. +This package is a core dependency for several other components in the TypeAgent ecosystem, such as `agent-server`, `agent-server-client`, and various extensions like VS Code and browser-based agents. ### Key Features 1. **Channel Names**: - - `AgentServerChannelName`: Fixed channel for conversation lifecycle RPC. - - `DiscoveryChannelName`: Fixed channel for read-only port discovery. - - Session-namespaced channels for dispatcher and client IO communication, constructed using helper functions like `getDispatcherChannelName` and `getClientIOChannelName`. + - `AgentServerChannelName`: A fixed channel for conversation lifecycle RPC. + - `DiscoveryChannelName`: A fixed channel for read-only port discovery. + - Helper functions like `getDispatcherChannelName` and `getClientIOChannelName` to construct session-namespaced channels. 2. **Conversation Management**: - - Types like `ConversationInfo` and `JoinConversationResult` describe conversation metadata and results. - - RPC methods such as `joinConversation`, `createConversation`, `listConversations`, and `deleteConversation` enable full lifecycle management of conversations. + - Types such as `ConversationInfo` and `JoinConversationResult` provide structured data about conversations and their participants. + - RPC methods allow for creating, joining, renaming, and deleting conversations, as well as listing all active conversations. 3. **Discovery Channel**: - - Provides a `lookupPort` method for external clients to discover live ports of app-agents, facilitating dynamic connection management. + - The `lookupPort` method enables external clients to query the live port of app-agents, facilitating dynamic connection management. 4. **Client-Type Registry**: - Functions like `registerClientType`, `getClientType`, and `unregisterClient` allow the server to track and manage client types based on their connection IDs. @@ -54,31 +54,34 @@ This package does not require any special setup beyond installation. To include pnpm install ``` -For more details on usage, refer to the hand-written README. +For additional details on usage, refer to the hand-written README. ## Key Files -The package is organized into several key files, each serving a specific purpose: +The package is structured into several key files, each with a specific role in defining and implementing the protocol: - **[index.ts](./src/index.ts)**: The main entry point, re-exporting all types, constants, and functions from other files. - **[protocol.ts](./src/protocol.ts)**: Contains the core protocol definitions, including channel names, conversation types, and RPC methods. -- **[queue.ts](./src/queue.ts)**: Re-exports queue-related types and errors from `@typeagent/dispatcher-types` for convenience. +- **[queue.ts](./src/queue.ts)**: Re-exports queue-related types and errors from `@typeagent/dispatcher-types` for use by clients. - **[tsconfig.json](./src/tsconfig.json)**: TypeScript configuration for the package. ### File Responsibilities 1. **[protocol.ts](./src/protocol.ts)**: - - Defines the fixed and session-namespaced channel names. - - Provides types like `ConversationInfo`, `JoinConversationResult`, and `DispatcherConnectOptions`. - - Implements the `AgentServerInvokeFunctions` interface, which includes methods for conversation management and server control. + - Defines fixed channel names like `AgentServerChannelName` and `DiscoveryChannelName`. + - Provides helper functions for constructing session-namespaced channel names, such as `getDispatcherChannelName` and `getClientIOChannelName`. + - Implements types like `ConversationInfo`, `JoinConversationResult`, and `DispatcherConnectOptions`. + - Defines the `AgentServerInvokeFunctions` interface, which includes methods for conversation management and server control. 2. **[queue.ts](./src/queue.ts)**: - Re-exports queue-related types and errors, such as `QueueRequestState` and `QueueFullError`, from `@typeagent/dispatcher-types`. + - This allows clients to access queue-related functionality without requiring a direct dependency on `@typeagent/dispatcher-types`. 3. **[index.ts](./src/index.ts)**: - - Serves as the public API surface, re-exporting all relevant types, constants, and functions from `protocol.ts` and `queue.ts`. + - Serves as the public API surface for the package. + - Re-exports all relevant types, constants, and functions from `protocol.ts` and `queue.ts`. ## How to extend @@ -86,17 +89,17 @@ To extend the functionality of this package, follow these steps: 1. **Understand the Existing Structure**: - - Start by reviewing the `protocol.ts` file, which contains the core definitions and RPC methods. + - Begin by reviewing the `protocol.ts` file, which contains the core protocol definitions and RPC methods. - Familiarize yourself with the exported types and functions in `index.ts`. 2. **Add New Features**: - - Define new types or methods in `protocol.ts`. For example, you might add a new RPC method for advanced conversation filtering. + - Define new types or methods in `protocol.ts`. For example, you might add a new RPC method to support additional server-client interactions. - If the new feature involves queue management, consider adding relevant types or errors in `queue.ts`. 3. **Export New Additions**: - - Update `index.ts` to export any new types or methods added to `protocol.ts` or `queue.ts`. + - Update `index.ts` to include any new types or methods added to `protocol.ts` or `queue.ts`. 4. **Test Your Changes**: @@ -104,9 +107,9 @@ To extend the functionality of this package, follow these steps: 5. **Document Your Changes**: - Update the hand-written README or other documentation to reflect the new functionality. - - Ensure that the new types and methods are well-documented with comments in the source code. + - Add comments to the source code to explain the purpose and usage of the new types and methods. -By following these steps, you can effectively extend the capabilities of the `@typeagent/agent-server-protocol` package while maintaining consistency with its existing structure and purpose. +By following these steps, you can ensure that your contributions align with the existing structure and maintain the quality and consistency of the `@typeagent/agent-server-protocol` package. ## Reference @@ -114,7 +117,7 @@ By following these steps, you can effectively extend the capabilities of the `@t ### Entry points -- default → `./dist/index.js` _(not found on disk)_ +- default → [./dist/index.js](./dist/index.js) ### Dependencies @@ -143,6 +146,6 @@ External: _None at runtime._ --- -_Auto-generated against commit `15ef5aa0362e3296bd9d6bd2f001fab704375d27` on `2026-07-06T09:20:03.630Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/agent-server-protocol docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/agent-server-protocol docs:verify-links` to spot-check._ diff --git a/ts/packages/agentServer/server/README.AUTOGEN.md b/ts/packages/agentServer/server/README.AUTOGEN.md index 2f3c571ec..60057f9cc 100644 --- a/ts/packages/agentServer/server/README.AUTOGEN.md +++ b/ts/packages/agentServer/server/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-server — AI-generated documentation @@ -16,14 +16,14 @@ The `agent-server` package is a TypeScript library that provides a long-running ## What it does -The `agent-server` package serves as the backbone for managing conversations and facilitating communication between clients and agents. It provides the following key functionalities: +The `agent-server` package is a core component of the TypeAgent ecosystem, enabling communication between clients and agents through WebSocket connections. It provides the following key functionalities: -- **Conversation Management**: The server supports actions like `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. These actions allow clients to manage and interact with conversations effectively. -- **Server Control**: The `shutdown` action enables a controlled shutdown of the server, ensuring that resources are released properly. +- **Conversation Management**: The server supports actions such as `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. These actions allow clients to manage and interact with conversations. +- **Server Control**: The `shutdown` action allows for a controlled shutdown of the server, ensuring proper resource cleanup. - **Idle Timeout**: The server can be configured to automatically shut down after a specified period of inactivity using the `--idle-timeout` flag. - **Ephemeral Conversation Cleanup**: On startup, the server removes temporary conversations (e.g., those created by CLI processes) to free up resources. -The server listens on a WebSocket endpoint and processes these actions by interacting with its core components, such as the `ConversationManager` and `SharedDispatcher`. It integrates with other TypeAgent packages, including `@typeagent/agent-server-client` and `@typeagent/agent-server-protocol`, to provide a consistent framework for agent-based communication. +The server integrates with other TypeAgent packages, such as `@typeagent/agent-server-client` and `@typeagent/agent-server-protocol`, to provide a consistent framework for agent-based communication. It also supports integration with external services like the Microsoft Speech SDK for speech-related functionalities. ## Setup @@ -37,25 +37,50 @@ To set up the `agent-server`, you need to configure the following environment va - `TYPEAGENT_USER_NAME`: The username for the TypeAgent user. This can override the default OS user name. - `XDG_CONFIG_HOME`: Specifies the base directory for configuration files. If not set, the default is typically `~/.config`. -You can set these variables in your environment or define them in a `.env` file in the `ts/` directory. For additional details, refer to the hand-written README. +These variables can be set in your environment or defined in a `.env` file in the `ts/` directory. For additional details, refer to the hand-written README. + +To start the server, you can use the following commands: + +- **With `pnpm`**: + + ```bash + pnpm --filter agent-server start + ``` + + Optionally, you can specify a configuration file: + + ```bash + pnpm --filter agent-server start -- --config + ``` + +- **With Node.js directly**: + ```bash + node --disable-warning=DEP0190 packages/agentServer/server/dist/server.js + ``` + To use a specific configuration file: + ```bash + node --disable-warning=DEP0190 packages/agentServer/server/dist/server.js --config + ``` + +The server listens on `ws://localhost:8999` by default. It can also be started automatically when clients call `ensureAgentServer()`. ## Key Files -The `agent-server` package is organized into several key files, each responsible for specific functionality: +The `agent-server` package is structured into several key files, each with specific responsibilities: -### [server.ts](./src/server.ts) — WebSocket Listener +### [server.ts](./src/server.ts) -This file initializes the WebSocket server and manages client connections. It performs the following tasks: +This file initializes the WebSocket server and manages client connections. Key responsibilities include: -1. Creates a `ConversationManager` instance to handle conversation-related operations. -2. Sets up the WebSocket server using `createWebSocketChannelServer`. -3. Exposes RPC functions over the `agent-server` channel, including: +1. Creating a `ConversationManager` instance to handle conversation-related operations. +2. Setting up the WebSocket server using `createWebSocketChannelServer`. +3. Exposing RPC functions over the `agent-server` channel, such as: - `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation` for conversation management. - `shutdown` for graceful server termination. -### [conversationManager.ts](./src/conversationManager.ts) — Conversation Pool +### [conversationManager.ts](./src/conversationManager.ts) -This file manages the lifecycle of conversations and their associated dispatchers. Key responsibilities include: +This file manages the lifecycle of conversations and their associated dispatchers. Key features include: - **Persistence**: Stores conversation metadata and data in the user's configuration directory. - **Lazy Initialization**: Creates `SharedDispatcher` instances only when a conversation is accessed. @@ -63,7 +88,7 @@ This file manages the lifecycle of conversations and their associated dispatcher - **Ephemeral Cleanup**: Deletes temporary conversations (e.g., `cli-ephemeral-*`) on server startup. - **Idle Shutdown**: Monitors client activity and shuts down the server after a period of inactivity if the `--idle-timeout` flag is set. -### [sharedDispatcher.ts](./src/sharedDispatcher.ts) — Routing Layer +### [sharedDispatcher.ts](./src/sharedDispatcher.ts) This file manages multiple client connections within a single conversation. It provides the following functionality: @@ -71,19 +96,19 @@ This file manages multiple client connections within a single conversation. It p - Routes client IO methods (e.g., `setDisplay`, `askYesNo`) to the appropriate client based on their `connectionId`. - Ensures that each client's interactions are isolated while sharing the same dispatcher and conversation context. -### [connectionHandler.ts](./src/connectionHandler.ts) — Connection Management +### [connectionHandler.ts](./src/connectionHandler.ts) This file defines the logic for handling individual client connections. It sets up the necessary RPC channels and integrates with the `ConversationManager` to manage conversations for each connected client. -### [copilot/displayLogSynthesis.ts](./src/copilot/displayLogSynthesis.ts) — Display Log Synthesis +### [copilot/displayLogSynthesis.ts](./src/copilot/displayLogSynthesis.ts) -This file synthesizes display logs from Copilot session data, enabling imported sessions to be replayed in the conversation UI. It ensures that the synthesized logs are deterministic and idempotent, allowing for consistent re-imports. +This file synthesizes display logs from Copilot session data, enabling imported sessions to be replayed in the conversation UI. It ensures that the synthesized logs are consistent and can be re-imported without issues. -### [copilot/mirrorImporter.ts](./src/copilot/mirrorImporter.ts) — Copilot Session Importer +### [copilot/mirrorImporter.ts](./src/copilot/mirrorImporter.ts) This file provides functionality to import Copilot sessions into the `agent-server`. It supports filtering sessions by repository, timestamp, and session ID, and integrates with the `ConversationManager` to create or update conversations based on the imported data. -### [inProcessAgentServer.ts](./src/inProcessAgentServer.ts) — In-Process Server +### [inProcessAgentServer.ts](./src/inProcessAgentServer.ts) This file provides an in-process implementation of the agent server, allowing it to be embedded within other applications. It includes options for user identity resolution, idle timeout, and shutdown handling. @@ -93,7 +118,7 @@ To extend the `agent-server` package, follow these steps: 1. **Understand the Core Components**: - - Start with [server.ts](./src/server.ts) to understand how the WebSocket server is initialized and how RPC functions are exposed. + - Begin with [server.ts](./src/server.ts) to understand how the WebSocket server is initialized and how RPC functions are exposed. - Review [conversationManager.ts](./src/conversationManager.ts) to understand how conversations are managed and persisted. - Examine [sharedDispatcher.ts](./src/sharedDispatcher.ts) to see how client connections are routed within a conversation. @@ -114,7 +139,7 @@ To extend the `agent-server` package, follow these steps: - Use the provided utilities in `status.ts` and `stop.ts` to control the server during testing. - Write unit tests for your changes to ensure they work as expected. -By following these guidelines, you can effectively extend the `agent-server` package to support additional functionality or integrate it with other systems. +By following these steps, you can effectively extend the `agent-server` package to support additional functionality or integrate it with other systems. ## Reference @@ -149,7 +174,7 @@ External: `@azure/identity`, `better-sqlite3`, `debug`, `dotenv`, `ws` ### Files of interest -`./src/connectionHandler.ts`, `./src/conversationManager.ts`, `./src/copilot/displayLogSynthesis.ts`, …and 11 more under `./src/`. +`./src/connectionHandler.ts`, `./src/conversationManager.ts`, `./src/copilot/displayLogSynthesis.ts`, …and 12 more under `./src/`. ### Environment variables @@ -165,6 +190,6 @@ _7 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `defc71271dc68db47e0d376be7aa9f755da0ac91` on `2026-07-14T08:47:00.044Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-server docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-server docs:verify-links` to spot-check._ diff --git a/ts/packages/agents/browser/README.AUTOGEN.md b/ts/packages/agents/browser/README.AUTOGEN.md index d062beba4..9acb78839 100644 --- a/ts/packages/agents/browser/README.AUTOGEN.md +++ b/ts/packages/agents/browser/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # browser-typeagent — AI-generated documentation @@ -12,17 +12,17 @@ ## Overview -The `browser-typeagent` package is a TypeAgent application agent designed for browser automation and control. It enables programmatic interaction with browser windows, tabs, and web pages through a defined set of actions. This package integrates with the TypeAgent shell and CLI, allowing users to perform browser-related tasks using commands or natural language. +The `browser-typeagent` package is a TypeAgent application agent designed for browser automation and control. It enables programmatic interaction with browser windows, tabs, and web pages through a defined set of actions. This package integrates with the TypeAgent shell and CLI, allowing users to perform browser-related tasks using commands or natural language. It also includes a browser extension for enhanced functionality and interaction. ## What it does -The `browser-typeagent` package provides a comprehensive set of browser automation capabilities, including: +The `browser-typeagent` package provides a wide range of browser automation capabilities, including: -- **Navigation**: Actions such as `openWebPage`, `goBack`, `goForward`, and `reloadPage` allow users to navigate between web pages and control browser tabs. -- **Interaction**: Users can interact with web content using actions like `clickOn`, `followLinkByText`, `scrollDown`, and `scrollUp`. -- **Content Capture and Extraction**: Actions such as `captureScreenshot`, `getHtmlFragments`, and `indexPage` enable users to extract and analyze web content. +- **Web Navigation**: Actions such as `openWebPage`, `goBack`, `goForward`, and `reloadPage` allow users to navigate between web pages and control browser tabs. +- **User Interaction**: Users can interact with web content using actions like `clickOn`, `followLinkByText`, `scrollDown`, and `scrollUp`. +- **Content Capture and Analysis**: Actions such as `captureScreenshot`, `getHtmlFragments`, and `indexPage` enable users to extract and analyze web content. - **Tab Management**: Actions like `changeTabs`, `closeWebPage`, and `closeAllWebPages` provide control over browser tabs. -- **Advanced Features**: The package supports executing custom scripts (`executeAdHocScript`) and managing search providers (`changeSearchProvider`). +- **Custom Scripts and Search**: Execute custom scripts with `executeAdHocScript` and manage search providers with `changeSearchProvider`. The package operates through a WebSocket server (`AgentWebSocketServer`) that facilitates communication between the browser agent and its clients. Supported clients include a Chrome extension and the Electron-based TypeAgent shell. These clients can send commands to the browser agent, which executes the requested actions and returns results. @@ -121,12 +121,12 @@ By following these steps, you can enhance the `browser-typeagent` package to sup ### Entry points - `./agent/manifest` → [./src/agent/manifest.json](./src/agent/manifest.json) -- `./agent/handlers` → `./dist/agent/browserActionHandler.mjs` _(not found on disk)_ -- `./agent/types` → `./dist/common/browserControl.mjs` _(not found on disk)_ -- `./agent/indexing` → `./dist/agent/indexing/browserIndexingService.js` _(not found on disk)_ -- `./contentScriptRpc/types` → `./dist/common/contentScriptRpc/types.mjs` _(not found on disk)_ -- `./contentScriptRpc/client` → `./dist/common/contentScriptRpc/client.mjs` _(not found on disk)_ -- `./htmlReducer` → `./dist/common/crossContextHtmlReducer.js` _(not found on disk)_ +- `./agent/handlers` → [./dist/agent/browserActionHandler.mjs](./dist/agent/browserActionHandler.mjs) +- `./agent/types` → [./dist/common/browserControl.mjs](./dist/common/browserControl.mjs) +- `./agent/indexing` → [./dist/agent/indexing/browserIndexingService.js](./dist/agent/indexing/browserIndexingService.js) +- `./contentScriptRpc/types` → [./dist/common/contentScriptRpc/types.mjs](./dist/common/contentScriptRpc/types.mjs) +- `./contentScriptRpc/client` → [./dist/common/contentScriptRpc/client.mjs](./dist/common/contentScriptRpc/client.mjs) +- `./htmlReducer` → [./dist/common/crossContextHtmlReducer.js](./dist/common/crossContextHtmlReducer.js) ### Dependencies @@ -179,7 +179,7 @@ _…and 17 more not shown._ - [./src/extension/contentScript/recording/index.ts](./src/extension/contentScript/recording/index.ts) - [./src/extension/serviceWorker/index.ts](./src/extension/serviceWorker/index.ts) - [./src/extension/webagent/crossword/crosswordSchema.agr](./src/extension/webagent/crossword/crosswordSchema.agr) -- _…and 289 more under `./src/`._ +- _…and 291 more under `./src/`._ ### Environment variables @@ -190,6 +190,6 @@ _2 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `44b34a9ac8794b6f90489ff7e55fe57283c34960` on `2026-07-13T09:04:14.089Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter browser-typeagent docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter browser-typeagent docs:verify-links` to spot-check._ diff --git a/ts/packages/chat-ui/README.AUTOGEN.md b/ts/packages/chat-ui/README.AUTOGEN.md index e9780585d..59b577bd1 100644 --- a/ts/packages/chat-ui/README.AUTOGEN.md +++ b/ts/packages/chat-ui/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # chat-ui — AI-generated documentation @@ -12,16 +12,16 @@ ## Overview -The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is designed to be used across multiple platforms, including the VS Code shell extension, the browser extension chat panel, and the Visual Studio extension webview. The package supports user and agent interactions, streaming updates, chat history management, command completions, feedback collection, and connection status indicators. Its platform-agnostic design ensures consistent functionality and appearance across different environments. +The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is designed to be used across multiple platforms, including the VS Code shell extension, the browser extension chat panel, and the Visual Studio extension webview. The package enables consistent and interactive chat experiences by offering components for rendering user and agent messages, managing chat history, handling command completions, and displaying connection statuses. ## What it does -The `chat-ui` package offers the following key features: +The `chat-ui` package includes the following key features: -- **ChatPanel**: The core component for rendering the chat interface. It supports: +- **ChatPanel**: The primary component for rendering the chat interface. It supports: - - Adding user and agent messages with `addAgentMessage`. - - Updating display metadata using `setDisplayInfo`. + - Adding user and agent messages using `addAgentMessage`. + - Updating display metadata with `setDisplayInfo`. - Replaying historical chat entries via `replayHistory`. - Streaming updates for dynamic content display. @@ -33,9 +33,9 @@ The `chat-ui` package offers the following key features: - **PlatformAdapter**: Abstracts platform-specific behaviors, such as handling link clicks and settings, to ensure compatibility across different environments. -- **Shared Styles**: Includes a CSS file (`styles/chat.css`) to ensure a consistent appearance for the chat UI across all host applications. +- **Shared Styles**: A CSS file (`styles/chat.css`) ensures a consistent appearance for the chat UI across all host applications. -The package is utilized by several TypeAgent components, such as the VS Code shell, the browser extension, and the Visual Studio extension webview. +The package is used by several TypeAgent components, such as the VS Code shell, the browser extension, and the Visual Studio extension webview. ## Setup @@ -115,10 +115,10 @@ External: `ansi_up`, `dompurify`, `markdown-it` ### Files of interest -`./src/index.ts`, `./src/chatPanel.ts`, `./src/connectionStatus.ts`, …and 12 more under `./src/`. +`./src/index.ts`, `./src/chatPanel.ts`, `./src/connectionStatus.ts`, …and 13 more under `./src/`. --- -_Auto-generated against commit `defc71271dc68db47e0d376be7aa9f755da0ac91` on `2026-07-14T08:47:00.044Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._ diff --git a/ts/packages/cli/README.AUTOGEN.md b/ts/packages/cli/README.AUTOGEN.md index 5f9c43d30..626e0b1ef 100644 --- a/ts/packages/cli/README.AUTOGEN.md +++ b/ts/packages/cli/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-cli — AI-generated documentation @@ -12,15 +12,15 @@ ## Overview -The `agent-cli` package is a command-line interface (CLI) for interacting with the TypeAgent system. It provides tools for connecting to the TypeAgent Dispatcher, managing conversations, running commands, and testing agent interactions. The CLI is designed to facilitate both interactive and non-interactive workflows, making it a versatile tool for developers working with the TypeAgent framework. +The `agent-cli` package is a command-line interface (CLI) for interacting with the TypeAgent system. It serves as a versatile tool for developers to connect to the TypeAgent Dispatcher, manage conversations, execute commands, and test agent interactions. The CLI supports both interactive and non-interactive workflows, making it suitable for development, debugging, and testing purposes. ## What it does -The `agent-cli` package supports several subcommands, each tailored to specific use cases: +The `agent-cli` package provides several subcommands, each designed for specific tasks: -- **`connect`**: The default subcommand, used for real-time interaction with the TypeAgent Dispatcher. It allows users to send requests, receive responses, and manage conversations interactively. -- **`run`**: Executes dispatcher commands non-interactively. This includes sending requests, translating them, or generating explanations without user confirmation. -- **`replay`**: Replays chat histories for regression testing or generating test files. This is useful for validating agent behavior over time. +- **`connect`**: The default subcommand, enabling real-time interaction with the TypeAgent Dispatcher. Users can send requests, receive responses, and manage conversations interactively. This mode is particularly useful for testing and debugging agent behavior. +- **`run`**: Allows non-interactive execution of dispatcher commands. This includes sending requests, translating them, or generating explanations without requiring user confirmation. +- **`replay`**: Replays chat histories for regression testing or generating test files. This is useful for validating agent behavior over time and ensuring consistency. - **`conversations`**: Provides tools for managing conversations on the agent server. Subcommands include: - `create`: Create a new conversation. - `delete`: Delete an existing conversation. @@ -144,6 +144,6 @@ External: `@oclif/core`, `@oclif/plugin-help`, `chalk`, `debug`, `dotenv`, `html --- -_Auto-generated against commit `2a8c6e65a1638c435219fd5b8688faeeec78d4c7` on `2026-07-16T01:20:16.260Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-cli docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-cli docs:verify-links` to spot-check._ diff --git a/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md b/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md index 4a97f8273..ae7998295 100644 --- a/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md +++ b/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-dispatcher — AI-generated documentation @@ -12,15 +12,15 @@ ## Overview -The TypeAgent Dispatcher is a TypeScript library that acts as the central processing hub for user requests within the TypeAgent ecosystem. It translates natural language inputs into structured actions using large language models (LLMs) and coordinates interactions between various application agents. The Dispatcher is designed to be extensible, scalable, and capable of integrating with multiple front ends, such as the TypeAgent Shell and CLI. +The TypeAgent Dispatcher is a TypeScript library that serves as the central hub for processing user requests in the TypeAgent ecosystem. It translates natural language inputs into structured actions using large language models (LLMs) and coordinates interactions across various application agents. The Dispatcher is designed to integrate with multiple front ends, such as the TypeAgent Shell and CLI, and supports an extensible architecture for adding new agents and capabilities. ## What it does -The Dispatcher provides a framework for interpreting user inputs and orchestrating actions across different agents. It supports both natural language requests and system commands, enabling a wide range of interactions. +The Dispatcher enables natural language interaction with application agents by translating user inputs into structured actions based on predefined schemas. It also provides system commands for managing agents, configurations, and sessions. ### Natural Language Requests -The Dispatcher leverages LLMs to process natural language inputs and translate them into structured actions defined by application agent schemas. For example: +The Dispatcher processes natural language inputs and translates them into structured actions. For example: ```bash [calendar]🤖> can you setup a meeting between 2-3PM @@ -37,7 +37,7 @@ Other examples include: ### System Commands -System commands prefixed with `@` allow users to configure and interact with the Dispatcher directly. Key commands include: +System commands prefixed with `@` allow users to interact with and configure the Dispatcher directly. Key commands include: - **Agent Management**: Enable or disable specific agents or groups of agents. @@ -236,6 +236,6 @@ _9 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `27016facc11ab05d8556e8b89c421f6a0a90f2e2` on `2026-07-15T22:35:06.059Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._ diff --git a/ts/packages/dispatcher/types/README.AUTOGEN.md b/ts/packages/dispatcher/types/README.AUTOGEN.md index b5c139a7b..2d339f56d 100644 --- a/ts/packages/dispatcher/types/README.AUTOGEN.md +++ b/ts/packages/dispatcher/types/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # @typeagent/dispatcher-types — AI-generated documentation @@ -12,36 +12,36 @@ ## Overview -The `@typeagent/dispatcher-types` package provides a centralized collection of TypeScript type definitions for the TypeAgent dispatcher. These types are essential for ensuring consistent and type-safe communication between various components of the TypeAgent ecosystem, such as agents, clients, and dispatchers. By standardizing the data structures and interfaces, this package helps maintain compatibility and reliability across the system. +The `@typeagent/dispatcher-types` package provides a collection of TypeScript type definitions that are essential for the TypeAgent dispatcher and its related components. These types ensure consistent and type-safe communication across the TypeAgent ecosystem, including agents, clients, and dispatchers. By standardizing the data structures and interfaces, this package facilitates integration and interoperability between various components. ## What it does -This package defines and exports TypeScript types that are used throughout the TypeAgent dispatcher and its related components. These types are consumed by multiple packages in the TypeAgent monorepo, including `@typeagent/agent-server-protocol`, `@typeagent/copilot-plugin`, and `agent-dispatcher`. The key functionalities provided by this package include: +The primary purpose of this package is to define and export TypeScript types that are used throughout the TypeAgent dispatcher and its associated modules. These types are consumed by multiple packages in the TypeAgent monorepo, such as `@typeagent/agent-server-protocol`, `@typeagent/copilot-plugin`, and `agent-dispatcher`. The key functionalities include: -- **Dispatcher Requests and Responses**: Types such as `RequestId`, `PendingInteractionRequest`, and `PendingInteractionResponse` define the structure of requests and responses handled by the dispatcher. -- **Client Input/Output Operations**: Types like `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData` are used to manage client interactions, including message formatting and data exchange. +- **Dispatcher Requests and Responses**: Types like `RequestId`, `PendingInteractionRequest`, and `PendingInteractionResponse` define the structure of requests and responses managed by the dispatcher. +- **Client Input/Output Operations**: Types such as `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData` are used to handle client interactions, including message formatting and data exchange. - **Dispatcher Status Management**: The package includes types and helper functions (e.g., `getStatusSummary` in [status.ts](./src/helpers/status.ts)) to represent and summarize the state of the dispatcher. -- **Queue Management**: Types such as `QueuedRequest`, `QueueCancelReason`, and `QueueRequestState` define the structure and lifecycle of server-side message queues. -- **Logging and Display**: Types like `SetDisplayEntry` and `AppendDisplayEntry` in [displayLogEntry.ts](./src/displayLogEntry.ts) are used for managing and formatting log entries for display purposes. +- **Queue Management**: Types like `QueuedRequest`, `QueueCancelReason`, and `QueueRequestState` define the structure and lifecycle of server-side message queues. +- **Logging and Display**: Types such as `SetDisplayEntry` and `AppendDisplayEntry` in [displayLogEntry.ts](./src/displayLogEntry.ts) are used for managing and formatting log entries for display purposes. -These types are foundational for the operation of the TypeAgent dispatcher and its integration with other components in the system. +These types are critical for the operation of the TypeAgent dispatcher and its integration with other components in the system. ## Setup -This package does not require any special setup beyond installation. To include it in your project, simply run: +This package does not require any special setup beyond installation. To include it in your project, use the following command: ```bash pnpm install ``` -For additional details, refer to the hand-written README. +For further details, refer to the hand-written README. ## Key Files -The `@typeagent/dispatcher-types` package is organized into several key files, each focusing on a specific aspect of the dispatcher: +The `@typeagent/dispatcher-types` package is organized into several key files, each serving a specific purpose: -- [src/index.ts](./src/index.ts): The main entry point for the package, exporting all the types and utilities defined in other modules. -- [src/clientIO.ts](./src/clientIO.ts): Contains types related to client input/output operations, such as `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData`. +- [src/index.ts](./src/index.ts): The main entry point of the package, exporting all the types and utilities defined in other modules. +- [src/clientIO.ts](./src/clientIO.ts): Contains types for client input/output operations, such as `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData`. - [src/dispatcher.ts](./src/dispatcher.ts): Defines core dispatcher types, including `RequestId`, `DispatcherName`, and `DispatcherEmoji`. - [src/displayLogEntry.ts](./src/displayLogEntry.ts): Provides types for logging and displaying information, such as `SetDisplayEntry` and `AppendDisplayEntry`. - [src/pendingInteraction.ts](./src/pendingInteraction.ts): Manages types for pending interactions, including `PendingInteractionRequest` and `PendingInteractionResponse`. @@ -103,6 +103,6 @@ External: _None at runtime._ --- -_Auto-generated against commit `defc71271dc68db47e0d376be7aa9f755da0ac91` on `2026-07-14T08:47:00.044Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/dispatcher-types docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/dispatcher-types docs:verify-links` to spot-check._ diff --git a/ts/packages/shell/README.AUTOGEN.md b/ts/packages/shell/README.AUTOGEN.md index 86b7433f4..9591fb79e 100644 --- a/ts/packages/shell/README.AUTOGEN.md +++ b/ts/packages/shell/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-shell — AI-generated documentation @@ -12,11 +12,11 @@ ## Overview -The `agent-shell` package is a TypeScript library that serves as the graphical user interface (GUI) entry point for the TypeAgent ecosystem. It provides a personal agent interface for processing user requests, performing actions, answering questions, and managing conversations. Built on Electron, it integrates with other TypeAgent components, such as the dispatcher and agent server, to deliver an interactive and extensible experience. The shell supports both text and voice input, multi-conversation management, and local or remote operation modes. +The `agent-shell` package is a TypeScript library that acts as the graphical user interface (GUI) entry point for the TypeAgent ecosystem. It provides a personal agent interface for processing user requests, performing actions, answering questions, and managing conversations. Built on Electron, it integrates with other TypeAgent components, such as the dispatcher and agent server, to deliver an interactive and extensible experience. The shell supports both text and voice input, multi-conversation management, and local or remote operation modes. ## What it does -The `agent-shell` package offers a range of features to enable interactive and conversational agent experiences: +The `agent-shell` package provides a range of features to enable interactive and conversational agent experiences: ### Conversation Management @@ -172,6 +172,6 @@ _5 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `5c9fc637c2f0a96d75d41a3bc9054d06247d26d8` on `2026-07-15T08:50:41.068Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-shell docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-shell docs:verify-links` to spot-check._ diff --git a/ts/packages/vscode-shell/README.AUTOGEN.md b/ts/packages/vscode-shell/README.AUTOGEN.md index 796403b63..7b6d89ce7 100644 --- a/ts/packages/vscode-shell/README.AUTOGEN.md +++ b/ts/packages/vscode-shell/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # vscode-shell — AI-generated documentation @@ -12,11 +12,11 @@ ## Overview -The `vscode-shell` package integrates the TypeAgent shell chat into Visual Studio Code, providing a way to interact with TypeAgent conversations directly within the editor. It offers a side panel and editor tabs for managing conversations hosted by a running TypeAgent agent server. +The `vscode-shell` package integrates the TypeAgent shell chat into Visual Studio Code, allowing users to interact with TypeAgent conversations directly within the editor. It provides a dedicated side panel and the ability to open multiple chat tabs in the editor, all connected to a running TypeAgent agent server. ## What it does -The `vscode-shell` package enables a feature-rich chat experience within Visual Studio Code, tightly integrated with the TypeAgent ecosystem. Its main capabilities include: +The `vscode-shell` package brings the functionality of the TypeAgent shell into Visual Studio Code, enabling users to manage and interact with conversations in a familiar development environment. Key features include: - **Chat Interface**: @@ -174,6 +174,6 @@ External: `ansi_up`, `debug`, `dompurify`, `isomorphic-ws`, `markdown-it`, `micr --- -_Auto-generated against commit `defc71271dc68db47e0d376be7aa9f755da0ac91` on `2026-07-14T08:47:00.044Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter vscode-shell docs:verify-links` to spot-check._ +_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter vscode-shell docs:verify-links` to spot-check._ From c64ca30d8893efdeab7623984779c5b018afb63e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 17 Jul 2026 16:55:40 -0700 Subject: [PATCH 05/12] Added mockups for historical retention --- .../mockups/stale-server-notice-live.html | 178 ++++++++++++++++++ .../chat-ui/mockups/stale-server-notice.html | 31 +-- .../chat-ui/mockups/stale-server-notice.md | 103 ++++++++++ 3 files changed, 297 insertions(+), 15 deletions(-) create mode 100644 ts/packages/chat-ui/mockups/stale-server-notice-live.html create mode 100644 ts/packages/chat-ui/mockups/stale-server-notice.md diff --git a/ts/packages/chat-ui/mockups/stale-server-notice-live.html b/ts/packages/chat-ui/mockups/stale-server-notice-live.html new file mode 100644 index 000000000..60d46a707 --- /dev/null +++ b/ts/packages/chat-ui/mockups/stale-server-notice-live.html @@ -0,0 +1,178 @@ + + + + + + + + + stale-server notice - live preview (real chat.css) + + + + +

Status notice - live preview

+

+ Rendered with the real packages/chat-ui/styles/chat.css. The + × on the interactive card minimizes to the pinned pill; click the + pill to re-expand. +

+ + +
+ + + + diff --git a/ts/packages/chat-ui/mockups/stale-server-notice.html b/ts/packages/chat-ui/mockups/stale-server-notice.html index 28ce2c7da..7f40b35c0 100644 --- a/ts/packages/chat-ui/mockups/stale-server-notice.html +++ b/ts/packages/chat-ui/mockups/stale-server-notice.html @@ -3,25 +3,26 @@ Licensed under the MIT License. --> diff --git a/ts/packages/chat-ui/mockups/stale-server-notice.md b/ts/packages/chat-ui/mockups/stale-server-notice.md new file mode 100644 index 000000000..04b2a1e87 --- /dev/null +++ b/ts/packages/chat-ui/mockups/stale-server-notice.md @@ -0,0 +1,103 @@ + + +# Stale-server notice — design & reference + +When the agent-server's code is rebuilt on disk while the process keeps +running, it is serving **out-of-date code**. On connect it pushes a notice to +each client so the user knows to restart it. This doc records the design +decision for how that notice appears and how the (reusable) affordance works. + +## Files in this folder + +- [`stale-server-notice.html`](./stale-server-notice.html) — the design + exploration: five treatments compared side by side (interactive). +- [`stale-server-notice-live.html`](./stale-server-notice-live.html) — a + preview of the chosen design rendered with the **real** `../styles/chat.css` + (stays accurate as the styles change). + +## Options considered → decision + +| # | Treatment | Verdict | +| ----- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| 0 | Notifications bell (the original ephemeral `warning`) | Too easy to miss; no call to action. | +| A | Persistent toast (top-right, until dismissed) | Good, but once dismissed it's gone while still stale. | +| B | Pinned banner across the top | Most "can't miss it"; heavier. | +| C | Compact pinned strip | Subtle; easy to tune out. | +| **D** | **Toast that collapses to a pinned pill** | **Chosen** — loud on arrival, dismiss _minimizes_ to a pill so it's never lost while stale, click the pill to re-expand. | + +## The affordance (option D) + +A persistent, dismissible **status notice** rendered by chat-ui as a +bottom-right toast (title, message, optional action button, and a `×`). The `×` +collapses it to a small **pinned pill** in the same corner; clicking the pill +re-expands it. Unlike `showToast()` (which auto-hides after 5s), it stays until +dismissed. Colors are self-contained amber/red/blue (like `.chat-reconnect-banner`) +so it reads on both light and dark host themes. + +It lives almost entirely in **chat-ui** so the Electron shell and vscode-shell +share it: + +- **Model + event** — `packages/chat-ui/src/statusNotice.ts` + (`StatusNotice`, `parseStatusNotice`, `STATUS_NOTICE_EVENT = "statusNotice"`). +- **Renderer** — `ChatPanel.showStatusNotice()` / `clearStatusNotice()` in + `packages/chat-ui/src/chatPanel.ts`, styled in + `packages/chat-ui/styles/chat.css` (`.chat-status-notice*`, `.csn-*`). + The action button runs its command through `injectCommand()`, so it works in + every host with no extra wiring. + +## Wire path + +``` +agent-server (stale) --notify("statusNotice", {...})--> client + Electron shell : chatPanelBridge.ts -> chatPanel.showStatusNotice() + vscode-shell : webview/main.ts -> chatPanel.showStatusNotice() + CLI : enhancedConsole.ts -> yellow console line +``` + +The server payload lives in +`packages/agentServer/server/src/connectionHandler.ts` (id `stale-build`, +level `warning`, action `@server restart`). + +## Reusable + +Not stale-build-specific. Any source can raise one: + +```ts +clientIO.notify( + undefined, + "statusNotice", + { + id: "my-notice", + level: "warning", // info | warning | error + title: "…", + message: "…", + actionLabel: "Do it", // optional button… + actionCommand: "@…", // …runs this via the chat input + }, + "my-source", +); +``` + +## Lifecycle + +Ephemeral: not written to the DisplayLog, so it does not replay on rejoin; a +fresh connect re-sends it (once per connection) while the condition holds. +Cleared on `ChatPanel.clear()` (conversation switch / reconnect replay). + +## Test on demand + +``` +@notify status # warning notice, no button +@notify status "custom body text" +@notify status --level info|warning|error +@notify status --restart # include the real "Restart server" button +``` + +Implemented in +`packages/dispatcher/dispatcher/src/context/system/handlers/notifyCommandHandler.ts`. + +## Related + +The server-side staleness detection and self-restart that this notice pairs +with: `packages/agentServer/server/src/staleBuild.ts` (watcher + console +banner) and the `@server restart` / `/restart` commands. From 556f256dd6a594c98cdcd5deddd6682c6101e918 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:25:43 +0000 Subject: [PATCH 06/12] Fix cognitive complexity ratchet: extract statusNotice handling into displayStatusNotice helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CI complexity ratchet failed because adding the statusNotice case inline to the notify() method pushed its cognitive complexity from ≤30 to 33. Extract the statusNotice handling logic into a dedicated displayStatusNotice() helper function, following the same pattern as displayToastNotification() and displayInlineNotification(), to bring notify() back under the cognitive complexity threshold. --- ts/packages/cli/src/enhancedConsole.ts | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ts/packages/cli/src/enhancedConsole.ts b/ts/packages/cli/src/enhancedConsole.ts index 1b9f3250f..7ba024d7f 100644 --- a/ts/packages/cli/src/enhancedConsole.ts +++ b/ts/packages/cli/src/enhancedConsole.ts @@ -926,6 +926,22 @@ export function createEnhancedClientIO( } } + function displayStatusNotice(data: unknown): void { + const notice = (data ?? {}) as { + title?: string; + message?: string; + actionCommand?: string; + }; + const text = [notice.title, notice.message].filter(Boolean).join(" — "); + const hint = notice.actionCommand ? ` (${notice.actionCommand})` : ""; + const line = `⚠ ${text}${hint}`; + if (currentSpinner?.isActive()) { + currentSpinner.writeAbove(chalk.yellow(line)); + } else { + console.warn(chalk.yellow(line)); + } + } + return { clear(): void { console.clear(); @@ -1100,23 +1116,7 @@ export function createEnhancedClientIO( // Persistent status notice (chat-ui STATUS_NOTICE_EVENT). // The shells render a toast/pill; the console prints one // yellow line, preserving the pre-existing behavior. - const notice = (data ?? {}) as { - title?: string; - message?: string; - actionCommand?: string; - }; - const text = [notice.title, notice.message] - .filter(Boolean) - .join(" \u2014 "); - const hint = notice.actionCommand - ? ` (${notice.actionCommand})` - : ""; - const line = `\u26a0 ${text}${hint}`; - if (currentSpinner?.isActive()) { - currentSpinner.writeAbove(chalk.yellow(line)); - } else { - console.warn(chalk.yellow(line)); - } + displayStatusNotice(data); break; } From bdf0bb82a95a3a61dfc2a32ea439f70c96228154 Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Sat, 18 Jul 2026 01:42:17 +0000 Subject: [PATCH 07/12] docs: regenerate README.AUTOGEN.md, command reference, and action browser --- .../agentServer/server/README.AUTOGEN.md | 65 ++++++++++--------- ts/packages/chat-ui/README.AUTOGEN.md | 16 ++--- ts/packages/cli/README.AUTOGEN.md | 14 ++-- .../dispatcher/dispatcher/README.AUTOGEN.md | 6 +- .../dispatcher/types/README.AUTOGEN.md | 22 +++---- ts/packages/vscode-shell/README.AUTOGEN.md | 8 +-- 6 files changed, 66 insertions(+), 65 deletions(-) diff --git a/ts/packages/agentServer/server/README.AUTOGEN.md b/ts/packages/agentServer/server/README.AUTOGEN.md index 82c7e0270..4570dd0b6 100644 --- a/ts/packages/agentServer/server/README.AUTOGEN.md +++ b/ts/packages/agentServer/server/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-server — AI-generated documentation @@ -12,18 +12,20 @@ ## Overview -The `agent-server` package is a TypeScript library that provides a long-running WebSocket server for hosting TypeAgent dispatchers. It manages client connections, facilitates communication between clients and agents, and handles conversation management. The server supports multiple clients and conversations simultaneously, with features such as conversation persistence, idle timeouts, and graceful shutdown. +The `agent-server` package is a TypeScript library that implements a long-running WebSocket server for hosting TypeAgent dispatchers. It serves as a central hub for managing client connections, facilitating communication between clients and agents, and orchestrating conversation management. The server is designed to handle multiple clients and conversations simultaneously, with features such as conversation persistence, idle timeouts, and ephemeral conversation cleanup. ## What it does -The `agent-server` package is a core component of the TypeAgent ecosystem, enabling communication between clients and agents through a WebSocket server. Its primary responsibilities include: +The `agent-server` provides the following key functionalities: -- **Conversation Management**: The server supports actions such as `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. These actions allow clients to manage and interact with conversations. -- **Server Control**: The `shutdown` action provides a mechanism for controlled server termination. -- **Idle Timeout**: The server can be configured to shut down automatically after a specified period of inactivity using the `--idle-timeout` flag. -- **Ephemeral Conversation Cleanup**: On startup, the server removes temporary conversations (e.g., those created by CLI processes) to free up resources. +- **WebSocket Server**: Hosts a WebSocket server that listens for client connections and facilitates communication between clients and agents. +- **Conversation Management**: Supports actions such as `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation`. These actions allow clients to manage and interact with conversations. +- **Server Control**: Includes a `shutdown` action for graceful server termination. +- **Idle Timeout**: Configurable idle timeout to automatically shut down the server after a specified period of inactivity. +- **Ephemeral Conversation Cleanup**: Automatically removes temporary conversations (e.g., those created by CLI processes) during server startup to free up resources. +- **Copilot Session Integration**: Provides tools for importing and replaying GitHub Copilot sessions into the conversation UI. -The server listens on a WebSocket endpoint and processes these actions by interacting with its core components, such as the `ConversationManager` and `SharedDispatcher`. It integrates with other TypeAgent packages, including `@typeagent/agent-server-client` and `@typeagent/agent-server-protocol`, to provide a consistent framework for agent-based communication. +The server integrates with other TypeAgent packages, such as `@typeagent/agent-server-client` and `@typeagent/agent-server-protocol`, to provide a cohesive framework for agent-based communication. ## Setup @@ -37,55 +39,54 @@ To set up the `agent-server`, you need to configure the following environment va - `TYPEAGENT_USER_NAME`: The username for the TypeAgent user. This can override the default OS user name. - `XDG_CONFIG_HOME`: Specifies the base directory for configuration files. If not set, the default is typically `~/.config`. -You can set these variables in your environment or define them in a `.env` file in the `ts/` directory. For additional details, refer to the hand-written README. +These variables can be set in your environment or defined in a `.env` file in the `ts/` directory. For further details, refer to the hand-written README. ## Key Files -The `agent-server` package is organized into several key files, each responsible for specific functionality: +The `agent-server` package is structured into several key files, each with specific responsibilities: ### [server.ts](./src/server.ts) — WebSocket Listener -This file initializes the WebSocket server and manages client connections. It performs the following tasks: - -1. Creates a `ConversationManager` instance to handle conversation-related operations. -2. Sets up the WebSocket server using `createWebSocketChannelServer`. -3. Exposes RPC functions over the `agent-server` channel, including: - - `joinConversation`, `leaveConversation`, `createConversation`, `listConversations`, `renameConversation`, and `deleteConversation` for conversation management. - - `shutdown` for graceful server termination. +- Initializes the WebSocket server using `createWebSocketChannelServer`. +- Creates a `ConversationManager` instance to handle conversation-related operations. +- Exposes RPC functions over the `agent-server` channel, including conversation management actions (`joinConversation`, `leaveConversation`, etc.) and server control (`shutdown`). ### [conversationManager.ts](./src/conversationManager.ts) — Conversation Pool -This file manages the lifecycle of conversations and their associated dispatchers. Key responsibilities include: - -- **Persistence**: Stores conversation metadata and data in the user's configuration directory. -- **Lazy Initialization**: Creates `SharedDispatcher` instances only when a conversation is accessed. -- **Default Conversations**: Automatically creates a default conversation if none exists. -- **Ephemeral Cleanup**: Deletes temporary conversations (e.g., `cli-ephemeral-*`) on server startup. -- **Idle Shutdown**: Monitors client activity and shuts down the server after a period of inactivity if the `--idle-timeout` flag is set. +- Manages the lifecycle of conversations and their associated dispatchers. +- Stores conversation metadata and data in the user's configuration directory. +- Implements lazy initialization for `SharedDispatcher` instances, creating them only when a conversation is accessed. +- Automatically creates a default conversation if none exists. +- Cleans up ephemeral conversations (e.g., `cli-ephemeral-*`) on server startup. +- Monitors client activity and shuts down the server after a period of inactivity if the `--idle-timeout` flag is set. ### [sharedDispatcher.ts](./src/sharedDispatcher.ts) — Routing Layer -This file manages multiple client connections within a single conversation. It provides the following functionality: - +- Manages multiple client connections within a single conversation. - Assigns unique `connectionId`s to clients and maintains a routing table. - Routes client IO methods (e.g., `setDisplay`, `askYesNo`) to the appropriate client based on their `connectionId`. - Ensures that each client's interactions are isolated while sharing the same dispatcher and conversation context. ### [connectionHandler.ts](./src/connectionHandler.ts) — Connection Management -This file defines the logic for handling individual client connections. It sets up the necessary RPC channels and integrates with the `ConversationManager` to manage conversations for each connected client. +- Defines the logic for handling individual client connections. +- Sets up the necessary RPC channels and integrates with the `ConversationManager` to manage conversations for each connected client. ### [copilot/displayLogSynthesis.ts](./src/copilot/displayLogSynthesis.ts) — Display Log Synthesis -This file synthesizes display logs from Copilot session data, enabling imported sessions to be replayed in the conversation UI. It ensures that the synthesized logs are deterministic and idempotent, allowing for consistent re-imports. +- Synthesizes display logs from Copilot session data, enabling imported sessions to be replayed in the conversation UI. +- Ensures that the synthesized logs are deterministic and idempotent, allowing for consistent re-imports. ### [copilot/mirrorImporter.ts](./src/copilot/mirrorImporter.ts) — Copilot Session Importer -This file provides functionality to import Copilot sessions into the `agent-server`. It supports filtering sessions by repository, timestamp, and session ID, and integrates with the `ConversationManager` to create or update conversations based on the imported data. +- Provides functionality to import Copilot sessions into the `agent-server`. +- Supports filtering sessions by repository, timestamp, and session ID. +- Integrates with the `ConversationManager` to create or update conversations based on the imported data. ### [inProcessAgentServer.ts](./src/inProcessAgentServer.ts) — In-Process Server -This file provides an in-process implementation of the agent server, allowing it to be embedded within other applications. It includes options for user identity resolution, idle timeout, and shutdown handling. +- Provides an in-process implementation of the agent server, allowing it to be embedded within other applications. +- Includes options for user identity resolution, idle timeout, and shutdown handling. ## How to extend @@ -149,7 +150,7 @@ External: `@azure/identity`, `better-sqlite3`, `debug`, `dotenv`, `ws` ### Files of interest -`./src/connectionHandler.ts`, `./src/conversationManager.ts`, `./src/copilot/displayLogSynthesis.ts`, …and 11 more under `./src/`. +`./src/connectionHandler.ts`, `./src/conversationManager.ts`, `./src/copilot/displayLogSynthesis.ts`, …and 12 more under `./src/`. ### Environment variables @@ -165,6 +166,6 @@ _7 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `5cbcf613f047f08749d0451296eb1cdc610ae414` on `2026-07-17T18:24:18.404Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-server docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-server docs:verify-links` to spot-check._ diff --git a/ts/packages/chat-ui/README.AUTOGEN.md b/ts/packages/chat-ui/README.AUTOGEN.md index 0150c895a..1d51ca771 100644 --- a/ts/packages/chat-ui/README.AUTOGEN.md +++ b/ts/packages/chat-ui/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # chat-ui — AI-generated documentation @@ -12,16 +12,16 @@ ## Overview -The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is designed to work across multiple platforms, including the VS Code shell extension, the browser extension chat panel, and the Visual Studio extension webview. The package supports user and agent interactions, streaming updates, chat history management, command completions, feedback collection, and connection status indicators. Its platform-agnostic design ensures consistent functionality and appearance across different environments. +The `chat-ui` package provides a shared, framework-free chat user interface for TypeAgent applications. It is designed to be used across multiple platforms, including the VS Code shell extension, the browser extension chat panel, and the Visual Studio extension webview. The package enables interactive chat experiences with features such as user and agent message rendering, streaming updates, chat history replay, command completions, feedback collection, and connection status indicators. Its platform-agnostic design ensures consistent functionality and appearance across different environments. ## What it does -The `chat-ui` package offers a range of features to support interactive chat experiences: +The `chat-ui` package offers the following key features: -- **ChatPanel**: The primary component for rendering the chat interface. It supports: +- **ChatPanel**: The core component for rendering the chat interface. It supports: - - Adding user and agent messages using `addAgentMessage`. - - Updating display metadata with `setDisplayInfo`. + - Adding user and agent messages with `addAgentMessage`. + - Updating display metadata using `setDisplayInfo`. - Replaying historical chat entries via `replayHistory`. - Streaming updates for dynamic content display. @@ -115,10 +115,10 @@ External: `ansi_up`, `dompurify`, `markdown-it` ### Files of interest -`./src/index.ts`, `./src/chatPanel.ts`, `./src/connectionStatus.ts`, …and 12 more under `./src/`. +`./src/index.ts`, `./src/chatPanel.ts`, `./src/connectionStatus.ts`, …and 13 more under `./src/`. --- -_Auto-generated against commit `5cbcf613f047f08749d0451296eb1cdc610ae414` on `2026-07-17T18:24:18.404Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter chat-ui docs:verify-links` to spot-check._ diff --git a/ts/packages/cli/README.AUTOGEN.md b/ts/packages/cli/README.AUTOGEN.md index 626e0b1ef..87c9b9ba3 100644 --- a/ts/packages/cli/README.AUTOGEN.md +++ b/ts/packages/cli/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-cli — AI-generated documentation @@ -12,14 +12,14 @@ ## Overview -The `agent-cli` package is a command-line interface (CLI) for interacting with the TypeAgent system. It serves as a versatile tool for developers to connect to the TypeAgent Dispatcher, manage conversations, execute commands, and test agent interactions. The CLI supports both interactive and non-interactive workflows, making it suitable for development, debugging, and testing purposes. +The `agent-cli` package is a command-line interface (CLI) for interacting with the TypeAgent system. It provides tools for developers to connect to the TypeAgent Dispatcher, manage conversations, execute commands, and test agent interactions. The CLI supports both interactive and non-interactive workflows, making it a versatile tool for development, debugging, and testing. ## What it does -The `agent-cli` package provides several subcommands, each designed for specific tasks: +The `agent-cli` package offers several subcommands, each tailored to specific tasks: -- **`connect`**: The default subcommand, enabling real-time interaction with the TypeAgent Dispatcher. Users can send requests, receive responses, and manage conversations interactively. This mode is particularly useful for testing and debugging agent behavior. -- **`run`**: Allows non-interactive execution of dispatcher commands. This includes sending requests, translating them, or generating explanations without requiring user confirmation. +- **`connect`**: The default subcommand, enabling real-time interaction with the TypeAgent Dispatcher. Users can send requests, receive responses, and manage conversations interactively. This mode is ideal for testing and debugging agent behavior. +- **`run`**: Executes dispatcher commands non-interactively. This includes sending requests, translating them, or generating explanations without requiring user confirmation. - **`replay`**: Replays chat histories for regression testing or generating test files. This is useful for validating agent behavior over time and ensuring consistency. - **`conversations`**: Provides tools for managing conversations on the agent server. Subcommands include: - `create`: Create a new conversation. @@ -30,7 +30,7 @@ The `agent-cli` package provides several subcommands, each designed for specific - `add`: Add new data to the explanation test dataset. - `diff`: Compare differences between datasets. -These commands enable developers to interact with the TypeAgent system in various ways, from testing and debugging to managing agent conversations and datasets. +These commands allow developers to interact with the TypeAgent system in various ways, from testing and debugging to managing agent conversations and datasets. ## Setup @@ -144,6 +144,6 @@ External: `@oclif/core`, `@oclif/plugin-help`, `chalk`, `debug`, `dotenv`, `html --- -_Auto-generated against commit `b1b5bcafdde8ba2387d669eec198eb70e8fa5986` on `2026-07-17T23:52:55.795Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-cli docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-cli docs:verify-links` to spot-check._ diff --git a/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md b/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md index 1ab04b6fe..298d59462 100644 --- a/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md +++ b/ts/packages/dispatcher/dispatcher/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-dispatcher — AI-generated documentation @@ -12,7 +12,7 @@ ## Overview -The TypeAgent Dispatcher is a TypeScript library that acts as the central hub for processing user requests within the TypeAgent ecosystem. It translates natural language inputs into structured actions using large language models (LLMs) and coordinates interactions between various application agents. The Dispatcher is designed to integrate with multiple front ends, such as the TypeAgent Shell and CLI, and supports an extensible architecture for building personal agents with natural language interfaces. +The TypeAgent Dispatcher is a TypeScript library that serves as the central component for processing user requests in the TypeAgent ecosystem. It translates natural language inputs into structured actions using large language models (LLMs) and coordinates interactions between various application agents. The Dispatcher is designed to work with multiple front ends, such as the TypeAgent Shell and CLI, and supports an extensible architecture for building personal agents with natural language interfaces. ## What it does @@ -236,6 +236,6 @@ _9 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `c9ef0d288f10874ca031db370793375b7b88a8bc` on `2026-07-17T23:22:28.266Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-dispatcher docs:verify-links` to spot-check._ diff --git a/ts/packages/dispatcher/types/README.AUTOGEN.md b/ts/packages/dispatcher/types/README.AUTOGEN.md index 0186f5647..a10271c74 100644 --- a/ts/packages/dispatcher/types/README.AUTOGEN.md +++ b/ts/packages/dispatcher/types/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # @typeagent/dispatcher-types — AI-generated documentation @@ -12,19 +12,19 @@ ## Overview -The `@typeagent/dispatcher-types` package provides a collection of TypeScript type definitions for the TypeAgent dispatcher. These types are used to standardize communication and data structures across the TypeAgent ecosystem, ensuring type safety and consistency between components such as agents, clients, and dispatchers. +The `@typeagent/dispatcher-types` package provides a set of TypeScript type definitions that are essential for the TypeAgent dispatcher and its related components. These types ensure consistent data structures and type safety across the TypeAgent ecosystem, facilitating communication between agents, clients, and dispatchers. ## What it does -This package defines and exports TypeScript types that are integral to the operation of the TypeAgent dispatcher and its related components. These types are widely consumed by other packages in the TypeAgent monorepo, such as `@typeagent/agent-server-protocol`, `@typeagent/copilot-plugin`, and `agent-dispatcher`. Key areas covered by the package include: +This package defines and exports TypeScript types that are widely used across the TypeAgent monorepo. These types are foundational for the operation of the dispatcher and its integration with other components. Key areas covered include: -- **Dispatcher Requests and Responses**: Types like `RequestId`, `PendingInteractionRequest`, and `PendingInteractionResponse` define the structure of requests and responses managed by the dispatcher. -- **Client Input/Output Operations**: Types such as `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData` are used to handle client interactions, including message formatting and data exchange. +- **Dispatcher Requests and Responses**: Types such as `RequestId`, `PendingInteractionRequest`, and `PendingInteractionResponse` define the structure of requests and responses handled by the dispatcher. +- **Client Input/Output Operations**: Types like `IAgentMessage`, `TemplateEditConfig`, and `NotifyExplainedData` support client interactions, including message formatting and data exchange. - **Dispatcher Status Management**: Includes types and helper functions (e.g., `getStatusSummary` in [status.ts](./src/helpers/status.ts)) to represent and summarize the state of the dispatcher. -- **Queue Management**: Types like `QueuedRequest`, `QueueCancelReason`, and `QueueRequestState` define the structure and lifecycle of server-side message queues. -- **Logging and Display**: Types such as `SetDisplayEntry` and `AppendDisplayEntry` in [displayLogEntry.ts](./src/displayLogEntry.ts) are used for managing and formatting log entries for display purposes. +- **Queue Management**: Types such as `QueuedRequest`, `QueueCancelReason`, and `QueueRequestState` define the structure and lifecycle of server-side message queues. +- **Logging and Display**: Types like `SetDisplayEntry` and `AppendDisplayEntry` in [displayLogEntry.ts](./src/displayLogEntry.ts) are used for managing and formatting log entries for display purposes. -These types are foundational for the operation of the dispatcher and its integration with other components in the system. +These types are consumed by various packages in the TypeAgent ecosystem, including `@typeagent/agent-server-protocol`, `@typeagent/copilot-plugin`, and `agent-dispatcher`. ## Setup @@ -34,7 +34,7 @@ This package does not require any special setup beyond installation. To include pnpm install ``` -For further details, refer to the hand-written README. +For additional details, refer to the hand-written README. ## Key Files @@ -95,7 +95,7 @@ External: _None at runtime._ - [browser-typeagent](../../../packages/agents/browser/README.md) - [chat-ui](../../../packages/chat-ui/README.md) - [coder-wrapper](../../../packages/coderWrapper/README.md) -- _…and 6 more workspace consumers._ +- _…and 7 more workspace consumers._ ### Files of interest @@ -103,6 +103,6 @@ External: _None at runtime._ --- -_Auto-generated against commit `5cbcf613f047f08749d0451296eb1cdc610ae414` on `2026-07-17T18:24:18.404Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/dispatcher-types docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter @typeagent/dispatcher-types docs:verify-links` to spot-check._ diff --git a/ts/packages/vscode-shell/README.AUTOGEN.md b/ts/packages/vscode-shell/README.AUTOGEN.md index b404f1da5..a400698e4 100644 --- a/ts/packages/vscode-shell/README.AUTOGEN.md +++ b/ts/packages/vscode-shell/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # vscode-shell — AI-generated documentation @@ -12,11 +12,11 @@ ## Overview -The `vscode-shell` package integrates the TypeAgent shell chat into Visual Studio Code, allowing users to interact with TypeAgent conversations directly within the editor. It provides a dedicated side panel and editor tabs for managing conversations hosted by a running TypeAgent agent server. +The `vscode-shell` package integrates the TypeAgent shell chat into Visual Studio Code, providing a way to interact with TypeAgent conversations directly within the editor. It offers a side panel and editor tabs for managing conversations hosted by a running TypeAgent agent server. ## What it does -The `vscode-shell` package provides a comprehensive chat interface within Visual Studio Code, enabling users to interact with the TypeAgent ecosystem. Key features include: +The `vscode-shell` package enables users to interact with the TypeAgent ecosystem through a chat interface embedded in Visual Studio Code. Its key features include: - **Chat Interface**: @@ -174,6 +174,6 @@ External: `ansi_up`, `debug`, `dompurify`, `isomorphic-ws`, `markdown-it`, `micr --- -_Auto-generated against commit `5cbcf613f047f08749d0451296eb1cdc610ae414` on `2026-07-17T18:24:18.404Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter vscode-shell docs:verify-links` to spot-check._ +_Auto-generated against commit `66ead8985b850f2775c9b1a96cb7de1d08e2aee1` on `2026-07-18T01:38:20.033Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter vscode-shell docs:verify-links` to spot-check._ From b5e02f44cf7b29416deb8cbfbdd218ddbb28316a Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Sat, 18 Jul 2026 01:50:19 +0000 Subject: [PATCH 08/12] docs: regenerate README.AUTOGEN.md, command reference, and action browser --- ts/packages/shell/README.AUTOGEN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/packages/shell/README.AUTOGEN.md b/ts/packages/shell/README.AUTOGEN.md index 3dd677316..658ba7921 100644 --- a/ts/packages/shell/README.AUTOGEN.md +++ b/ts/packages/shell/README.AUTOGEN.md @@ -3,7 +3,7 @@ - + # agent-shell — AI-generated documentation @@ -16,7 +16,7 @@ The `agent-shell` package is a TypeScript library that serves as the graphical u ## What it does -The `agent-shell` package provides a rich set of features to enable interactive and conversational agent experiences: +The `agent-shell` package provides a range of features to enable interactive and conversational agent experiences: ### Conversation Management @@ -172,6 +172,6 @@ _5 environment variables referenced from `./src/` (set in `ts/.env` or your shel --- -_Auto-generated against commit `5cbcf613f047f08749d0451296eb1cdc610ae414` on `2026-07-17T18:24:18.404Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-shell docs:verify-links` to spot-check._ +_Auto-generated against commit `bdf0bb82a95a3a61dfc2a32ea439f70c96228154` on `2026-07-18T01:48:24.228Z` by `docs-generate.yml`. Links validated at that commit; the working tree may have drifted by up to 24h. Re-run `pnpm --filter agent-shell docs:verify-links` to spot-check._ From 3e83f8ed527b1846de8fbf4a6fee7c0e91f78dce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:09:51 +0000 Subject: [PATCH 09/12] Fix lint ratchet failures in restart logging --- ts/packages/agentServer/server/src/server.ts | 4 ++-- ts/packages/agentServer/server/src/staleBuild.ts | 8 +++----- ts/packages/cli/src/slashCommands.ts | 12 ++++-------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/ts/packages/agentServer/server/src/server.ts b/ts/packages/agentServer/server/src/server.ts index cd665af27..e77f0bd8c 100644 --- a/ts/packages/agentServer/server/src/server.ts +++ b/ts/packages/agentServer/server/src/server.ts @@ -253,8 +253,8 @@ async function main() { // us. Releasing the port/lock *before* spawning keeps the successor's bind // and lock acquisition from racing this process. async function restartServer() { - console.log( - "\x1b[1;30;43m Restart requested - relaunching agent server... \x1b[0m", + process.stdout.write( + "\x1b[1;30;43m Restart requested - relaunching agent server... \x1b[0m\n", ); await teardownServer(); const child = spawn( diff --git a/ts/packages/agentServer/server/src/staleBuild.ts b/ts/packages/agentServer/server/src/staleBuild.ts index f56e31188..8940ce428 100644 --- a/ts/packages/agentServer/server/src/staleBuild.ts +++ b/ts/packages/agentServer/server/src/staleBuild.ts @@ -42,11 +42,9 @@ function printStaleBanner(): void { if (process.stdout.isTTY !== true) { // No colors/box for non-interactive logs - just make it greppable. - console.log(""); - for (const line of lines) { - console.log(`[stale-build] ${line}`); - } - console.log(""); + process.stdout.write( + "\n" + lines.map((line) => `[stale-build] ${line}`).join("\n") + "\n\n", + ); return; } diff --git a/ts/packages/cli/src/slashCommands.ts b/ts/packages/cli/src/slashCommands.ts index 692136189..ab5bef952 100644 --- a/ts/packages/cli/src/slashCommands.ts +++ b/ts/packages/cli/src/slashCommands.ts @@ -380,17 +380,13 @@ const slashCommands: SlashCommand[] = [ handler: async () => { const port = serverPort ?? AGENT_SERVER_DEFAULT_PORT; if (!serverConnection) { - console.log( - chalk.red( - "Not connected to an agent server; cannot restart.", - ), + process.stdout.write( + `${chalk.red("Not connected to an agent server; cannot restart.")}\n`, ); return; } - console.log( - chalk.dim( - `Requesting restart of server on port ${port}; it will relaunch and this CLI will disconnect. Reconnect with 'agent-cli connect'.`, - ), + process.stdout.write( + `${chalk.dim(`Requesting restart of server on port ${port}; it will relaunch and this CLI will disconnect. Reconnect with 'agent-cli connect'.`)}\n`, ); try { await serverConnection.restart(); From b41fc693f7db3ac44a80df17b2f92ca1a3b08a61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:10:43 +0000 Subject: [PATCH 10/12] Route stale-build non-TTY warning to stderr --- ts/packages/agentServer/server/src/staleBuild.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/packages/agentServer/server/src/staleBuild.ts b/ts/packages/agentServer/server/src/staleBuild.ts index 8940ce428..58b049aae 100644 --- a/ts/packages/agentServer/server/src/staleBuild.ts +++ b/ts/packages/agentServer/server/src/staleBuild.ts @@ -42,7 +42,7 @@ function printStaleBanner(): void { if (process.stdout.isTTY !== true) { // No colors/box for non-interactive logs - just make it greppable. - process.stdout.write( + process.stderr.write( "\n" + lines.map((line) => `[stale-build] ${line}`).join("\n") + "\n\n", ); return; From 4d3ef0a0326ac9f4ba8808cafb67e0f1d76f3080 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:11:28 +0000 Subject: [PATCH 11/12] Send stale-build restart warnings to stderr --- ts/packages/agentServer/server/src/server.ts | 2 +- ts/packages/agentServer/server/src/staleBuild.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/packages/agentServer/server/src/server.ts b/ts/packages/agentServer/server/src/server.ts index e77f0bd8c..abc99eb6d 100644 --- a/ts/packages/agentServer/server/src/server.ts +++ b/ts/packages/agentServer/server/src/server.ts @@ -253,7 +253,7 @@ async function main() { // us. Releasing the port/lock *before* spawning keeps the successor's bind // and lock acquisition from racing this process. async function restartServer() { - process.stdout.write( + process.stderr.write( "\x1b[1;30;43m Restart requested - relaunching agent server... \x1b[0m\n", ); await teardownServer(); diff --git a/ts/packages/agentServer/server/src/staleBuild.ts b/ts/packages/agentServer/server/src/staleBuild.ts index 58b049aae..aa8ddb4b7 100644 --- a/ts/packages/agentServer/server/src/staleBuild.ts +++ b/ts/packages/agentServer/server/src/staleBuild.ts @@ -56,7 +56,7 @@ function printStaleBanner(): void { return "| " + text + " ".repeat(inner - text.length) + " |"; }; const box = ["+" + bar + "+", ...lines.map(pad), "+" + bar + "+"]; - process.stdout.write( + process.stderr.write( "\n" + box.map((l) => `${YELLOW_BG}${l}${RESET}`).join("\n") + "\n\n", ); } From 3b44b9a58a3513b72c61d4627d2e056fe455acf3 Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Sat, 18 Jul 2026 03:24:24 +0000 Subject: [PATCH 12/12] style: apply prettier formatting and policy fixes --- ts/packages/agentServer/server/src/staleBuild.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts/packages/agentServer/server/src/staleBuild.ts b/ts/packages/agentServer/server/src/staleBuild.ts index aa8ddb4b7..102457283 100644 --- a/ts/packages/agentServer/server/src/staleBuild.ts +++ b/ts/packages/agentServer/server/src/staleBuild.ts @@ -43,7 +43,9 @@ function printStaleBanner(): void { if (process.stdout.isTTY !== true) { // No colors/box for non-interactive logs - just make it greppable. process.stderr.write( - "\n" + lines.map((line) => `[stale-build] ${line}`).join("\n") + "\n\n", + "\n" + + lines.map((line) => `[stale-build] ${line}`).join("\n") + + "\n\n", ); return; }