From 9b0d5fa0f0556bbf285fa85de608ba4c662ed06b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 23 Mar 2026 13:38:04 -0700 Subject: [PATCH 01/14] UPdated to allow for long running MCP server loading --- .../src/mcpAgentProvider.ts | 266 ++++++++++++-- .../src/agentProvider/agentProvider.ts | 10 + .../dispatcher/src/context/appAgentManager.ts | 330 ++++++++---------- .../src/context/commandHandlerContext.ts | 112 +++--- .../system/handlers/configCommandHandlers.ts | 58 +-- 5 files changed, 460 insertions(+), 316 deletions(-) diff --git a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts index c8625e5521..0db46b55fa 100644 --- a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts +++ b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts @@ -3,6 +3,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { AppAgent, AppAgentManifest } from "@typeagent/agent-sdk"; import { createActionResult } from "@typeagent/agent-sdk/helpers/action"; import { @@ -12,7 +13,6 @@ import { import { AppAgentProvider } from "agent-dispatcher"; import { ArgDefinitions, - ParameterDefinitions, ParsedCommandParams, ActionContext, } from "@typeagent/agent-sdk"; @@ -21,6 +21,12 @@ import { getCommandInterface, } from "@typeagent/agent-sdk/helpers/command"; import { InstanceConfig, InstanceConfigProvider } from "./utils/config.js"; +import { spawn, ChildProcess } from "child_process"; +import net from "net"; +import registerDebug from "debug"; + +const debug = registerDebug("typeagent:mcp"); +const debugError = registerDebug("typeagent:mcp:error"); export type McpAppAgentInfo = { emojiChar: string; @@ -30,12 +36,16 @@ export type McpAppAgentInfo = { actionDefaultEnabled?: boolean; serverScript?: string; serverScriptArgs?: string[] | ArgDefinitions; + serverUrl?: string; + serverCommand?: string; + serverCommandArgs?: string[]; }; export type McpAppAgent = { manifest: AppAgentManifest; agent: AppAgent; - transport: StdioClientTransport | undefined; + transport: StdioClientTransport | StreamableHTTPClientTransport | undefined; + serverProcess?: ChildProcess | undefined; }; export type McpAppAgentRecord = { agentP: Promise; @@ -52,11 +62,83 @@ function convertSchema(tools: any) { return JSON.stringify(toJSONParsedActionSchema(pas)); } +// Check if anything is already listening on the port — raw TCP, no MCP handshake. +// This prevents us from launching a second server when one (even a broken one) is +// already bound to the port, which would cause compilation + bind-failure hangs. +function isPortOccupied(url: string): Promise { + return new Promise((resolve) => { + try { + const parsed = new URL(url); + const port = parseInt( + parsed.port || + (parsed.protocol === "https:" ? "443" : "80"), + ); + const socket = net.createConnection(port, parsed.hostname); + socket.once("connect", () => { + socket.destroy(); + resolve(true); + }); + socket.once("error", () => resolve(false)); + socket.setTimeout(2000, () => { + socket.destroy(); + resolve(false); + }); + } catch { + resolve(false); + } + }); +} + +function launchHttpServer( + command: string, + args: string[], +): Promise { + return new Promise((resolve, reject) => { + const proc = spawn(command, args, { + stdio: ["ignore", "pipe", "pipe"], + }); + let started = false; + const timeout = setTimeout(() => { + if (!started) { + proc.kill(); + reject(new Error(`HTTP MCP server failed to start within 180s`)); + } + }, 180000); + const onData = (data: Buffer) => { + const line = data.toString().trimEnd(); + debug(`[server] ${line}`); + if (!started && line.includes("Now listening on")) { + started = true; + clearTimeout(timeout); + resolve(proc); + } + }; + proc.stdout?.on("data", onData); + proc.stderr?.on("data", onData); + proc.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + proc.on("exit", (code) => { + clearTimeout(timeout); + if (!started) { + reject( + new Error(`HTTP MCP server exited with code ${code} before starting`), + ); + } + }); + }); +} + function createMcpAppAgentTransport( appAgentName: string, info: McpAppAgentInfo, instanceConfig?: McpAppAgentConfig, -) { +): StdioClientTransport | StreamableHTTPClientTransport { + if (info.serverUrl !== undefined) { + return new StreamableHTTPClientTransport(new URL(info.serverUrl)); + } + const serverScriptPath = info.serverScript; if (serverScriptPath === undefined) { throw new Error(`Invalid app agent: ${appAgentName}`); @@ -110,18 +192,8 @@ function getMcpCommandHandlerTable( }, run: async ( context: ActionContext, - params: ParsedCommandParams, + params: ParsedCommandParams<{}>, ) => { - const serverArgs: string[] = []; - if (params.args) { - for (const value of Object.values(params.args)) { - if (Array.isArray(value)) { - serverArgs.push(...value.map(String)); - } else if (value !== undefined) { - serverArgs.push(String(value)); - } - } - } const instanceConfig: InstanceConfig = structuredClone( configs.getInstanceConfig(), ); @@ -129,11 +201,11 @@ function getMcpCommandHandlerTable( instanceConfig.mcpServers = {}; } instanceConfig.mcpServers[appAgentName] = { - serverScriptArgs: serverArgs, + serverScriptArgs: params.tokens, }; configs.setInstanceConfig(instanceConfig); context.actionIO.appendDisplay( - `Server arguments set to ${serverArgs.join(" ")}. Please restart TypeAgent to reflect the change.`, + `Server arguments set to ${params.tokens.join(" ")}. Please restart TypeAgent to reflect the change.`, ); }, }, @@ -170,17 +242,51 @@ function createMcpAppAgentRecord( } const createMcpAppAgent = async (): Promise => { - let transport: StdioClientTransport | undefined; + let transport: StdioClientTransport | StreamableHTTPClientTransport | undefined; + let serverProcess: ChildProcess | undefined; let agent: AppAgent; try { + if (info.serverCommand !== undefined) { + const occupied = + info.serverUrl !== undefined && + (await isPortOccupied(info.serverUrl)); + debug( + `[${appAgentName}] serverUrl=${info.serverUrl} port occupied=${occupied}`, + ); + if (!occupied) { + debug( + `[${appAgentName}] launching server: ${info.serverCommand} ${(info.serverCommandArgs ?? []).join(" ")}`, + ); + serverProcess = await launchHttpServer( + info.serverCommand, + info.serverCommandArgs ?? [], + ); + debug( + `[${appAgentName}] server process started (pid ${serverProcess.pid})`, + ); + } else { + debug( + `[${appAgentName}] server already running, skipping launch`, + ); + } + } + const transportUrl = + info.serverUrl ?? info.serverScript ?? "(stdio)"; + debug( + `[${appAgentName}] connecting transport to ${transportUrl}`, + ); transport = createMcpAppAgentTransport( appAgentName, info, instanceConfig, ); const client = new Client({ name: clientName, version }); - await client.connect(transport); + await client.connect(transport as any); + debug(`[${appAgentName}] connected, listing tools...`); const tools = (await client.listTools()).tools; + debug( + `[${appAgentName}] found ${tools.length} tool(s): ${tools.map((t) => t.name).join(", ")}`, + ); if (tools.length === 0) { throw new Error( `Invalid app agent config ${appAgentName}: No tools found`, @@ -212,10 +318,17 @@ function createMcpAppAgentRecord( }, }; } catch (error: any) { + debugError( + `[${appAgentName}] failed to connect: ${error?.message ?? error}`, + ); if (transport !== undefined) { transport.close(); transport = undefined; } + if (serverProcess !== undefined) { + serverProcess.kill(); + serverProcess = undefined; + } agent = { updateAgentContext() { // Delay throwing error until the agent is used. @@ -240,6 +353,7 @@ function createMcpAppAgentRecord( manifest, transport, agent, + serverProcess, }; }; return { @@ -255,14 +369,71 @@ export function createMcpAppAgentProvider( configs?: InstanceConfigProvider, ): AppAgentProvider { const instanceConfig = configs?.getInstanceConfig()?.mcpServers; - const mcpAppAgents = new Map(); + + // For server-command agents: background records that start the server eagerly. + // count is kept at 1 (background ref) so the server process is never killed + // by a stray unload. They are moved into mcpAppAgents on first loadAppAgent. + const backgroundRecords = new Map(); + + // Callbacks registered via onSchemaReady() + const schemaReadyCallbacks: (( + agentName: string, + manifest: AppAgentManifest, + ) => void)[] = []; + + // Manifests that are already resolved (so late-registered callbacks fire immediately) + const resolvedManifests = new Map(); + + function startBackgroundAgent(appAgentName: string) { + if ( + backgroundRecords.has(appAgentName) || + mcpAppAgents.has(appAgentName) + ) { + return; + } + const info = infos[appAgentName]; + if (info === undefined || info.serverCommand === undefined) { + return; + } + const record = createMcpAppAgentRecord( + name, + version, + appAgentName, + info, + configs, + instanceConfig?.[appAgentName], + ); + backgroundRecords.set(appAgentName, record); + + record.agentP + .then((agentData) => { + if (agentData.transport !== undefined) { + resolvedManifests.set(appAgentName, agentData.manifest); + for (const cb of schemaReadyCallbacks) { + cb(appAgentName, agentData.manifest); + } + } + }) + .catch(() => { + // errors surface when the agent is actually used + }); + } + function getMpcAppAgentRecord(appAgentName: string) { const existing = mcpAppAgents.get(appAgentName); if (existing !== undefined) { existing.count++; return existing; } + // Promote a background record (server already loading/loaded) + const background = backgroundRecords.get(appAgentName); + if (background !== undefined) { + background.count++; + backgroundRecords.delete(appAgentName); + mcpAppAgents.set(appAgentName, background); + return background; + } const info = infos[appAgentName]; if (info === undefined) { throw new Error(`Invalid app agent: ${appAgentName}`); @@ -278,19 +449,63 @@ export function createMcpAppAgentProvider( mcpAppAgents.set(appAgentName, record); return record; } + return { getAppAgentNames() { return Object.keys(infos); }, + + getLoadingAgentNames() { + return [...backgroundRecords.keys()]; + }, + + onSchemaReady(callback) { + schemaReadyCallbacks.push(callback); + // Fire immediately for any agents already resolved + for (const [agentName, manifest] of resolvedManifests) { + callback(agentName, manifest); + } + }, + async getAppAgentManifest(appAgentName: string) { + const info = infos[appAgentName]; + if (info === undefined) { + throw new Error(`Invalid app agent: ${appAgentName}`); + } + if (info.serverCommand !== undefined) { + // Non-blocking: kick off background server startup and return a + // stub manifest immediately. The real manifest (with schema) is + // delivered later via the onSchemaReady callback. + startBackgroundAgent(appAgentName); + // Include a stub schema so the agent row appears in @config + // (default view filters by schema names). The empty content + // will fail to parse, showing ❌ while loading. refreshAgentSchema + // replaces it with the real schema once the server is ready. + return { + emojiChar: info.emojiChar, + description: info.description, + defaultEnabled: info.defaultEnabled, + schema: { + description: info.description, + schemaType: entryTypeName, + schemaFile: { + format: "pas" as const, + content: "", + }, + }, + } as AppAgentManifest; + } + // Stdio agents start fast — keep blocking path. const record = getMpcAppAgentRecord(appAgentName); const manifest = (await record.agentP).manifest; await this.unloadAppAgent(appAgentName); return manifest; }, + async loadAppAgent(appAgentName: string) { return (await getMpcAppAgentRecord(appAgentName).agentP).agent; }, + async unloadAppAgent(appAgentName: string) { const record = mcpAppAgents.get(appAgentName); if (!record || record.count === 0) { @@ -301,11 +516,16 @@ export function createMcpAppAgentProvider( const agent = await record.agentP; const transport = agent.transport; if (transport !== undefined) { - return new Promise((resolve) => { - transport.onclose = resolve; - transport.close(); - }); + if (transport instanceof StreamableHTTPClientTransport) { + await transport.close(); + } else { + return new Promise((resolve) => { + transport.onclose = resolve; + transport.close(); + }); + } } + agent.serverProcess?.kill(); } }, }; diff --git a/ts/packages/dispatcher/dispatcher/src/agentProvider/agentProvider.ts b/ts/packages/dispatcher/dispatcher/src/agentProvider/agentProvider.ts index 116753c648..35dbb16772 100644 --- a/ts/packages/dispatcher/dispatcher/src/agentProvider/agentProvider.ts +++ b/ts/packages/dispatcher/dispatcher/src/agentProvider/agentProvider.ts @@ -9,6 +9,16 @@ export interface AppAgentProvider { loadAppAgent(appAgentName: string): Promise; unloadAppAgent(appAgentName: string): Promise; setTraceNamespaces?(namespaces: string): void; + // Optional: providers that start slowly can return a stub manifest from + // getAppAgentManifest and call the registered callback with the real + // manifest once the agent is ready. + onSchemaReady?: ( + callback: (agentName: string, manifest: AppAgentManifest) => void, + ) => void; + // Optional: returns the names of agents currently loading asynchronously. + // Only these agents should show ⏳ in the UI. If omitted, no agents are + // treated as loading. + getLoadingAgentNames?(): string[]; } export interface AppAgentInstaller { diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index bcf5389527..a27a8f9779 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -20,6 +20,7 @@ import { import { getAppAgentName } from "../translation/agentTranslators.js"; import { createSessionContext } from "../execute/sessionContext.js"; import { AppAgentProvider } from "../agentProvider/agentProvider.js"; +import { getPackageFilePath } from "../utils/getPackageFilePath.js"; import registerDebug from "debug"; import { DispatcherName } from "./dispatcher/dispatcherUtils.js"; import { @@ -40,7 +41,6 @@ import { AgentGrammarRegistry, compileGrammarToNFA, enrichGrammarWithCheckedVariables, - loadGrammarRulesNoThrow, } from "action-grammar"; import fs from "node:fs"; import { FlowDefinition } from "../execute/flowInterpreter.js"; @@ -120,6 +120,7 @@ function loadGrammar(actionConfig: ActionConfig): Grammar | undefined { export class AppAgentManager implements ActionConfigProvider { private readonly agents = new Map(); private readonly actionConfigs = new Map(); + private readonly loadingSchemas = new Set(); private readonly flowRegistry = new Map(); private readonly transientAgents: Record = {}; private readonly actionSemanticMap?: ActionSchemaSemanticMap; @@ -149,6 +150,10 @@ export class AppAgentManager implements ActionConfigProvider { return Array.from(this.agents.keys()); } + public isSchemaLoading(schemaName: string): boolean { + return this.loadingSchemas.has(schemaName); + } + public getAppAgentDescription(appAgentName: string) { const record = this.getRecord(appAgentName); return record.manifest.description; @@ -276,6 +281,7 @@ export class AppAgentManager implements ActionConfigProvider { actionEmbeddingCache?: EmbeddingCache, agentGrammarRegistry?: AgentGrammarRegistry, useNFAGrammar?: boolean, + stateRefreshFn?: () => Promise, ) { const agentNames = provider.getAppAgentNames(); const semanticMapP: Promise[] = []; @@ -296,6 +302,94 @@ export class AppAgentManager implements ActionConfigProvider { debug("Waiting for action embeddings"); await Promise.all(semanticMapP); debug("Finish action embeddings"); + + if (provider.onSchemaReady && stateRefreshFn) { + // Mark only the agents that are actually loading asynchronously (e.g. + // serverCommand MCP agents with slow startup). Agents that failed + // synchronously should show ❌, not ⏳. + const loadingNames = new Set( + provider.getLoadingAgentNames?.() ?? [], + ); + for (const name of agentNames) { + if (!loadingNames.has(name)) { + continue; + } + const record = this.agents.get(name); + if (record) { + for (const schemaName of Object.keys( + convertToActionConfig(name, record.manifest), + )) { + this.loadingSchemas.add(schemaName); + } + } + } + + provider.onSchemaReady(async (agentName, manifest) => { + try { + const refreshSemanticMapP: Promise[] = []; + this.refreshAgentSchema( + agentName, + manifest, + refreshSemanticMapP, + actionGrammarStore, + actionEmbeddingCache, + agentGrammarRegistry, + useNFAGrammar, + ); + await Promise.all(refreshSemanticMapP); + await stateRefreshFn(); + } catch (e) { + debugError( + `Failed to refresh schema for agent '${agentName}': ${e}`, + ); + } + }); + } + } + + private refreshAgentSchema( + appAgentName: string, + manifest: AppAgentManifest, + semanticMapP: Promise[], + actionGrammarStore: GrammarStore | undefined, + actionEmbeddingCache?: EmbeddingCache, + agentGrammarRegistry?: AgentGrammarRegistry, + useNFAGrammar?: boolean, + ) { + const record = this.agents.get(appAgentName); + if (record === undefined) { + throw new Error(`Agent not found: ${appAgentName}`); + } + + // Update the manifest (emoji, schema, etc.) + record.manifest = manifest; + + const actionConfigs = convertToActionConfig(appAgentName, manifest); + for (const [schemaName, config] of Object.entries(actionConfigs)) { + debug(`Refreshing action config: ${schemaName}`); + this.actionConfigs.set(schemaName, config); + if (config.transient) { + this.transientAgents[schemaName] = false; + } + try { + const actionSchemaFile = + this.actionSchemaFileCache.getActionSchemaFile(config); + if (this.actionSemanticMap) { + semanticMapP.push( + this.actionSemanticMap.addActionSchemaFile( + config, + actionSchemaFile, + actionEmbeddingCache, + ), + ); + } + record.schemaErrors.delete(schemaName); + this.loadingSchemas.delete(schemaName); + } catch (e: any) { + record.schemaErrors.set(schemaName, e); + this.loadingSchemas.delete(schemaName); + } + } } private addAgentManifest( @@ -373,14 +467,25 @@ export class AppAgentManager implements ActionConfigProvider { // Add to NFA grammar registry if using NFA system if (useNFAGrammar && agentGrammarRegistry) { try { - // Enrich grammar with checked variables from the already-parsed action schema - enrichGrammarWithCheckedVariables( - g, - actionSchemaFile.parsedActionSchema, - ); - debug( - `Enriched grammar with checked variables for schema: ${schemaName}`, - ); + // Enrich grammar with checked variables from .pas.json if available + if (config.compiledSchemaFilePath) { + try { + const pasJsonPath = getPackageFilePath( + config.compiledSchemaFilePath, + ); + enrichGrammarWithCheckedVariables( + g, + pasJsonPath, + ); + debug( + `Enriched grammar with checked variables for schema: ${schemaName}`, + ); + } catch (enrichError) { + debug( + `Could not enrich grammar with checked variables for ${schemaName}: ${enrichError}`, + ); + } + } const nfa = compileGrammarToNFA(g, schemaName); agentGrammarRegistry.registerAgent( @@ -662,16 +767,22 @@ export class AppAgentManager implements ActionConfigProvider { ); if (enableSchema !== record.schemas.has(name)) { if (enableSchema) { - const e = record.schemaErrors.get(name); - if (e !== undefined) { - failedSchemas.push([name, enableSchema, e]); - debugError( - `Schema '${name}' is not enabled because of error: ${e.message}`, - ); + if (this.loadingSchemas.has(name)) { + // Schema is still loading (e.g. slow MCP server start). + // Skip for now; refreshAgentSchema will re-run setState once ready. + debug(`Schema '${name}' is still loading, skipping enable`); } else { - record.schemas.add(name); - changedSchemas.push([name, enableSchema]); - debug(`Schema enabled ${name}`); + const e = record.schemaErrors.get(name); + if (e !== undefined) { + failedSchemas.push([name, enableSchema, e]); + debugError( + `Schema '${name}' is not enabled because of error: ${e.message}`, + ); + } else { + record.schemas.add(name); + changedSchemas.push([name, enableSchema]); + debug(`Schema enabled ${name}`); + } } } else { record.schemas.delete(name); @@ -688,21 +799,26 @@ export class AppAgentManager implements ActionConfigProvider { failedActions, ); if (enableAction !== record.actions.has(name)) { - p.push( - (async () => { - try { - await this.updateAction( - name, - record, - enableAction, - context, - ); - changedActions.push([name, enableAction]); - } catch (e: any) { - failedActions.push([name, enableAction, e]); - } - })(), - ); + if (enableAction && this.loadingSchemas.has(name)) { + // Action agent is still loading — skip to avoid blocking on server startup. + debug(`Action '${name}' is still loading, skipping enable`); + } else { + p.push( + (async () => { + try { + await this.updateAction( + name, + record, + enableAction, + context, + ); + changedActions.push([name, enableAction]); + } catch (e: any) { + failedActions.push([name, enableAction, e]); + } + })(), + ); + } } } @@ -796,140 +912,6 @@ export class AppAgentManager implements ActionConfigProvider { await Promise.all(closeP); } - private async loadDynamicGrammar( - schemaName: string, - appAgent: AppAgent, - sessionContext: SessionContext, - context: CommandHandlerContext, - ): Promise { - let dynamicGrammar: Grammar | undefined; - - // Prefer the agent callback over file-based convention - if (appAgent.getDynamicGrammar) { - const grammarContent = await appAgent.getDynamicGrammar( - sessionContext, - schemaName, - ); - if (grammarContent?.content?.trim()) { - if (grammarContent.format === "ag") { - dynamicGrammar = grammarFromJson( - JSON.parse(grammarContent.content), - ); - } else { - // "agr" format — raw grammar rule text - const errors: string[] = []; - dynamicGrammar = - loadGrammarRulesNoThrow( - `${schemaName}-dynamic.agr`, - grammarContent.content, - errors, - ) ?? undefined; - if (errors.length > 0) { - debugError( - `Failed to parse dynamic grammar for ${schemaName}: ${errors.join(", ")}`, - ); - } - } - } - } - - // Fallback: read grammar/dynamic.agr from instance storage - if (dynamicGrammar === undefined) { - const instanceStorage = sessionContext.instanceStorage; - if (instanceStorage) { - try { - const agrText = await instanceStorage.read( - "grammar/dynamic.agr", - "utf8", - ); - if (agrText?.trim()) { - const errors: string[] = []; - dynamicGrammar = - loadGrammarRulesNoThrow( - `${schemaName}-dynamic.agr`, - agrText, - errors, - ) ?? undefined; - if (errors.length > 0) { - debugError( - `Failed to parse dynamic grammar for ${schemaName}: ${errors.join(", ")}`, - ); - } - } - } catch { - // No dynamic grammar file - } - } - } - - if (!dynamicGrammar || dynamicGrammar.rules.length === 0) return; - - const config = this.actionConfigs.get(schemaName); - const staticGrammar = config ? loadGrammar(config) : undefined; - - const merged: Grammar = { - rules: [...(staticGrammar?.rules ?? []), ...dynamicGrammar.rules], - }; - - context.agentCache.grammarStore.addGrammar(schemaName, merged); - debug( - `Loaded dynamic grammar for ${schemaName} (${dynamicGrammar.rules.length} dynamic rules merged with ${staticGrammar?.rules.length ?? 0} static rules)`, - ); - } - - private async loadDynamicSchema( - schemaName: string, - appAgent: AppAgent, - sessionContext: SessionContext, - context: CommandHandlerContext, - ): Promise { - if (!appAgent.getDynamicSchema) return; - - const schemaContent = await appAgent.getDynamicSchema( - sessionContext, - schemaName, - ); - if (!schemaContent?.content) return; - - const config = this.actionConfigs.get(schemaName); - if (!config) return; - - // Replace the schema content with the dynamic version - config.schemaFile = schemaContent; - - // Invalidate cached parsed schema so it gets re-parsed from new content - this.actionSchemaFileCache.unloadActionSchemaFile(schemaName); - - // Clear translator cache so next translation uses the updated schema - context.translatorCache.clear(); - - debug(`Loaded dynamic schema for ${schemaName}`); - } - - public async reloadAgentSchema( - schemaName: string, - context: CommandHandlerContext, - ): Promise { - const appAgentName = getAppAgentName(schemaName); - const record = this.getRecord(appAgentName); - if (!record.appAgent || !record.sessionContext) { - throw new Error(`Agent '${appAgentName}' is not initialized`); - } - - await this.loadDynamicSchema( - schemaName, - record.appAgent, - record.sessionContext, - context, - ); - await this.loadDynamicGrammar( - schemaName, - record.appAgent, - record.sessionContext, - context, - ); - } - private async updateAction( schemaName: string, record: AppAgentRecord, @@ -954,20 +936,6 @@ export class AppAgentManager implements ActionConfigProvider { schemaName, ), ); - - // Load dynamic schema and grammar from agent callbacks - await this.loadDynamicSchema( - schemaName, - record.appAgent!, - sessionContext, - context, - ); - await this.loadDynamicGrammar( - schemaName, - record.appAgent!, - sessionContext, - context, - ); } catch (e) { // Rollback if there is a exception record.actions.delete(schemaName); diff --git a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts index 46a39cfd73..169783a5ee 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts @@ -82,7 +82,6 @@ import { createSchemaInfoProvider } from "../translation/actionSchemaFileCache.j import { createBuiltinAppAgentProvider } from "./inlineAgentProvider.js"; import { CommandResult } from "@typeagent/dispatcher-types"; import { DispatcherName } from "./dispatcher/dispatcherUtils.js"; -import { DisplayLog } from "../displayLog.js"; import lockfile from "proper-lockfile"; import { IndexManager } from "./indexManager.js"; import { ActionContextWithClose } from "../execute/actionContext.js"; @@ -101,51 +100,6 @@ import { DefaultAzureCredential } from "@azure/identity"; const debug = registerDebug("typeagent:dispatcher:init"); const debugError = registerDebug("typeagent:dispatcher:init:error"); -function wrapClientIOWithDisplayLog( - clientIO: ClientIO, - displayLog: DisplayLog, -): ClientIO { - return { - ...clientIO, - setUserRequest(requestId, command) { - const seq = displayLog.logUserRequest(requestId, command); - clientIO.setUserRequest(requestId, command, seq); - }, - setDisplayInfo(requestId, source, actionIndex?, action?) { - const seq = displayLog.logSetDisplayInfo( - requestId, - source, - actionIndex, - action, - ); - clientIO.setDisplayInfo( - requestId, - source, - actionIndex, - action, - seq, - ); - }, - setDisplay(message) { - const seq = displayLog.logSetDisplay(message); - clientIO.setDisplay(message, seq); - }, - appendDisplay(message, mode) { - const seq = displayLog.logAppendDisplay(message, mode); - clientIO.appendDisplay(message, mode, seq); - }, - notify(notificationId, event, data, source) { - const seq = displayLog.logNotify( - notificationId, - event, - data, - source, - ); - clientIO.notify(notificationId, event, data, source, seq); - }, - }; -} - export type EmptyFunction = () => void; export type SetSettingFunction = (name: string, value: any) => void; @@ -203,7 +157,6 @@ export type CommandHandlerContext = { currentScriptDir: string; logger?: Logger | undefined; currentRequestId: RequestId | undefined; - currentAbortSignal: AbortSignal | undefined; noReasoning: boolean; commandResult?: CommandResult | undefined; chatHistory: ChatHistory; @@ -223,16 +176,10 @@ export type CommandHandlerContext = { commandProfiler?: Profiler | undefined; promptLogger?: PromptLogger | undefined; - // Maps requestId string → AbortController for in-flight commands. - // Used by cancelCommand() to abort a running command. - activeRequests: Map; - instanceDirLock: (() => Promise) | undefined; userRequestKnowledgeExtraction: boolean; actionResultKnowledgeExtraction: boolean; - - displayLog: DisplayLog; }; export function getRequestId(context: CommandHandlerContext): RequestId { @@ -481,6 +428,7 @@ async function addAppAgentProviders( ); if (appAgentProviders) { + const stateRefreshFn = () => setAppAgentStates(context); for (const provider of appAgentProviders) { await context.agents.addProvider( provider, @@ -488,6 +436,7 @@ async function addAppAgentProviders( embeddingCache, context.agentGrammarRegistry, useNFAGrammar, + stateRefreshFn, ); } } @@ -593,11 +542,7 @@ export async function initializeCommandHandlerContext( } const sessionDirPath = session.getSessionDirPath(); debug(`Session directory: ${sessionDirPath}`); - const displayLog = await DisplayLog.load(sessionDirPath); - const clientIO = wrapClientIOWithDisplayLog( - options?.clientIO ?? nullClientIO, - displayLog, - ); + const clientIO = options?.clientIO ?? nullClientIO; const loggerSink = getLoggerSink(() => context.dblogging, clientIO); const logger = new ChildLogger(loggerSink, DispatcherName, { hostName, @@ -637,7 +582,6 @@ export async function initializeCommandHandlerContext( // Runtime context commandLock: createLimiter(1), // Make sure we process one command at a time. currentRequestId: undefined, - currentAbortSignal: undefined, noReasoning: false, pendingToggleTransientAgents: [], agentCache: await getAgentCache( @@ -659,7 +603,6 @@ export async function initializeCommandHandlerContext( promptLogger: createPromptLogger(getCosmosFactories()), batchMode: false, pendingChoiceRoutes: new Map(), - activeRequests: new Map(), instanceDirLock, constructionProvider, collectCommandResult: options?.collectCommandResult ?? false, @@ -674,8 +617,6 @@ export async function initializeCommandHandlerContext( actionResultKnowledgeExtraction: options?.conversationMemorySettings ?.actionResultKnowledgeExtraction ?? true, - - displayLog, }; await initializeMemory(context, sessionDirPath); @@ -811,20 +752,53 @@ async function setupGrammarGeneration(context: CommandHandlerContext) { // Enable auto-save await grammarStore.setAutoSave(config.cache.autoSave); + // Import getPackageFilePath for resolving schema paths + const { getPackageFilePath } = await import( + "../utils/getPackageFilePath.js" + ); + // Configure agent cache with grammar generation support context.agentCache.configureGrammarGeneration( context.agentGrammarRegistry, grammarStore, true, (schemaName: string) => { - const actionSchemaFile = - context.agents.tryGetActionSchemaFile(schemaName); - if (!actionSchemaFile) { + // Get compiled schema file path (.pas.json) from action config for grammar generation + const actionConfig = context.agents.tryGetActionConfig(schemaName); + if (!actionConfig) { throw new Error( - `Action schema file not found for schema: ${schemaName}`, + `Action config not found for schema: ${schemaName}`, ); } - return actionSchemaFile.parsedActionSchema; + + // Prefer explicit compiledSchemaFile field + if (actionConfig.compiledSchemaFilePath) { + return getPackageFilePath(actionConfig.compiledSchemaFilePath); + } + + // Fallback: try to derive .pas.json path from .ts schemaFilePath + if ( + actionConfig.schemaFilePath && + actionConfig.schemaFilePath.endsWith(".ts") + ) { + // Try common pattern: ./src/schema.ts -> ../dist/schema.pas.json + const derivedPath = actionConfig.schemaFilePath + .replace(/^\.\/src\//, "../dist/") + .replace(/\.ts$/, ".pas.json"); + debug( + `Attempting fallback .pas.json path for ${schemaName}: ${derivedPath}`, + ); + try { + return getPackageFilePath(derivedPath); + } catch { + // Fallback path doesn't exist, continue to error + } + } + + throw new Error( + `Compiled schema file path (.pas.json) not found for schema: ${schemaName}. ` + + `Please add 'compiledSchemaFile' field to the manifest pointing to the .pas.json file.`, + ); }, ); @@ -908,7 +882,6 @@ export async function closeCommandHandlerContext( ) { // Save the session because the token count is in it. context.session.save(); - await context.displayLog.save(); await context.agents.close(); if (context.instanceDirLock) { await context.instanceDirLock(); @@ -919,10 +892,7 @@ export async function setSessionOnCommandHandlerContext( context: CommandHandlerContext, session: Session, ) { - // Persist the old session's display log before switching - await context.displayLog.save(); context.session = session; - context.displayLog = await DisplayLog.load(session.getSessionDirPath()); await context.agents.close(); await initializeMemory(context, session.getSessionDirPath()); diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts index 8bfe229ba3..59bd5934a2 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts @@ -18,7 +18,6 @@ import chalk from "chalk"; import { ActionContext, CompletionGroup, - CompletionGroups, ParameterDefinitions, ParsedCommandParams, PartialParsedCommandParams, @@ -255,16 +254,23 @@ function showAgentStatus( if (showSchema || showAction) { for (const name of agents.getSchemaNames()) { + const loading = agents.isSchemaLoading(name); if (showSchema) { const state = agents.isSchemaEnabled(name); const active = agents.isSchemaActive(name); setStatus(status, "schemas", name, state, active, changes); + if (loading && status[name] !== undefined) { + status[name]["schemas"] = "⏳"; + } } if (showAction) { const state = agents.isActionEnabled(name); const active = agents.isActionActive(name); setStatus(status, "actions", name, state, active, changes); + if (loading && status[name] !== undefined) { + status[name]["actions"] = "⏳"; + } } } } @@ -503,7 +509,7 @@ class AgentToggleCommandHandler implements CommandHandler { } } - return { groups: completions }; + return completions; } } @@ -558,7 +564,7 @@ class ExplainerCommandHandler implements CommandHandler { }); } } - return { groups: completions }; + return completions; } } @@ -627,7 +633,7 @@ class ConfigModelSetCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; for (const name of names) { if (name === "model") { @@ -638,7 +644,7 @@ class ConfigModelSetCommandHandler implements CommandHandler { } } - return { groups: completions }; + return completions; } } @@ -747,7 +753,7 @@ class FixedSchemaCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; const systemContext = context.agentContext; for (const name of names) { @@ -758,7 +764,7 @@ class FixedSchemaCommandHandler implements CommandHandler { }); } } - return { groups: completions }; + return completions; } } @@ -840,7 +846,7 @@ class GrammarSystemCommandHandler implements CommandHandler { }); } } - return { groups: completions }; + return completions; } } class GrammarUseDFACommandHandler implements CommandHandler { @@ -883,7 +889,7 @@ class GrammarUseDFACommandHandler implements CommandHandler { completions.push({ name, completions: ["true", "false"] }); } } - return { groups: completions }; + return completions; } } @@ -1220,7 +1226,7 @@ class ConfigRequestCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; const systemContext = context.agentContext; for (const name of names) { @@ -1245,7 +1251,7 @@ class ConfigRequestCommandHandler implements CommandHandler { } } } - return { groups: completions }; + return completions; } } @@ -1458,35 +1464,6 @@ class ConfigExecutionPlanReuseCommandHandler implements CommandHandler { } } -class ConfigExecutionScriptReuseCommandHandler implements CommandHandler { - public readonly description = - "Enable or disable PowerShell script reuse for reasoning actions"; - public readonly parameters = { - args: { - mode: { - description: - "Script reuse mode: 'enabled' to capture and reuse PowerShell scripts, 'disabled' for standard reasoning", - type: "string" as const, - enum: ["enabled", "disabled"], - }, - }, - } as const; - - async run( - context: ActionContext, - params: ParsedCommandParams, - ) { - const mode = params.args.mode as "enabled" | "disabled"; - - await changeContextConfig( - { execution: { scriptReuse: mode } }, - context, - ); - - return displayResult(`Script reuse ${mode}`, context); - } -} - const configExecutionCommandHandlers: CommandHandlerTable = { description: "Execution configuration", commands: { @@ -1501,7 +1478,6 @@ const configExecutionCommandHandlers: CommandHandlerTable = { ), reasoning: new ConfigExecutionReasoningCommandHandler(), planReuse: new ConfigExecutionPlanReuseCommandHandler(), - scriptReuse: new ConfigExecutionScriptReuseCommandHandler(), }, }; From f8d7dd43c4c30e800819814103fe802cd205554f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 23 Mar 2026 13:39:05 -0700 Subject: [PATCH 02/14] lint --- .../src/mcpAgentProvider.ts | 20 +++++++++++-------- .../dispatcher/src/context/appAgentManager.ts | 4 +++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts index 0db46b55fa..50b2ada902 100644 --- a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts +++ b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts @@ -70,8 +70,7 @@ function isPortOccupied(url: string): Promise { try { const parsed = new URL(url); const port = parseInt( - parsed.port || - (parsed.protocol === "https:" ? "443" : "80"), + parsed.port || (parsed.protocol === "https:" ? "443" : "80"), ); const socket = net.createConnection(port, parsed.hostname); socket.once("connect", () => { @@ -101,7 +100,9 @@ function launchHttpServer( const timeout = setTimeout(() => { if (!started) { proc.kill(); - reject(new Error(`HTTP MCP server failed to start within 180s`)); + reject( + new Error(`HTTP MCP server failed to start within 180s`), + ); } }, 180000); const onData = (data: Buffer) => { @@ -123,7 +124,9 @@ function launchHttpServer( clearTimeout(timeout); if (!started) { reject( - new Error(`HTTP MCP server exited with code ${code} before starting`), + new Error( + `HTTP MCP server exited with code ${code} before starting`, + ), ); } }); @@ -242,7 +245,10 @@ function createMcpAppAgentRecord( } const createMcpAppAgent = async (): Promise => { - let transport: StdioClientTransport | StreamableHTTPClientTransport | undefined; + let transport: + | StdioClientTransport + | StreamableHTTPClientTransport + | undefined; let serverProcess: ChildProcess | undefined; let agent: AppAgent; try { @@ -272,9 +278,7 @@ function createMcpAppAgentRecord( } const transportUrl = info.serverUrl ?? info.serverScript ?? "(stdio)"; - debug( - `[${appAgentName}] connecting transport to ${transportUrl}`, - ); + debug(`[${appAgentName}] connecting transport to ${transportUrl}`); transport = createMcpAppAgentTransport( appAgentName, info, diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index a27a8f9779..98b6b3b6b7 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -770,7 +770,9 @@ export class AppAgentManager implements ActionConfigProvider { if (this.loadingSchemas.has(name)) { // Schema is still loading (e.g. slow MCP server start). // Skip for now; refreshAgentSchema will re-run setState once ready. - debug(`Schema '${name}' is still loading, skipping enable`); + debug( + `Schema '${name}' is still loading, skipping enable`, + ); } else { const e = record.schemaErrors.get(name); if (e !== undefined) { From fc2e97ed0d52b239b0498f639c560a73e26f4af5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 14:07:23 -0700 Subject: [PATCH 03/14] Fix TypeScript build errors in agent-dispatcher package (#2051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `agent-dispatcher` package failed to build due to several TypeScript type errors introduced by recent API changes that weren't propagated to all call sites. ### `CompletionGroups` return type mismatch (`configCommandHandlers.ts`) `CommandHandler.getCompletion` was updated to return `Promise` (wrapped object with `groups` property), but 7 implementations still returned `Promise`: ```ts // Before return completions; // CompletionGroup[] // After return { groups: completions }; // CompletionGroups ``` ### Missing properties on `CommandHandlerContext` (`commandHandlerContext.ts`) Three properties used in `command.ts` and `dispatcher.ts` were missing from the type definition: - `currentAbortSignal: AbortSignal | undefined` - `activeRequests: Map` - `displayLog: DisplayLog` Added to the type and initialized in the context factory (`undefined`, `new Map()`, and `await DisplayLog.load(persistDir)` respectively). ### Wrong callback return type in `configureGrammarGeneration` (`commandHandlerContext.ts`) The callback passed to `configureGrammarGeneration` returned a `string` (file path) but the signature requires `(schemaName: string) => ParsedActionSchema`. Also referenced the non-existent `actionConfig.compiledSchemaFilePath` field. Fixed to load and parse the `.pas.json` file: ```ts const content = fs.readFileSync(schemaPath, "utf-8"); return fromJSONParsedActionSchema(JSON.parse(content) as ParsedActionSchemaJSON); ``` ### Non-existent `compiledSchemaFilePath` on `ActionConfig` (`appAgentManager.ts`) Same dead field access; replaced with `config.schemaFilePath?.endsWith(".pas.json")` check.
Original prompt > Fix the failing GitHub Actions workflow build_ts (windows-latest, 20) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68255852060 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23459055003/job/68255852060
--- 📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- .../dispatcher/src/context/appAgentManager.ts | 8 +++- .../src/context/commandHandlerContext.ts | 39 ++++++++++++++----- .../system/handlers/configCommandHandlers.ts | 21 +++++----- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index 98b6b3b6b7..0369d1259e 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -468,10 +468,14 @@ export class AppAgentManager implements ActionConfigProvider { if (useNFAGrammar && agentGrammarRegistry) { try { // Enrich grammar with checked variables from .pas.json if available - if (config.compiledSchemaFilePath) { + const compiledSchemaFilePath = + config.schemaFilePath?.endsWith(".pas.json") + ? config.schemaFilePath + : undefined; + if (compiledSchemaFilePath) { try { const pasJsonPath = getPackageFilePath( - config.compiledSchemaFilePath, + compiledSchemaFilePath, ); enrichGrammarWithCheckedVariables( g, diff --git a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts index 169783a5ee..5bd6e4034f 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts @@ -96,6 +96,11 @@ import fs from "node:fs"; import { CosmosClient, PartitionKeyBuilder } from "@azure/cosmos"; import { CosmosPartitionKeyBuilder } from "telemetry"; import { DefaultAzureCredential } from "@azure/identity"; +import { DisplayLog } from "../displayLog.js"; +import { + fromJSONParsedActionSchema, + ParsedActionSchemaJSON, +} from "@typeagent/action-schema"; const debug = registerDebug("typeagent:dispatcher:init"); const debugError = registerDebug("typeagent:dispatcher:init:error"); @@ -157,10 +162,13 @@ export type CommandHandlerContext = { currentScriptDir: string; logger?: Logger | undefined; currentRequestId: RequestId | undefined; + currentAbortSignal: AbortSignal | undefined; + activeRequests: Map; noReasoning: boolean; commandResult?: CommandResult | undefined; chatHistory: ChatHistory; constructionProvider?: ConstructionProvider | undefined; + displayLog: DisplayLog; batchMode: boolean; pendingChoiceRoutes: Map< @@ -582,6 +590,8 @@ export async function initializeCommandHandlerContext( // Runtime context commandLock: createLimiter(1), // Make sure we process one command at a time. currentRequestId: undefined, + currentAbortSignal: undefined, + activeRequests: new Map(), noReasoning: false, pendingToggleTransientAgents: [], agentCache: await getAgentCache( @@ -598,6 +608,7 @@ export async function initializeCommandHandlerContext( chatHistory: createChatHistory( session.getConfig().execution.history, ), + displayLog: await DisplayLog.load(persistDir), logger, metricsManager: metrics ? new RequestMetricsManager() : undefined, promptLogger: createPromptLogger(getCosmosFactories()), @@ -771,16 +782,19 @@ async function setupGrammarGeneration(context: CommandHandlerContext) { ); } - // Prefer explicit compiledSchemaFile field - if (actionConfig.compiledSchemaFilePath) { - return getPackageFilePath(actionConfig.compiledSchemaFilePath); - } + let schemaPath: string | undefined; - // Fallback: try to derive .pas.json path from .ts schemaFilePath + // Use schemaFilePath directly if it's already a .pas.json file if ( + actionConfig.schemaFilePath && + actionConfig.schemaFilePath.endsWith(".pas.json") + ) { + schemaPath = getPackageFilePath(actionConfig.schemaFilePath); + } else if ( actionConfig.schemaFilePath && actionConfig.schemaFilePath.endsWith(".ts") ) { + // Fallback: try to derive .pas.json path from .ts schemaFilePath // Try common pattern: ./src/schema.ts -> ../dist/schema.pas.json const derivedPath = actionConfig.schemaFilePath .replace(/^\.\/src\//, "../dist/") @@ -789,15 +803,22 @@ async function setupGrammarGeneration(context: CommandHandlerContext) { `Attempting fallback .pas.json path for ${schemaName}: ${derivedPath}`, ); try { - return getPackageFilePath(derivedPath); + schemaPath = getPackageFilePath(derivedPath); } catch { // Fallback path doesn't exist, continue to error } } - throw new Error( - `Compiled schema file path (.pas.json) not found for schema: ${schemaName}. ` + - `Please add 'compiledSchemaFile' field to the manifest pointing to the .pas.json file.`, + if (!schemaPath) { + throw new Error( + `Compiled schema file path (.pas.json) not found for schema: ${schemaName}. ` + + `Please ensure the schema is compiled to a .pas.json file.`, + ); + } + + const content = fs.readFileSync(schemaPath, "utf-8"); + return fromJSONParsedActionSchema( + JSON.parse(content) as ParsedActionSchemaJSON, ); }, ); diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts index 59bd5934a2..fee9c2b2b7 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts @@ -18,6 +18,7 @@ import chalk from "chalk"; import { ActionContext, CompletionGroup, + CompletionGroups, ParameterDefinitions, ParsedCommandParams, PartialParsedCommandParams, @@ -509,7 +510,7 @@ class AgentToggleCommandHandler implements CommandHandler { } } - return completions; + return { groups: completions }; } } @@ -564,7 +565,7 @@ class ExplainerCommandHandler implements CommandHandler { }); } } - return completions; + return { groups: completions }; } } @@ -633,7 +634,7 @@ class ConfigModelSetCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; for (const name of names) { if (name === "model") { @@ -644,7 +645,7 @@ class ConfigModelSetCommandHandler implements CommandHandler { } } - return completions; + return { groups: completions }; } } @@ -753,7 +754,7 @@ class FixedSchemaCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; const systemContext = context.agentContext; for (const name of names) { @@ -764,7 +765,7 @@ class FixedSchemaCommandHandler implements CommandHandler { }); } } - return completions; + return { groups: completions }; } } @@ -846,7 +847,7 @@ class GrammarSystemCommandHandler implements CommandHandler { }); } } - return completions; + return { groups: completions }; } } class GrammarUseDFACommandHandler implements CommandHandler { @@ -889,7 +890,7 @@ class GrammarUseDFACommandHandler implements CommandHandler { completions.push({ name, completions: ["true", "false"] }); } } - return completions; + return { groups: completions }; } } @@ -1226,7 +1227,7 @@ class ConfigRequestCommandHandler implements CommandHandler { context: SessionContext, params: PartialParsedCommandParams, names: string[], - ): Promise { + ): Promise { const completions: CompletionGroup[] = []; const systemContext = context.agentContext; for (const name of names) { @@ -1251,7 +1252,7 @@ class ConfigRequestCommandHandler implements CommandHandler { } } } - return completions; + return { groups: completions }; } } From 749957349d99d0a75ca14e82bc36406106050944 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:15:47 -0700 Subject: [PATCH 04/14] Fix agent-dispatcher TypeScript build errors: missing reloadAgentSchema and wrong enrichGrammarWithCheckedVariables argument (#2054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `agent-dispatcher` package failed to build on Windows due to two type errors introduced by API changes that weren't propagated to all call sites. ## Changes - **`enrichGrammarWithCheckedVariables` call site** (`appAgentManager.ts`): The function signature changed from accepting a `.pas.json` file path `string` to a `ParsedActionSchema` object. Updated the call to pass `actionSchemaFile.parsedActionSchema` (already in scope), eliminating a redundant file read and the now-unused `getPackageFilePath` import. ```ts // Before — passed file path string, wrong type const pasJsonPath = getPackageFilePath(compiledSchemaFilePath); enrichGrammarWithCheckedVariables(g, pasJsonPath); // After — uses already-loaded parsed schema enrichGrammarWithCheckedVariables(g, actionSchemaFile.parsedActionSchema); ``` - **`reloadAgentSchema` missing from `AppAgentManager`** (`sessionContext.ts`, `claude.ts`): Both call sites invoked `context.agents.reloadAgentSchema(name, context)` but the method didn't exist. Added the implementation, which: 1. Unloads cached schema files for all schemas belonging to the agent (forces re-read from disk) 2. Fetches a fresh manifest from the provider and calls `refreshAgentSchema` 3. Clears the translator cache so subsequent requests use the updated schema
Original prompt > Fix the failing GitHub Actions workflow build_package_shell (windows-latest, 22) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68259596486 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23460182903/job/68259596486
--- 📱 Kick off Copilot coding agent tasks wherever you are with [GitHub Mobile](https://gh.io/cca-mobile-docs), available on iOS and Android. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- .../dispatcher/src/context/appAgentManager.ts | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index 0369d1259e..8fd9ed41f1 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -20,7 +20,6 @@ import { import { getAppAgentName } from "../translation/agentTranslators.js"; import { createSessionContext } from "../execute/sessionContext.js"; import { AppAgentProvider } from "../agentProvider/agentProvider.js"; -import { getPackageFilePath } from "../utils/getPackageFilePath.js"; import registerDebug from "debug"; import { DispatcherName } from "./dispatcher/dispatcherUtils.js"; import { @@ -467,28 +466,19 @@ export class AppAgentManager implements ActionConfigProvider { // Add to NFA grammar registry if using NFA system if (useNFAGrammar && agentGrammarRegistry) { try { - // Enrich grammar with checked variables from .pas.json if available - const compiledSchemaFilePath = - config.schemaFilePath?.endsWith(".pas.json") - ? config.schemaFilePath - : undefined; - if (compiledSchemaFilePath) { - try { - const pasJsonPath = getPackageFilePath( - compiledSchemaFilePath, - ); - enrichGrammarWithCheckedVariables( - g, - pasJsonPath, - ); - debug( - `Enriched grammar with checked variables for schema: ${schemaName}`, - ); - } catch (enrichError) { - debug( - `Could not enrich grammar with checked variables for ${schemaName}: ${enrichError}`, - ); - } + // Enrich grammar with checked variables from parsed schema + try { + enrichGrammarWithCheckedVariables( + g, + actionSchemaFile.parsedActionSchema, + ); + debug( + `Enriched grammar with checked variables for schema: ${schemaName}`, + ); + } catch (enrichError) { + debug( + `Could not enrich grammar with checked variables for ${schemaName}: ${enrichError}`, + ); } const nfa = compileGrammarToNFA(g, schemaName); @@ -1093,6 +1083,32 @@ export class AppAgentManager implements ActionConfigProvider { return this.actionSchemaFileCache.getActionSchemaFile(config); } + public async reloadAgentSchema( + appAgentName: string, + context: CommandHandlerContext, + ): Promise { + const record = this.getRecord(appAgentName); + if (record.provider === undefined) { + return; + } + + // Unload cached schema files so they get reloaded from disk + for (const schemaName of this.actionConfigs.keys()) { + if (getAppAgentName(schemaName) === appAgentName) { + this.actionSchemaFileCache.unloadActionSchemaFile(schemaName); + } + } + + // Get fresh manifest from provider and refresh schemas + const manifest = await record.provider.getAppAgentManifest(appAgentName); + const semanticMapP: Promise[] = []; + this.refreshAgentSchema(appAgentName, manifest, semanticMapP, undefined); + await Promise.all(semanticMapP); + + // Clear translator cache to force re-translation with new schema + context.translatorCache.clear(); + } + public setTraceNamespaces(namespaces: string) { const providers = new Set(); for (const { provider } of this.agents.values()) { From 237a56f34bb6cb3535af71e8b8c563a4a31ac8e7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:29:05 -0700 Subject: [PATCH 05/14] Fix TS2339 build error: `tokens` does not exist on `ParsedCommandParams` (#2055) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `build_package_shell` CI job was failing because `getMcpCommandHandlerTable` in `mcpAgentProvider.ts` accessed `params.tokens`, which only exists on the internal `ParseParamsResult` type (dispatcher package), not on the public `ParsedCommandParams` from `@typeagent/agent-sdk`. ## Changes - **`mcpAgentProvider.ts`**: Replace `params.tokens` with an explicit collection of arg values from `params.args`, keyed by the `ArgDefinitions` passed to the outer function (preserving definition order): ```typescript const serverScriptArgs = Object.keys(args).map((k) => String((params.args as Record)[k]), ); ``` - Update the `run` callback parameter type from `ParsedCommandParams<{}>` to `ParsedCommandParams` to match the `CommandHandler` interface. - Add `ParameterDefinitions` to the import from `@typeagent/agent-sdk`.
Original prompt > Fix the failing GitHub Actions workflow build_package_shell (windows-latest, 22) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68268369843 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23462848733/job/68268369843
--- ⌨️ Start Copilot coding agent tasks without leaving your editor — available in [VS Code](https://gh.io/cca-vs-code-docs), [Visual Studio](https://gh.io/cca-visual-studio-docs), [JetBrains IDEs](https://gh.io/cca-jetbrains-docs) and [Eclipse](https://gh.io/cca-eclipse-docs). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- .../defaultAgentProvider/src/mcpAgentProvider.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts index 50b2ada902..5d7d0bc688 100644 --- a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts +++ b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts @@ -14,6 +14,7 @@ import { AppAgentProvider } from "agent-dispatcher"; import { ArgDefinitions, ParsedCommandParams, + ParameterDefinitions, ActionContext, } from "@typeagent/agent-sdk"; import { @@ -195,7 +196,7 @@ function getMcpCommandHandlerTable( }, run: async ( context: ActionContext, - params: ParsedCommandParams<{}>, + params: ParsedCommandParams, ) => { const instanceConfig: InstanceConfig = structuredClone( configs.getInstanceConfig(), @@ -203,12 +204,17 @@ function getMcpCommandHandlerTable( if (instanceConfig.mcpServers === undefined) { instanceConfig.mcpServers = {}; } + const serverScriptArgs = Object.keys(args).map((k) => + String( + (params.args as Record)[k], + ), + ); instanceConfig.mcpServers[appAgentName] = { - serverScriptArgs: params.tokens, + serverScriptArgs, }; configs.setInstanceConfig(instanceConfig); context.actionIO.appendDisplay( - `Server arguments set to ${params.tokens.join(" ")}. Please restart TypeAgent to reflect the change.`, + `Server arguments set to ${serverScriptArgs.join(" ")}. Please restart TypeAgent to reflect the change.`, ); }, }, From 6d6b7941f2b5d184a4f7f9bd1ffa7b9cda1858d4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 15:40:04 -0700 Subject: [PATCH 06/14] fix: prettier formatting in appAgentManager.ts (#2056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `build_ts` CI workflow was failing due to a prettier check violation in `agent-dispatcher`'s `appAgentManager.ts`. Two recently added lines exceeded the max line length. ## Changes - **`ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts`**: Reformatted two overly long expressions to comply with prettier line-length rules: - Wrapped `await record.provider.getAppAgentManifest(appAgentName)` assignment - Expanded `this.refreshAgentSchema(appAgentName, manifest, semanticMapP, undefined)` to multi-line call ```ts // Before const manifest = await record.provider.getAppAgentManifest(appAgentName); this.refreshAgentSchema(appAgentName, manifest, semanticMapP, undefined); // After const manifest = await record.provider.getAppAgentManifest(appAgentName); this.refreshAgentSchema( appAgentName, manifest, semanticMapP, undefined, ); ```
Original prompt > Fix the failing GitHub Actions workflow build_ts (ubuntu-latest, 22) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68269878964 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23463307901/job/68269878964
--- 📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- .../dispatcher/src/context/appAgentManager.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index 8fd9ed41f1..f5636b1599 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -1100,9 +1100,15 @@ export class AppAgentManager implements ActionConfigProvider { } // Get fresh manifest from provider and refresh schemas - const manifest = await record.provider.getAppAgentManifest(appAgentName); + const manifest = + await record.provider.getAppAgentManifest(appAgentName); const semanticMapP: Promise[] = []; - this.refreshAgentSchema(appAgentName, manifest, semanticMapP, undefined); + this.refreshAgentSchema( + appAgentName, + manifest, + semanticMapP, + undefined, + ); await Promise.all(semanticMapP); // Clear translator cache to force re-translation with new schema From a359e68b284acd6b21a64c80e8ca397aba2e77fb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 18:29:58 -0700 Subject: [PATCH 07/14] fix: format mcpAgentProvider.ts to pass prettier check (#2060) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI (`build_ts` / `macos-latest`) was failing because `ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts` did not pass the `prettier --check` step. ## Change Collapsed an over-wrapped `String(...)` call inside a `.map()` callback to fit on one line as prettier requires: ```ts // Before const serverScriptArgs = Object.keys(args).map((k) => String( (params.args as Record)[k], ), ); // After const serverScriptArgs = Object.keys(args).map((k) => String((params.args as Record)[k]), ); ```
Original prompt > Fix the failing GitHub Actions workflow build_ts (macos-latest, 22) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68271050235 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23463673502/job/68271050235
--- 📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts index 5d7d0bc688..bd792a2c8f 100644 --- a/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts +++ b/ts/packages/defaultAgentProvider/src/mcpAgentProvider.ts @@ -205,9 +205,7 @@ function getMcpCommandHandlerTable( instanceConfig.mcpServers = {}; } const serverScriptArgs = Object.keys(args).map((k) => - String( - (params.args as Record)[k], - ), + String((params.args as Record)[k]), ); instanceConfig.mcpServers[appAgentName] = { serverScriptArgs, From 4207df8b56a6e343c41451d8d6bd19a20d28c065 Mon Sep 17 00:00:00 2001 From: robgruen Date: Mon, 23 Mar 2026 20:49:40 -0700 Subject: [PATCH 08/14] Add dynamic grammar and schema loading methods --- .../dispatcher/src/context/appAgentManager.ts | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index f5636b1599..59db53ffd6 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -908,6 +908,140 @@ export class AppAgentManager implements ActionConfigProvider { await Promise.all(closeP); } +private async loadDynamicGrammar( + schemaName: string, + appAgent: AppAgent, + sessionContext: SessionContext, + context: CommandHandlerContext, + ): Promise { + let dynamicGrammar: Grammar | undefined; + + // Prefer the agent callback over file-based convention + if (appAgent.getDynamicGrammar) { + const grammarContent = await appAgent.getDynamicGrammar( + sessionContext, + schemaName, + ); + if (grammarContent?.content?.trim()) { + if (grammarContent.format === "ag") { + dynamicGrammar = grammarFromJson( + JSON.parse(grammarContent.content), + ); + } else { + // "agr" format — raw grammar rule text + const errors: string[] = []; + dynamicGrammar = + loadGrammarRulesNoThrow( + `${schemaName}-dynamic.agr`, + grammarContent.content, + errors, + ) ?? undefined; + if (errors.length > 0) { + debugError( + `Failed to parse dynamic grammar for ${schemaName}: ${errors.join(", ")}`, + ); + } + } + } + } + + // Fallback: read grammar/dynamic.agr from instance storage + if (dynamicGrammar === undefined) { + const instanceStorage = sessionContext.instanceStorage; + if (instanceStorage) { + try { + const agrText = await instanceStorage.read( + "grammar/dynamic.agr", + "utf8", + ); + if (agrText?.trim()) { + const errors: string[] = []; + dynamicGrammar = + loadGrammarRulesNoThrow( + `${schemaName}-dynamic.agr`, + agrText, + errors, + ) ?? undefined; + if (errors.length > 0) { + debugError( + `Failed to parse dynamic grammar for ${schemaName}: ${errors.join(", ")}`, + ); + } + } + } catch { + // No dynamic grammar file + } + } + } + + if (!dynamicGrammar || dynamicGrammar.rules.length === 0) return; + + const config = this.actionConfigs.get(schemaName); + const staticGrammar = config ? loadGrammar(config) : undefined; + + const merged: Grammar = { + rules: [...(staticGrammar?.rules ?? []), ...dynamicGrammar.rules], + }; + + context.agentCache.grammarStore.addGrammar(schemaName, merged); + debug( + `Loaded dynamic grammar for ${schemaName} (${dynamicGrammar.rules.length} dynamic rules merged with ${staticGrammar?.rules.length ?? 0} static rules)`, + ); + } + + private async loadDynamicSchema( + schemaName: string, + appAgent: AppAgent, + sessionContext: SessionContext, + context: CommandHandlerContext, + ): Promise { + if (!appAgent.getDynamicSchema) return; + + const schemaContent = await appAgent.getDynamicSchema( + sessionContext, + schemaName, + ); + if (!schemaContent?.content) return; + + const config = this.actionConfigs.get(schemaName); + if (!config) return; + + // Replace the schema content with the dynamic version + config.schemaFile = schemaContent; + + // Invalidate cached parsed schema so it gets re-parsed from new content + this.actionSchemaFileCache.unloadActionSchemaFile(schemaName); + + // Clear translator cache so next translation uses the updated schema + context.translatorCache.clear(); + + debug(`Loaded dynamic schema for ${schemaName}`); + } + + public async reloadAgentSchema( + schemaName: string, + context: CommandHandlerContext, + ): Promise { + const appAgentName = getAppAgentName(schemaName); + const record = this.getRecord(appAgentName); + if (!record.appAgent || !record.sessionContext) { + throw new Error(`Agent '${appAgentName}' is not initialized`); + } + + await this.loadDynamicSchema( + schemaName, + record.appAgent, + record.sessionContext, + context, + ); + await this.loadDynamicGrammar( + schemaName, + record.appAgent, + record.sessionContext, + context, + ); + } + private async updateAction( schemaName: string, record: AppAgentRecord, @@ -932,6 +1066,19 @@ export class AppAgentManager implements ActionConfigProvider { schemaName, ), ); + // Load dynamic schema and grammar from agent callbacks + await this.loadDynamicSchema( + schemaName, + record.appAgent!, + sessionContext, + context, + ); + await this.loadDynamicGrammar( + schemaName, + record.appAgent!, + sessionContext, + context, + ); } catch (e) { // Rollback if there is a exception record.actions.delete(schemaName); From b023acc6ed4f50a72f17bd57e90d989d7f8a34c7 Mon Sep 17 00:00:00 2001 From: robgruen Date: Mon, 23 Mar 2026 20:50:24 -0700 Subject: [PATCH 09/14] Add wrapClientIOWithDisplayLog function --- .../src/context/commandHandlerContext.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts index 5bd6e4034f..ef8b1546e4 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts @@ -105,6 +105,51 @@ import { const debug = registerDebug("typeagent:dispatcher:init"); const debugError = registerDebug("typeagent:dispatcher:init:error"); +function wrapClientIOWithDisplayLog( + clientIO: ClientIO, + displayLog: DisplayLog, +): ClientIO { + return { + ...clientIO, + setUserRequest(requestId, command) { + const seq = displayLog.logUserRequest(requestId, command); + clientIO.setUserRequest(requestId, command, seq); + }, + setDisplayInfo(requestId, source, actionIndex?, action?) { + const seq = displayLog.logSetDisplayInfo( + requestId, + source, + actionIndex, + action, + ); + clientIO.setDisplayInfo( + requestId, + source, + actionIndex, + action, + seq, + ); + }, + setDisplay(message) { + const seq = displayLog.logSetDisplay(message); + clientIO.setDisplay(message, seq); + }, + appendDisplay(message, mode) { + const seq = displayLog.logAppendDisplay(message, mode); + clientIO.appendDisplay(message, mode, seq); + }, + notify(notificationId, event, data, source) { + const seq = displayLog.logNotify( + notificationId, + event, + data, + source, + ); + clientIO.notify(notificationId, event, data, source, seq); + }, + }; +} + export type EmptyFunction = () => void; export type SetSettingFunction = (name: string, value: any) => void; From 07067610e5f947a5875f520050bc522d6949408d Mon Sep 17 00:00:00 2001 From: robgruen Date: Mon, 23 Mar 2026 20:51:11 -0700 Subject: [PATCH 10/14] Add ConfigExecutionScriptReuseCommandHandler class --- .../system/handlers/configCommandHandlers.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts index fee9c2b2b7..0bb474fb59 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts @@ -1465,6 +1465,35 @@ class ConfigExecutionPlanReuseCommandHandler implements CommandHandler { } } +class ConfigExecutionScriptReuseCommandHandler implements CommandHandler { + public readonly description = + "Enable or disable PowerShell script reuse for reasoning actions"; + public readonly parameters = { + args: { + mode: { + description: + "Script reuse mode: 'enabled' to capture and reuse PowerShell scripts, 'disabled' for standard reasoning", + type: "string" as const, + enum: ["enabled", "disabled"], + }, + }, + } as const; + + async run( + context: ActionContext, + params: ParsedCommandParams, + ) { + const mode = params.args.mode as "enabled" | "disabled"; + + await changeContextConfig( + { execution: { scriptReuse: mode } }, + context, + ); + + return displayResult(`Script reuse ${mode}`, context); + } +} + const configExecutionCommandHandlers: CommandHandlerTable = { description: "Execution configuration", commands: { From 32f8b7598cafbec811945531d41d6b9dd973988b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:27:54 -0700 Subject: [PATCH 11/14] Fix TypeScript compilation errors in agent-dispatcher breaking CI build (#2064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four TS errors in `agent-dispatcher` were causing `tsc -b` to fail, blocking the `build_ts` workflow on all platforms. ## Changes - **`appAgentManager.ts`** — Add missing `loadGrammarRulesNoThrow` to import from `action-grammar` (used in newly added `loadDynamicGrammar` for `.agr` format parsing) - **`appAgentManager.ts`** — Remove duplicate `reloadAgentSchema` method (TS2393); the new implementation at line 1021 conflicted with the pre-existing one at line 1233. The private helpers it introduced (`loadDynamicSchema`, `loadDynamicGrammar`) are retained and already wired into `updateAction` - **`configCommandHandlers.ts`** — Register `ConfigExecutionScriptReuseCommandHandler` in `configExecutionCommandHandlers` alongside the existing `planReuse` entry (class was defined but never instantiated) - **`commandHandlerContext.ts`** — Remove unused `wrapClientIOWithDisplayLog` helper function (TS6133; never called)
Original prompt > Fix the failing GitHub Actions workflow build_ts (windows-latest, 20) > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > Job ID: 68297257030 > Job URL: https://github.com/microsoft/TypeAgent/actions/runs/23472219964/job/68297257030
--- 📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- .../dispatcher/src/context/appAgentManager.ts | 25 +---------- .../src/context/commandHandlerContext.ts | 45 ------------------- .../system/handlers/configCommandHandlers.ts | 1 + 3 files changed, 2 insertions(+), 69 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index 59db53ffd6..5315f22160 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -40,6 +40,7 @@ import { AgentGrammarRegistry, compileGrammarToNFA, enrichGrammarWithCheckedVariables, + loadGrammarRulesNoThrow, } from "action-grammar"; import fs from "node:fs"; import { FlowDefinition } from "../execute/flowInterpreter.js"; @@ -1018,30 +1019,6 @@ private async loadDynamicGrammar( debug(`Loaded dynamic schema for ${schemaName}`); } - public async reloadAgentSchema( - schemaName: string, - context: CommandHandlerContext, - ): Promise { - const appAgentName = getAppAgentName(schemaName); - const record = this.getRecord(appAgentName); - if (!record.appAgent || !record.sessionContext) { - throw new Error(`Agent '${appAgentName}' is not initialized`); - } - - await this.loadDynamicSchema( - schemaName, - record.appAgent, - record.sessionContext, - context, - ); - await this.loadDynamicGrammar( - schemaName, - record.appAgent, - record.sessionContext, - context, - ); - } - private async updateAction( schemaName: string, record: AppAgentRecord, diff --git a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts index ef8b1546e4..5bd6e4034f 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/commandHandlerContext.ts @@ -105,51 +105,6 @@ import { const debug = registerDebug("typeagent:dispatcher:init"); const debugError = registerDebug("typeagent:dispatcher:init:error"); -function wrapClientIOWithDisplayLog( - clientIO: ClientIO, - displayLog: DisplayLog, -): ClientIO { - return { - ...clientIO, - setUserRequest(requestId, command) { - const seq = displayLog.logUserRequest(requestId, command); - clientIO.setUserRequest(requestId, command, seq); - }, - setDisplayInfo(requestId, source, actionIndex?, action?) { - const seq = displayLog.logSetDisplayInfo( - requestId, - source, - actionIndex, - action, - ); - clientIO.setDisplayInfo( - requestId, - source, - actionIndex, - action, - seq, - ); - }, - setDisplay(message) { - const seq = displayLog.logSetDisplay(message); - clientIO.setDisplay(message, seq); - }, - appendDisplay(message, mode) { - const seq = displayLog.logAppendDisplay(message, mode); - clientIO.appendDisplay(message, mode, seq); - }, - notify(notificationId, event, data, source) { - const seq = displayLog.logNotify( - notificationId, - event, - data, - source, - ); - clientIO.notify(notificationId, event, data, source, seq); - }, - }; -} - export type EmptyFunction = () => void; export type SetSettingFunction = (name: string, value: any) => void; diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts index 0bb474fb59..5344d4d54b 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/configCommandHandlers.ts @@ -1508,6 +1508,7 @@ const configExecutionCommandHandlers: CommandHandlerTable = { ), reasoning: new ConfigExecutionReasoningCommandHandler(), planReuse: new ConfigExecutionPlanReuseCommandHandler(), + scriptReuse: new ConfigExecutionScriptReuseCommandHandler(), }, }; From 876b51392bf12e3eb7eb62b6d0bb433c0c782e51 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:47:43 -0700 Subject: [PATCH 12/14] Fix shell:test CI failure: exclude Jest unit tests from Playwright discovery (#2066) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `shell_and_cli` smoke-test job (windows-latest, Node 22) was failing because Playwright's `testDir: "./test"` recursively picks up the newly added `test/partialCompletion/*.spec.ts` Jest unit tests. When Playwright (ESM) attempts to run them, the CJS-only named export `jest` from `@jest/globals` is unresolvable, producing: ``` SyntaxError: The requested module '@jest/globals' does not provide an export named 'jest' ``` This is not a PNPM version issue — the `partialCompletion/` tests are new, so prior PRs never triggered the collision. ## Changes - **`ts/packages/shell/playwright.config.ts`** — add `testIgnore: /\/partialCompletion\//` to prevent Playwright from discovering the Jest unit tests in that subdirectory. Those tests remain covered by `test:local` (`jest-esm`) in the `build-ts` workflow. Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: robgruen <25374553+robgruen@users.noreply.github.com> --- ts/packages/shell/playwright.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ts/packages/shell/playwright.config.ts b/ts/packages/shell/playwright.config.ts index 4102afa60a..aa6cf4910c 100644 --- a/ts/packages/shell/playwright.config.ts +++ b/ts/packages/shell/playwright.config.ts @@ -16,6 +16,8 @@ import { defineConfig, devices } from "@playwright/test"; */ export default defineConfig({ testDir: "./test", + /* Exclude Jest unit tests from Playwright test discovery */ + testIgnore: /\/partialCompletion\//, /* Run tests sequentially otherwise the client will complain about locked session file */ fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ From 068da45a5184978f2ffab1f47df7ce761f37fe17 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 23 Mar 2026 22:55:57 -0700 Subject: [PATCH 13/14] fixed failing list test --- ts/packages/shell/test/testHelper.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 3dac9985b9..81c6ae3a29 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -217,6 +217,12 @@ export async function sendUserRequest(prompt: string, page: Page) { await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus({ timeout: 30000 }); await locator.fill(prompt, { timeout: 30000 }); + + // robgruen - dismiss completion suggestion since it doesn't auto-dismiss on input and would cause the Enter key press to not submit the request but instead accept the suggestion + // TODO: fix completion to not need this workaround + await locator.press("ArrowLeft", { timeout: 30000 }); + + // send the request await locator.press("Enter", { timeout: 30000 }); } From 901fd12c8c6d2a04d432580f433fcaa5921a000d Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 23 Mar 2026 23:09:56 -0700 Subject: [PATCH 14/14] lint --- .../dispatcher/dispatcher/src/context/appAgentManager.ts | 4 ++-- ts/packages/shell/test/testHelper.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts index 5315f22160..8b48148432 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/appAgentManager.ts @@ -909,7 +909,7 @@ export class AppAgentManager implements ActionConfigProvider { await Promise.all(closeP); } -private async loadDynamicGrammar( + private async loadDynamicGrammar( schemaName: string, appAgent: AppAgent, sessionContext: SessionContext, @@ -1055,7 +1055,7 @@ private async loadDynamicGrammar( record.appAgent!, sessionContext, context, - ); + ); } catch (e) { // Rollback if there is a exception record.actions.delete(schemaName); diff --git a/ts/packages/shell/test/testHelper.ts b/ts/packages/shell/test/testHelper.ts index 81c6ae3a29..ca701d1412 100644 --- a/ts/packages/shell/test/testHelper.ts +++ b/ts/packages/shell/test/testHelper.ts @@ -217,7 +217,7 @@ export async function sendUserRequest(prompt: string, page: Page) { await locator.waitFor({ timeout: 30000, state: "visible" }); await locator.focus({ timeout: 30000 }); await locator.fill(prompt, { timeout: 30000 }); - + // robgruen - dismiss completion suggestion since it doesn't auto-dismiss on input and would cause the Enter key press to not submit the request but instead accept the suggestion // TODO: fix completion to not need this workaround await locator.press("ArrowLeft", { timeout: 30000 });