@openeryc/pi-coding-agent 0.75.51 → 0.75.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.75.52] - 2026-05-30
4
+
5
+ ### Added
6
+ - MCP tools now accept an optional `_timeoutMs` parameter that the AI can use to override timeout per-call (e.g. `_timeoutMs: 300000` for 5 minutes). Stripped before sending to the MCP server. Documented in system prompt.
7
+
3
8
  ## [0.75.51] - 2026-05-30
4
9
 
5
10
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAuCzE,MAAM,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE/F,qBAAa,UAAU;IACtB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,eAAe,CAAoD;IAC3E,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,cAAc,CAA0C;IAEhE,yDAAyD;IACzD,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAQ;IAErD,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAEvD;IAED,cAAc,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAM9F;IAED,OAAO,CAAC,SAAS;IAWX,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAYzE;YAEa,aAAa;IAsC3B,OAAO,CAAC,aAAa;IA2DrB,OAAO,CAAC,iBAAiB;IA6CzB,0DAA0D;IACpD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjD;IAGD,kBAAkB,IAAI,cAAc,EAAE,CAErC;IAED,cAAc,IAAI,MAAM,EAAE,CAEzB;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAEK,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/C;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB1B;CACD","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPConnectionStatus, MCPToolDefinition } from \"./types.ts\";\n\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;\nconst DEFAULT_RECONNECT_INTERVAL_MS = 5000;\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate serverConfigs = new Map<string, MCPServerConfig>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\tprivate reconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\tprivate reconnectAttempts = new Map<string, number>();\n\tprivate _statuses = new Map<string, MCPConnectionStatus>();\n\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\n\n\t/** Called when any server's connection status changes */\n\tonStatusChange: MCPStatusChangeHandler | null = null;\n\n\tgetServerStatus(serverName: string): MCPConnectionStatus {\n\t\treturn this._statuses.get(serverName) ?? \"disconnected\";\n\t}\n\n\tgetAllStatuses(): Array<{ serverName: string; status: MCPConnectionStatus; toolCount: number }> {\n\t\treturn [...this._statuses.entries()].map(([serverName, status]) => ({\n\t\t\tserverName,\n\t\t\tstatus,\n\t\t\ttoolCount: this.tools.get(serverName)?.length ?? 0,\n\t\t}));\n\t}\n\n\tprivate setStatus(serverName: string, status: MCPConnectionStatus): void {\n\t\tconst prev = this._statuses.get(serverName);\n\t\tif (prev === status) return;\n\t\tthis._statuses.set(serverName, status);\n\t\ttry {\n\t\t\tthis.onStatusChange?.(serverName, status);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) {\n\t\t\t\tthis.setStatus(serverName, \"disabled\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.serverConfigs.set(serverName, config);\n\t\t\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters: toTypeBox(tool.inputSchema),\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\t\ttext: `MCP server \"${serverName}\" is disconnected. Use /mcp to check status.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// If a per-tool timeout is configured, race it with the caller's signal\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\ttoolSpecificTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(toolSpecificTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, params as Record<string, unknown>, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t\tthis.tools.set(serverName, defs);\n\t}\n\n\tprivate scheduleReconnect(serverName: string): void {\n\t\t// Cancel any existing reconnect timer for this server\n\t\tconst existing = this.reconnectTimers.get(serverName);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) return;\n\n\t\tconst reconnectCfg = config.reconnect ?? {};\n\t\tif (reconnectCfg.enabled === false) return;\n\n\t\tconst maxAttempts = reconnectCfg.maxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS;\n\t\tconst intervalMs = reconnectCfg.intervalMs ?? DEFAULT_RECONNECT_INTERVAL_MS;\n\t\tconst attempt = (this.reconnectAttempts.get(serverName) ?? 0) + 1;\n\n\t\tif (attempt > maxAttempts) {\n\t\t\tconsole.warn(`MCP server \"${serverName}\": max reconnection attempts (${maxAttempts}) reached, giving up.`);\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.set(serverName, attempt);\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\n\t\tconst timer = setTimeout(async () => {\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t\tconst cfg = this.serverConfigs.get(serverName);\n\t\t\tif (!cfg) return;\n\n\t\t\t// Remove old client if any\n\t\t\tconst oldClient = this.clients.get(serverName);\n\t\t\tif (oldClient) {\n\t\t\t\toldClient.onDisconnect = null;\n\t\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tawait this.connectServer(serverName, cfg);\n\t\t}, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x\n\n\t\tthis.reconnectTimers.set(serverName, timer);\n\t}\n\n\t/** Manually trigger reconnection for a specific server */\n\tasync reconnect(serverName: string): Promise<void> {\n\t\tconst existingTimer = this.reconnectTimers.get(serverName);\n\t\tif (existingTimer) {\n\t\t\tclearTimeout(existingTimer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\n\t\tconst oldClient = this.clients.get(serverName);\n\t\tif (oldClient) {\n\t\t\toldClient.onDisconnect = null;\n\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\tthis.clients.delete(serverName);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) {\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t// Rebuild tool definitions for a server (used after reconnect to pick up any changes)\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// Cancel all reconnect timers\n\t\tfor (const [, timer] of this.reconnectTimers) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.reconnectTimers.clear();\n\t\tthis.reconnectAttempts.clear();\n\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tthis._statuses.clear();\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAqB,MAAM,YAAY,CAAC;AAuCzE,MAAM,MAAM,sBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE/F,qBAAa,UAAU;IACtB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,eAAe,CAAoD;IAC3E,OAAO,CAAC,iBAAiB,CAA6B;IACtD,OAAO,CAAC,SAAS,CAA0C;IAC3D,OAAO,CAAC,cAAc,CAA0C;IAEhE,yDAAyD;IACzD,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAQ;IAErD,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAEvD;IAED,cAAc,IAAI,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAM9F;IAED,OAAO,CAAC,SAAS;IAWX,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAYzE;YAEa,aAAa;IAsC3B,OAAO,CAAC,aAAa;IAkFrB,OAAO,CAAC,iBAAiB;IA6CzB,0DAA0D;IACpD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjD;IAGD,kBAAkB,IAAI,cAAc,EAAE,CAErC;IAED,cAAc,IAAI,MAAM,EAAE,CAEzB;IAED,eAAe,IAAI,MAAM,EAAE,CAE1B;IAEK,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ/C;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAsB1B;CACD","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPConnectionStatus, MCPToolDefinition } from \"./types.ts\";\n\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;\nconst DEFAULT_RECONNECT_INTERVAL_MS = 5000;\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate serverConfigs = new Map<string, MCPServerConfig>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\tprivate reconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\tprivate reconnectAttempts = new Map<string, number>();\n\tprivate _statuses = new Map<string, MCPConnectionStatus>();\n\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\n\n\t/** Called when any server's connection status changes */\n\tonStatusChange: MCPStatusChangeHandler | null = null;\n\n\tgetServerStatus(serverName: string): MCPConnectionStatus {\n\t\treturn this._statuses.get(serverName) ?? \"disconnected\";\n\t}\n\n\tgetAllStatuses(): Array<{ serverName: string; status: MCPConnectionStatus; toolCount: number }> {\n\t\treturn [...this._statuses.entries()].map(([serverName, status]) => ({\n\t\t\tserverName,\n\t\t\tstatus,\n\t\t\ttoolCount: this.tools.get(serverName)?.length ?? 0,\n\t\t}));\n\t}\n\n\tprivate setStatus(serverName: string, status: MCPConnectionStatus): void {\n\t\tconst prev = this._statuses.get(serverName);\n\t\tif (prev === status) return;\n\t\tthis._statuses.set(serverName, status);\n\t\ttry {\n\t\t\tthis.onStatusChange?.(serverName, status);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) {\n\t\t\t\tthis.setStatus(serverName, \"disabled\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.serverConfigs.set(serverName, config);\n\t\t\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\t// Merge _timeoutMs into the existing schema (preserving required fields)\n\t\t\tconst existingProps = (baseParams as any).properties ?? {};\n\t\t\tconst existingRequired = (baseParams as any).required ?? [];\n\t\t\tconst parameters = Type.Object(\n\t\t\t\t{\n\t\t\t\t\t...existingProps,\n\t\t\t\t\t_timeoutMs: Type.Optional(\n\t\t\t\t\t\tType.Number({\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\t{ required: existingRequired },\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\t\ttext: `MCP server \"${serverName}\" is disconnected. Use /mcp to check status.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t\tthis.tools.set(serverName, defs);\n\t}\n\n\tprivate scheduleReconnect(serverName: string): void {\n\t\t// Cancel any existing reconnect timer for this server\n\t\tconst existing = this.reconnectTimers.get(serverName);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) return;\n\n\t\tconst reconnectCfg = config.reconnect ?? {};\n\t\tif (reconnectCfg.enabled === false) return;\n\n\t\tconst maxAttempts = reconnectCfg.maxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS;\n\t\tconst intervalMs = reconnectCfg.intervalMs ?? DEFAULT_RECONNECT_INTERVAL_MS;\n\t\tconst attempt = (this.reconnectAttempts.get(serverName) ?? 0) + 1;\n\n\t\tif (attempt > maxAttempts) {\n\t\t\tconsole.warn(`MCP server \"${serverName}\": max reconnection attempts (${maxAttempts}) reached, giving up.`);\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.set(serverName, attempt);\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\n\t\tconst timer = setTimeout(async () => {\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t\tconst cfg = this.serverConfigs.get(serverName);\n\t\t\tif (!cfg) return;\n\n\t\t\t// Remove old client if any\n\t\t\tconst oldClient = this.clients.get(serverName);\n\t\t\tif (oldClient) {\n\t\t\t\toldClient.onDisconnect = null;\n\t\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tawait this.connectServer(serverName, cfg);\n\t\t}, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x\n\n\t\tthis.reconnectTimers.set(serverName, timer);\n\t}\n\n\t/** Manually trigger reconnection for a specific server */\n\tasync reconnect(serverName: string): Promise<void> {\n\t\tconst existingTimer = this.reconnectTimers.get(serverName);\n\t\tif (existingTimer) {\n\t\t\tclearTimeout(existingTimer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\n\t\tconst oldClient = this.clients.get(serverName);\n\t\tif (oldClient) {\n\t\t\toldClient.onDisconnect = null;\n\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\tthis.clients.delete(serverName);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) {\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t// Rebuild tool definitions for a server (used after reconnect to pick up any changes)\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// Cancel all reconnect timers\n\t\tfor (const [, timer] of this.reconnectTimers) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.reconnectTimers.clear();\n\t\tthis.reconnectAttempts.clear();\n\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tthis._statuses.clear();\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}
@@ -120,6 +120,17 @@ export class MCPManager {
120
120
  const defs = mcpTools.map((tool) => {
121
121
  // Resolve effective timeout: per-tool > server default > client default (120s)
122
122
  const toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);
123
+ // Add optional _timeoutMs parameter so the AI can override timeout per-call
124
+ const baseParams = toTypeBox(tool.inputSchema);
125
+ // Merge _timeoutMs into the existing schema (preserving required fields)
126
+ const existingProps = baseParams.properties ?? {};
127
+ const existingRequired = baseParams.required ?? [];
128
+ const parameters = Type.Object({
129
+ ...existingProps,
130
+ _timeoutMs: Type.Optional(Type.Number({
131
+ description: "Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.",
132
+ })),
133
+ }, { required: existingRequired });
123
134
  return {
124
135
  name: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,
125
136
  label: `mcp.${serverName}.${tool.name}`,
@@ -129,7 +140,7 @@ export class MCPManager {
129
140
  promptSnippet: tool.description
130
141
  ? `[${serverName}] ${tool.description.split("\n")[0]}`
131
142
  : `[${serverName}] ${tool.name}`,
132
- parameters: toTypeBox(tool.inputSchema),
143
+ parameters,
133
144
  renderShell: "default",
134
145
  execute: async (_id, params, signal) => {
135
146
  if (!client.isConnected) {
@@ -144,11 +155,14 @@ export class MCPManager {
144
155
  };
145
156
  }
146
157
  try {
147
- // If a per-tool timeout is configured, race it with the caller's signal
148
- const effectiveSignal = toolSpecificTimeoutMs !== undefined
149
- ? AbortSignal.any([AbortSignal.timeout(toolSpecificTimeoutMs), ...(signal ? [signal] : [])])
158
+ // Extract _timeoutMs from params (AI-specified timeout override)
159
+ const rawParams = params;
160
+ const callTimeoutMs = typeof rawParams._timeoutMs === "number" ? rawParams._timeoutMs : toolSpecificTimeoutMs;
161
+ const { _timeoutMs: _ignored, ...mcpParams } = rawParams;
162
+ const effectiveSignal = callTimeoutMs !== undefined
163
+ ? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])
150
164
  : signal;
151
- const result = await client.callTool(tool.name, params, effectiveSignal);
165
+ const result = await client.callTool(tool.name, mcpParams, effectiveSignal);
152
166
  const text = result.content
153
167
  .map((item) => (item.type === "text" && item.text ? item.text : `[Image: ${item.mimeType}]`))
154
168
  .join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPConnectionStatus, MCPToolDefinition } from \"./types.ts\";\n\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;\nconst DEFAULT_RECONNECT_INTERVAL_MS = 5000;\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate serverConfigs = new Map<string, MCPServerConfig>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\tprivate reconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\tprivate reconnectAttempts = new Map<string, number>();\n\tprivate _statuses = new Map<string, MCPConnectionStatus>();\n\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\n\n\t/** Called when any server's connection status changes */\n\tonStatusChange: MCPStatusChangeHandler | null = null;\n\n\tgetServerStatus(serverName: string): MCPConnectionStatus {\n\t\treturn this._statuses.get(serverName) ?? \"disconnected\";\n\t}\n\n\tgetAllStatuses(): Array<{ serverName: string; status: MCPConnectionStatus; toolCount: number }> {\n\t\treturn [...this._statuses.entries()].map(([serverName, status]) => ({\n\t\t\tserverName,\n\t\t\tstatus,\n\t\t\ttoolCount: this.tools.get(serverName)?.length ?? 0,\n\t\t}));\n\t}\n\n\tprivate setStatus(serverName: string, status: MCPConnectionStatus): void {\n\t\tconst prev = this._statuses.get(serverName);\n\t\tif (prev === status) return;\n\t\tthis._statuses.set(serverName, status);\n\t\ttry {\n\t\t\tthis.onStatusChange?.(serverName, status);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) {\n\t\t\t\tthis.setStatus(serverName, \"disabled\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.serverConfigs.set(serverName, config);\n\t\t\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters: toTypeBox(tool.inputSchema),\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\t\ttext: `MCP server \"${serverName}\" is disconnected. Use /mcp to check status.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// If a per-tool timeout is configured, race it with the caller's signal\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\ttoolSpecificTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(toolSpecificTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, params as Record<string, unknown>, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t\tthis.tools.set(serverName, defs);\n\t}\n\n\tprivate scheduleReconnect(serverName: string): void {\n\t\t// Cancel any existing reconnect timer for this server\n\t\tconst existing = this.reconnectTimers.get(serverName);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) return;\n\n\t\tconst reconnectCfg = config.reconnect ?? {};\n\t\tif (reconnectCfg.enabled === false) return;\n\n\t\tconst maxAttempts = reconnectCfg.maxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS;\n\t\tconst intervalMs = reconnectCfg.intervalMs ?? DEFAULT_RECONNECT_INTERVAL_MS;\n\t\tconst attempt = (this.reconnectAttempts.get(serverName) ?? 0) + 1;\n\n\t\tif (attempt > maxAttempts) {\n\t\t\tconsole.warn(`MCP server \"${serverName}\": max reconnection attempts (${maxAttempts}) reached, giving up.`);\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.set(serverName, attempt);\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\n\t\tconst timer = setTimeout(async () => {\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t\tconst cfg = this.serverConfigs.get(serverName);\n\t\t\tif (!cfg) return;\n\n\t\t\t// Remove old client if any\n\t\t\tconst oldClient = this.clients.get(serverName);\n\t\t\tif (oldClient) {\n\t\t\t\toldClient.onDisconnect = null;\n\t\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tawait this.connectServer(serverName, cfg);\n\t\t}, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x\n\n\t\tthis.reconnectTimers.set(serverName, timer);\n\t}\n\n\t/** Manually trigger reconnection for a specific server */\n\tasync reconnect(serverName: string): Promise<void> {\n\t\tconst existingTimer = this.reconnectTimers.get(serverName);\n\t\tif (existingTimer) {\n\t\t\tclearTimeout(existingTimer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\n\t\tconst oldClient = this.clients.get(serverName);\n\t\tif (oldClient) {\n\t\t\toldClient.onDisconnect = null;\n\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\tthis.clients.delete(serverName);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) {\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t// Rebuild tool definitions for a server (used after reconnect to pick up any changes)\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// Cancel all reconnect timers\n\t\tfor (const [, timer] of this.reconnectTimers) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.reconnectTimers.clear();\n\t\tthis.reconnectAttempts.clear();\n\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tthis._statuses.clear();\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}
1
+ {"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/core/mcp/manager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentToolResult } from \"@openeryc/pi-agent-core\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { MCPClient, type MCPServerConfig } from \"./client.ts\";\nimport type { MCPConnectionStatus, MCPToolDefinition } from \"./types.ts\";\n\nconst DEFAULT_RECONNECT_MAX_ATTEMPTS = 5;\nconst DEFAULT_RECONNECT_INTERVAL_MS = 5000;\n\nfunction toTypeBox(schema: MCPToolDefinition[\"inputSchema\"]): ReturnType<typeof Type.Object> {\n\tif (!schema.properties || Object.keys(schema.properties).length === 0) {\n\t\treturn Type.Object({});\n\t}\n\tconst required = new Set(schema.required ?? []);\n\tconst fields: Record<string, unknown> = {};\n\tfor (const [key, prop] of Object.entries(schema.properties)) {\n\t\tconst p = prop as { type?: string; description?: string; enum?: string[] };\n\t\tconst t = p.type ?? \"string\";\n\t\tconst desc = p.description ? { description: p.description } : {};\n\t\tlet f: unknown;\n\t\tswitch (t) {\n\t\t\tcase \"string\":\n\t\t\t\tf = p.enum ? Type.Unsafe<string>({ type: \"string\", enum: p.enum, ...desc }) : Type.String(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"number\":\n\t\t\tcase \"integer\":\n\t\t\t\tf = Type.Number(desc);\n\t\t\t\tbreak;\n\t\t\tcase \"boolean\":\n\t\t\t\tf = Type.Boolean(desc);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tf = Type.Any(desc);\n\t\t}\n\t\tfields[key] = required.has(key) ? f : Type.Optional(f as never);\n\t}\n\treturn Type.Object(fields as Record<string, ReturnType<typeof Type.String>>);\n}\n\nfunction sanitizeMcpName(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\nexport type MCPStatusChangeHandler = (serverName: string, status: MCPConnectionStatus) => void;\n\nexport class MCPManager {\n\tprivate clients = new Map<string, MCPClient>();\n\tprivate serverConfigs = new Map<string, MCPServerConfig>();\n\tprivate tools = new Map<string, ToolDefinition[]>();\n\tprivate resourceUris: string[] = [];\n\tprivate reconnectTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\tprivate reconnectAttempts = new Map<string, number>();\n\tprivate _statuses = new Map<string, MCPConnectionStatus>();\n\tprivate toolTimeoutMap = new Map<string, Map<string, number>>();\n\n\t/** Called when any server's connection status changes */\n\tonStatusChange: MCPStatusChangeHandler | null = null;\n\n\tgetServerStatus(serverName: string): MCPConnectionStatus {\n\t\treturn this._statuses.get(serverName) ?? \"disconnected\";\n\t}\n\n\tgetAllStatuses(): Array<{ serverName: string; status: MCPConnectionStatus; toolCount: number }> {\n\t\treturn [...this._statuses.entries()].map(([serverName, status]) => ({\n\t\t\tserverName,\n\t\t\tstatus,\n\t\t\ttoolCount: this.tools.get(serverName)?.length ?? 0,\n\t\t}));\n\t}\n\n\tprivate setStatus(serverName: string, status: MCPConnectionStatus): void {\n\t\tconst prev = this._statuses.get(serverName);\n\t\tif (prev === status) return;\n\t\tthis._statuses.set(serverName, status);\n\t\ttry {\n\t\t\tthis.onStatusChange?.(serverName, status);\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t}\n\n\tasync start(serverConfigs: Record<string, MCPServerConfig>): Promise<void> {\n\t\tfor (const [serverName, config] of Object.entries(serverConfigs)) {\n\t\t\tif (config.enabled === false) {\n\t\t\t\tthis.setStatus(serverName, \"disabled\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthis.serverConfigs.set(serverName, config);\n\t\t\tif (config.toolTimeouts) {\n\t\t\t\tthis.toolTimeoutMap.set(serverName, new Map(Object.entries(config.toolTimeouts)));\n\t\t\t}\n\t\t\tawait this.connectServer(serverName, config);\n\t\t}\n\t}\n\n\tprivate async connectServer(serverName: string, config: MCPServerConfig): Promise<void> {\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\t\ttry {\n\t\t\tconst client = new MCPClient(config.timeoutMs);\n\t\t\tclient.onDisconnect = (_reason) => {\n\t\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\t\tthis.tools.delete(serverName);\n\t\t\t\tthis.scheduleReconnect(serverName);\n\t\t\t};\n\n\t\t\tawait client.connect(config);\n\t\t\tconst { tools: mcpTools } = await client.listTools();\n\t\t\tthis.clients.set(serverName, client);\n\n\t\t\tthis.buildToolDefs(serverName, mcpTools);\n\n\t\t\t// Discover resources\n\t\t\ttry {\n\t\t\t\tconst { resources } = await client.listResources();\n\t\t\t\tfor (const r of resources) {\n\t\t\t\t\tthis.resourceUris.push(`mcp://${serverName}${r.uri}`);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Server may not support resources\n\t\t\t}\n\n\t\t\tthis.setStatus(serverName, \"connected\");\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t} catch (error) {\n\t\t\tthis.clients.delete(serverName);\n\t\t\tconsole.warn(\n\t\t\t\t`Failed to connect to MCP server \"${serverName}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\tthis.scheduleReconnect(serverName);\n\t\t}\n\t}\n\n\tprivate buildToolDefs(serverName: string, mcpTools: MCPToolDefinition[]): void {\n\t\tconst safeServerName = sanitizeMcpName(serverName);\n\t\tconst client = this.clients.get(serverName)!;\n\t\tconst perToolTimeout = this.toolTimeoutMap.get(serverName);\n\n\t\tconst defs: ToolDefinition[] = mcpTools.map((tool) => {\n\t\t\t// Resolve effective timeout: per-tool > server default > client default (120s)\n\t\t\tconst toolSpecificTimeoutMs = perToolTimeout?.get(tool.name);\n\n\t\t\t// Add optional _timeoutMs parameter so the AI can override timeout per-call\n\t\t\tconst baseParams = toTypeBox(tool.inputSchema);\n\t\t\t// Merge _timeoutMs into the existing schema (preserving required fields)\n\t\t\tconst existingProps = (baseParams as any).properties ?? {};\n\t\t\tconst existingRequired = (baseParams as any).required ?? [];\n\t\t\tconst parameters = Type.Object(\n\t\t\t\t{\n\t\t\t\t\t...existingProps,\n\t\t\t\t\t_timeoutMs: Type.Optional(\n\t\t\t\t\t\tType.Number({\n\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\"Optional timeout override in milliseconds for this specific call (e.g. 300000 for 5 min, 5000 for 5s). Overrides the server default timeout.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t\t{ required: existingRequired },\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tname: `mcp_${safeServerName}_${sanitizeMcpName(tool.name)}`,\n\t\t\t\tlabel: `mcp.${serverName}.${tool.name}`,\n\t\t\t\tdescription: tool.description\n\t\t\t\t\t? `${tool.description} (from MCP server \"${serverName}\")`\n\t\t\t\t\t: `Tool from MCP server \"${serverName}\"`,\n\t\t\t\tpromptSnippet: tool.description\n\t\t\t\t\t? `[${serverName}] ${tool.description.split(\"\\n\")[0]}`\n\t\t\t\t\t: `[${serverName}] ${tool.name}`,\n\t\t\t\tparameters,\n\t\t\t\trenderShell: \"default\" as const,\n\t\t\t\texecute: async (_id, params, signal) => {\n\t\t\t\t\tif (!client.isConnected) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\t\ttext: `MCP server \"${serverName}\" is disconnected. Use /mcp to check status.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: { error: \"disconnected\" },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// Extract _timeoutMs from params (AI-specified timeout override)\n\t\t\t\t\t\tconst rawParams = params as Record<string, unknown>;\n\t\t\t\t\t\tconst callTimeoutMs =\n\t\t\t\t\t\t\ttypeof rawParams._timeoutMs === \"number\" ? rawParams._timeoutMs : toolSpecificTimeoutMs;\n\t\t\t\t\t\tconst { _timeoutMs: _ignored, ...mcpParams } = rawParams;\n\n\t\t\t\t\t\tconst effectiveSignal =\n\t\t\t\t\t\t\tcallTimeoutMs !== undefined\n\t\t\t\t\t\t\t\t? AbortSignal.any([AbortSignal.timeout(callTimeoutMs), ...(signal ? [signal] : [])])\n\t\t\t\t\t\t\t\t: signal;\n\t\t\t\t\t\tconst result = await client.callTool(tool.name, mcpParams, effectiveSignal);\n\t\t\t\t\t\tconst text = result.content\n\t\t\t\t\t\t\t.map((item) => (item.type === \"text\" && item.text ? item.text : `[Image: ${item.mimeType}]`))\n\t\t\t\t\t\t\t.join(\"\\n\");\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text }],\n\t\t\t\t\t\t\tdetails: result,\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconst msg = error instanceof Error ? error.message : String(error);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `MCP tool error: ${msg}` }],\n\t\t\t\t\t\t\tdetails: { error: msg },\n\t\t\t\t\t\t} satisfies AgentToolResult<unknown>;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t});\n\t\tthis.tools.set(serverName, defs);\n\t}\n\n\tprivate scheduleReconnect(serverName: string): void {\n\t\t// Cancel any existing reconnect timer for this server\n\t\tconst existing = this.reconnectTimers.get(serverName);\n\t\tif (existing) {\n\t\t\tclearTimeout(existing);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) return;\n\n\t\tconst reconnectCfg = config.reconnect ?? {};\n\t\tif (reconnectCfg.enabled === false) return;\n\n\t\tconst maxAttempts = reconnectCfg.maxAttempts ?? DEFAULT_RECONNECT_MAX_ATTEMPTS;\n\t\tconst intervalMs = reconnectCfg.intervalMs ?? DEFAULT_RECONNECT_INTERVAL_MS;\n\t\tconst attempt = (this.reconnectAttempts.get(serverName) ?? 0) + 1;\n\n\t\tif (attempt > maxAttempts) {\n\t\t\tconsole.warn(`MCP server \"${serverName}\": max reconnection attempts (${maxAttempts}) reached, giving up.`);\n\t\t\tthis.reconnectAttempts.delete(serverName);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.set(serverName, attempt);\n\t\tthis.setStatus(serverName, \"reconnecting\");\n\n\t\tconst timer = setTimeout(async () => {\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t\tconst cfg = this.serverConfigs.get(serverName);\n\t\t\tif (!cfg) return;\n\n\t\t\t// Remove old client if any\n\t\t\tconst oldClient = this.clients.get(serverName);\n\t\t\tif (oldClient) {\n\t\t\t\toldClient.onDisconnect = null;\n\t\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\t\tthis.clients.delete(serverName);\n\t\t\t}\n\n\t\t\tawait this.connectServer(serverName, cfg);\n\t\t}, intervalMs * Math.min(attempt, 5)); // exponential backoff, cap at 5x\n\n\t\tthis.reconnectTimers.set(serverName, timer);\n\t}\n\n\t/** Manually trigger reconnection for a specific server */\n\tasync reconnect(serverName: string): Promise<void> {\n\t\tconst existingTimer = this.reconnectTimers.get(serverName);\n\t\tif (existingTimer) {\n\t\t\tclearTimeout(existingTimer);\n\t\t\tthis.reconnectTimers.delete(serverName);\n\t\t}\n\n\t\tconst oldClient = this.clients.get(serverName);\n\t\tif (oldClient) {\n\t\t\toldClient.onDisconnect = null;\n\t\t\tawait oldClient.disconnect().catch(() => {});\n\t\t\tthis.clients.delete(serverName);\n\t\t}\n\n\t\tconst config = this.serverConfigs.get(serverName);\n\t\tif (!config) {\n\t\t\tthis.setStatus(serverName, \"disconnected\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reconnectAttempts.delete(serverName);\n\t\tawait this.connectServer(serverName, config);\n\t}\n\n\t// Rebuild tool definitions for a server (used after reconnect to pick up any changes)\n\tgetToolDefinitions(): ToolDefinition[] {\n\t\treturn Array.from(this.tools.values()).flat();\n\t}\n\n\tgetServerNames(): string[] {\n\t\treturn [...this.clients.keys()];\n\t}\n\n\tgetResourceUris(): string[] {\n\t\treturn this.resourceUris;\n\t}\n\n\tasync readResource(uri: string): Promise<string> {\n\t\tconst serverName = this.clients.keys().next().value;\n\t\tif (!serverName) throw new Error(\"No MCP servers connected\");\n\t\tconst mcpUri = uri.startsWith(`mcp://${serverName}`) ? uri.slice(`mcp://${serverName}`.length) : uri;\n\t\tconst client = this.clients.get(serverName);\n\t\tif (!client) throw new Error(`MCP server \"${serverName}\" not connected`);\n\t\tconst result = await client.readResource(mcpUri);\n\t\treturn result.contents.map((c) => c.text ?? \"\").join(\"\\n\");\n\t}\n\n\tasync stop(): Promise<void> {\n\t\t// Cancel all reconnect timers\n\t\tfor (const [, timer] of this.reconnectTimers) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t\tthis.reconnectTimers.clear();\n\t\tthis.reconnectAttempts.clear();\n\n\t\tconst pending = [...this.clients.entries()].map(async ([name, client]) => {\n\t\t\ttry {\n\t\t\t\tawait client.disconnect();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`Error disconnecting MCP server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\tthis.clients.clear();\n\t\tthis.tools.clear();\n\t\tthis.resourceUris = [];\n\t\tthis._statuses.clear();\n\t\tawait Promise.all(pending);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9F,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAqL3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** MCP tools grouped by server name. */\n\tmcpTools?: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>;\n\t/** Project memory content (MEMORY.md). */\n\tmemory?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmcpTools: providedMcpTools,\n\t\tmemory,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append project memory (AI-learned across sessions)\n\t\tif (memory) {\n\t\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Append MCP tools section — explicitly note these are callable function tools\n\t\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\t\tprompt +=\n\t\t\t\t\"\\n\\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above.\";\n\t\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Determine if any visible tool is an MCP tool\n\tconst hasMcpTools = visibleTools.some((name) => name.startsWith(\"mcp_\"));\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\taddGuideline(\n\t\t\"Save important project learnings (build commands, debugging insights, architecture notes, code style preferences) to .pi/memory/MEMORY.md so they persist across sessions. Read MEMORY.md first when starting new work.\",\n\t);\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tconst additionalToolsNote = hasMcpTools\n\t\t? `\nMCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Detailed descriptions are listed below.`\n\t\t: \"\\nIn addition to the tools above, you may have access to other custom tools depending on the project.\";\n\n\tlet prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}${additionalToolsNote}\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append project memory (AI-learned across sessions)\n\tif (memory) {\n\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Append MCP tools section\n\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\tprompt += \"\\n\\nMCP tool details:\";\n\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n\nfunction xmlEscape(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n\nfunction formatMcpToolsForPrompt(\n\tservers: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>,\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const server of servers) {\n\t\tlines.push(`\\n<server name=\"${xmlEscape(server.serverName)}\">`);\n\t\tfor (const tool of server.tools) {\n\t\t\tlines.push(\" <tool>\");\n\t\t\tlines.push(` <name>${xmlEscape(tool.name)}</name>`);\n\t\t\tlines.push(` <description>${xmlEscape(tool.description)}</description>`);\n\t\t\tlines.push(\" </tool>\");\n\t\t}\n\t\tlines.push(\"</server>\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,aAAa,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACxC,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,uCAAuC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IAC9F,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAsL3E","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** MCP tools grouped by server name. */\n\tmcpTools?: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>;\n\t/** Project memory content (MEMORY.md). */\n\tmemory?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmcpTools: providedMcpTools,\n\t\tmemory,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append project memory (AI-learned across sessions)\n\t\tif (memory) {\n\t\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Append MCP tools section — explicitly note these are callable function tools\n\t\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\t\tprompt +=\n\t\t\t\t\"\\n\\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above. Each accepts an optional `_timeoutMs` parameter to override the timeout for that specific call.\";\n\t\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Determine if any visible tool is an MCP tool\n\tconst hasMcpTools = visibleTools.some((name) => name.startsWith(\"mcp_\"));\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\taddGuideline(\n\t\t\"Save important project learnings (build commands, debugging insights, architecture notes, code style preferences) to .pi/memory/MEMORY.md so they persist across sessions. Read MEMORY.md first when starting new work.\",\n\t);\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tconst additionalToolsNote = hasMcpTools\n\t\t? `\nMCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Each accepts an optional _timeoutMs parameter (in milliseconds) to override the timeout for that specific call (e.g. _timeoutMs: 300000 for 5 minutes).`\n\t\t: \"\\nIn addition to the tools above, you may have access to other custom tools depending on the project.\";\n\n\tlet prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}${additionalToolsNote}\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append project memory (AI-learned across sessions)\n\tif (memory) {\n\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Append MCP tools section\n\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\tprompt +=\n\t\t\t\"\\n\\nMCP tool details (each tool accepts an optional `_timeoutMs` param to override timeout per-call, e.g. `_timeoutMs: 300000` for 5 min):\";\n\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n\nfunction xmlEscape(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n\nfunction formatMcpToolsForPrompt(\n\tservers: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>,\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const server of servers) {\n\t\tlines.push(`\\n<server name=\"${xmlEscape(server.serverName)}\">`);\n\t\tfor (const tool of server.tools) {\n\t\t\tlines.push(\" <tool>\");\n\t\t\tlines.push(` <name>${xmlEscape(tool.name)}</name>`);\n\t\t\tlines.push(` <description>${xmlEscape(tool.description)}</description>`);\n\t\t\tlines.push(\" </tool>\");\n\t\t}\n\t\tlines.push(\"</server>\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -42,7 +42,7 @@ export function buildSystemPrompt(options) {
42
42
  // Append MCP tools section — explicitly note these are callable function tools
43
43
  if (providedMcpTools && providedMcpTools.length > 0) {
44
44
  prompt +=
45
- "\n\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above.";
45
+ "\n\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above. Each accepts an optional `_timeoutMs` parameter to override the timeout for that specific call.";
46
46
  prompt += formatMcpToolsForPrompt(providedMcpTools);
47
47
  }
48
48
  // Add date and working directory last
@@ -96,7 +96,7 @@ export function buildSystemPrompt(options) {
96
96
  const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
97
97
  const additionalToolsNote = hasMcpTools
98
98
  ? `
99
- MCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Detailed descriptions are listed below.`
99
+ MCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Each accepts an optional _timeoutMs parameter (in milliseconds) to override the timeout for that specific call (e.g. _timeoutMs: 300000 for 5 minutes).`
100
100
  : "\nIn addition to the tools above, you may have access to other custom tools depending on the project.";
101
101
  let prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
102
102
 
@@ -136,7 +136,8 @@ Pi documentation (read only when the user asks about pi itself, its SDK, extensi
136
136
  }
137
137
  // Append MCP tools section
138
138
  if (providedMcpTools && providedMcpTools.length > 0) {
139
- prompt += "\n\nMCP tool details:";
139
+ prompt +=
140
+ "\n\nMCP tool details (each tool accepts an optional `_timeoutMs` param to override timeout per-call, e.g. `_timeoutMs: 300000` for 5 min):";
140
141
  prompt += formatMcpToolsForPrompt(providedMcpTools);
141
142
  }
142
143
  // Add date and working directory last
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** MCP tools grouped by server name. */\n\tmcpTools?: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>;\n\t/** Project memory content (MEMORY.md). */\n\tmemory?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmcpTools: providedMcpTools,\n\t\tmemory,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append project memory (AI-learned across sessions)\n\t\tif (memory) {\n\t\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Append MCP tools section — explicitly note these are callable function tools\n\t\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\t\tprompt +=\n\t\t\t\t\"\\n\\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above.\";\n\t\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Determine if any visible tool is an MCP tool\n\tconst hasMcpTools = visibleTools.some((name) => name.startsWith(\"mcp_\"));\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\taddGuideline(\n\t\t\"Save important project learnings (build commands, debugging insights, architecture notes, code style preferences) to .pi/memory/MEMORY.md so they persist across sessions. Read MEMORY.md first when starting new work.\",\n\t);\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tconst additionalToolsNote = hasMcpTools\n\t\t? `\nMCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Detailed descriptions are listed below.`\n\t\t: \"\\nIn addition to the tools above, you may have access to other custom tools depending on the project.\";\n\n\tlet prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}${additionalToolsNote}\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append project memory (AI-learned across sessions)\n\tif (memory) {\n\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Append MCP tools section\n\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\tprompt += \"\\n\\nMCP tool details:\";\n\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n\nfunction xmlEscape(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n\nfunction formatMcpToolsForPrompt(\n\tservers: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>,\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const server of servers) {\n\t\tlines.push(`\\n<server name=\"${xmlEscape(server.serverName)}\">`);\n\t\tfor (const tool of server.tools) {\n\t\t\tlines.push(\" <tool>\");\n\t\t\tlines.push(` <name>${xmlEscape(tool.name)}</name>`);\n\t\t\tlines.push(` <description>${xmlEscape(tool.description)}</description>`);\n\t\t\tlines.push(\" </tool>\");\n\t\t}\n\t\tlines.push(\"</server>\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nexport interface BuildSystemPromptOptions {\n\t/** Custom system prompt (replaces default). */\n\tcustomPrompt?: string;\n\t/** Tools to include in prompt. Default: [read, bash, edit, write] */\n\tselectedTools?: string[];\n\t/** Optional one-line tool snippets keyed by tool name. */\n\ttoolSnippets?: Record<string, string>;\n\t/** Additional guideline bullets appended to the default system prompt guidelines. */\n\tpromptGuidelines?: string[];\n\t/** Text to append to system prompt. */\n\tappendSystemPrompt?: string;\n\t/** Working directory. */\n\tcwd: string;\n\t/** Pre-loaded context files. */\n\tcontextFiles?: Array<{ path: string; content: string }>;\n\t/** Pre-loaded skills. */\n\tskills?: Skill[];\n\t/** MCP tools grouped by server name. */\n\tmcpTools?: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>;\n\t/** Project memory content (MEMORY.md). */\n\tmemory?: string;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n\tconst {\n\t\tcustomPrompt,\n\t\tselectedTools,\n\t\ttoolSnippets,\n\t\tpromptGuidelines,\n\t\tappendSystemPrompt,\n\t\tcwd,\n\t\tcontextFiles: providedContextFiles,\n\t\tskills: providedSkills,\n\t\tmcpTools: providedMcpTools,\n\t\tmemory,\n\t} = options;\n\tconst resolvedCwd = cwd;\n\tconst promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n\tconst now = new Date();\n\tconst year = now.getFullYear();\n\tconst month = String(now.getMonth() + 1).padStart(2, \"0\");\n\tconst day = String(now.getDate()).padStart(2, \"0\");\n\tconst date = `${year}-${month}-${day}`;\n\n\tconst appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n\n\tconst contextFiles = providedContextFiles ?? [];\n\tconst skills = providedSkills ?? [];\n\n\tif (customPrompt) {\n\t\tlet prompt = customPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t\t}\n\t\t\tprompt += \"</project_context>\\n\";\n\t\t}\n\n\t\t// Append project memory (AI-learned across sessions)\n\t\tif (memory) {\n\t\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t\t}\n\n\t\t// Append skills section (only if read tool is available)\n\t\tconst customPromptHasRead = !selectedTools || selectedTools.includes(\"read\");\n\t\tif (customPromptHasRead && skills.length > 0) {\n\t\t\tprompt += formatSkillsForPrompt(skills);\n\t\t}\n\n\t\t// Append MCP tools section — explicitly note these are callable function tools\n\t\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\t\tprompt +=\n\t\t\t\t\"\\n\\nThe MCP (Model Context Protocol) tools below are regular callable tools — use them just like any other tool listed above. Each accepts an optional `_timeoutMs` parameter to override the timeout for that specific call.\";\n\t\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t\t}\n\n\t\t// Add date and working directory last\n\t\tprompt += `\\nCurrent date: ${date}`;\n\t\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute paths to documentation and examples\n\tconst readmePath = getReadmePath();\n\tconst docsPath = getDocsPath();\n\tconst examplesPath = getExamplesPath();\n\n\t// Build tools list based on selected tools.\n\t// A tool appears in Available tools only when the caller provides a one-line snippet.\n\tconst tools = selectedTools || [\"read\", \"bash\", \"edit\", \"write\"];\n\tconst visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n\tconst toolsList =\n\t\tvisibleTools.length > 0 ? visibleTools.map((name) => `- ${name}: ${toolSnippets![name]}`).join(\"\\n\") : \"(none)\";\n\n\t// Determine if any visible tool is an MCP tool\n\tconst hasMcpTools = visibleTools.some((name) => name.startsWith(\"mcp_\"));\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\tconst guidelinesSet = new Set<string>();\n\tconst addGuideline = (guideline: string): void => {\n\t\tif (guidelinesSet.has(guideline)) {\n\t\t\treturn;\n\t\t}\n\t\tguidelinesSet.add(guideline);\n\t\tguidelinesList.push(guideline);\n\t};\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\taddGuideline(\"Use bash for file operations like ls, rg, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\taddGuideline(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\tfor (const guideline of promptGuidelines ?? []) {\n\t\tconst normalized = guideline.trim();\n\t\tif (normalized.length > 0) {\n\t\t\taddGuideline(normalized);\n\t\t}\n\t}\n\n\t// Always include these\n\taddGuideline(\"Be concise in your responses\");\n\taddGuideline(\"Show file paths clearly when working with files\");\n\taddGuideline(\n\t\t\"Save important project learnings (build commands, debugging insights, architecture notes, code style preferences) to .pi/memory/MEMORY.md so they persist across sessions. Read MEMORY.md first when starting new work.\",\n\t);\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tconst additionalToolsNote = hasMcpTools\n\t\t? `\nMCP (Model Context Protocol) tools are regular callable tools with the mcp_ prefix — use them just like any other tool. Each accepts an optional _timeoutMs parameter (in milliseconds) to override the timeout for that specific call (e.g. _timeoutMs: 300000 for 5 minutes).`\n\t\t: \"\\nIn addition to the tools above, you may have access to other custom tools depending on the project.\";\n\n\tlet prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}${additionalToolsNote}\n\nGuidelines:\n${guidelines}\n\nPi documentation (read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- When reading pi docs or examples, resolve docs/... under Additional docs and examples/... under Examples, not the current working directory\n- When asked about: extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), pi packages (docs/packages.md)\n- When working on pi topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read pi .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n<project_context>\\n\\n\";\n\t\tprompt += \"Project-specific instructions and guidelines:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `<project_instructions path=\"${filePath}\">\\n${content}\\n</project_instructions>\\n\\n`;\n\t\t}\n\t\tprompt += \"</project_context>\\n\";\n\t}\n\n\t// Append project memory (AI-learned across sessions)\n\tif (memory) {\n\t\tprompt += `\\n<project_memory>\\n${memory}\\n</project_memory>\\n`;\n\t}\n\n\t// Append skills section (only if read tool is available)\n\tif (hasRead && skills.length > 0) {\n\t\tprompt += formatSkillsForPrompt(skills);\n\t}\n\n\t// Append MCP tools section\n\tif (providedMcpTools && providedMcpTools.length > 0) {\n\t\tprompt +=\n\t\t\t\"\\n\\nMCP tool details (each tool accepts an optional `_timeoutMs` param to override timeout per-call, e.g. `_timeoutMs: 300000` for 5 min):\";\n\t\tprompt += formatMcpToolsForPrompt(providedMcpTools);\n\t}\n\n\t// Add date and working directory last\n\tprompt += `\\nCurrent date: ${date}`;\n\tprompt += `\\nCurrent working directory: ${promptCwd}`;\n\n\treturn prompt;\n}\n\nfunction xmlEscape(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&apos;\");\n}\n\nfunction formatMcpToolsForPrompt(\n\tservers: Array<{ serverName: string; tools: Array<{ name: string; description: string }> }>,\n): string {\n\tconst lines: string[] = [];\n\n\tfor (const server of servers) {\n\t\tlines.push(`\\n<server name=\"${xmlEscape(server.serverName)}\">`);\n\t\tfor (const tool of server.tools) {\n\t\t\tlines.push(\" <tool>\");\n\t\t\tlines.push(` <name>${xmlEscape(tool.name)}</name>`);\n\t\t\tlines.push(` <description>${xmlEscape(tool.description)}</description>`);\n\t\t\tlines.push(\" </tool>\");\n\t\t}\n\t\tlines.push(\"</server>\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"]}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.75.51",
3
+ "version": "0.75.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.75.51",
9
+ "version": "0.75.52",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.75.51",
4
+ "version": "0.75.52",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.75.51",
4
+ "version": "0.75.52",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.5.51",
3
+ "version": "1.5.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.5.51",
9
+ "version": "1.5.52",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.5.51",
4
+ "version": "1.5.52",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.75.51",
3
+ "version": "0.75.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.75.51",
9
+ "version": "0.75.52",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.75.51",
4
+ "version": "0.75.52",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@openeryc/pi-coding-agent",
3
- "version": "0.75.51",
3
+ "version": "0.75.52",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openeryc/pi-coding-agent",
9
- "version": "0.75.51",
9
+ "version": "0.75.52",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@openeryc/pi-agent-core": "^0.75.51",
13
- "@openeryc/pi-ai": "^0.75.51",
14
- "@openeryc/pi-tui": "^0.75.51",
12
+ "@openeryc/pi-agent-core": "^0.75.52",
13
+ "@openeryc/pi-ai": "^0.75.52",
14
+ "@openeryc/pi-tui": "^0.75.52",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
16
  "chalk": "5.6.2",
17
17
  "cross-spawn": "7.0.6",
@@ -699,11 +699,11 @@
699
699
  ]
700
700
  },
701
701
  "node_modules/@openeryc/pi-agent-core": {
702
- "version": "0.75.51",
703
- "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.51.tgz",
702
+ "version": "0.75.52",
703
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.52.tgz",
704
704
  "license": "MIT",
705
705
  "dependencies": {
706
- "@openeryc/pi-ai": "^0.75.51",
706
+ "@openeryc/pi-ai": "^0.75.52",
707
707
  "ignore": "7.0.5",
708
708
  "typebox": "1.1.38",
709
709
  "yaml": "2.9.0"
@@ -713,8 +713,8 @@
713
713
  }
714
714
  },
715
715
  "node_modules/@openeryc/pi-ai": {
716
- "version": "0.75.51",
717
- "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.51.tgz",
716
+ "version": "0.75.52",
717
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.52.tgz",
718
718
  "license": "MIT",
719
719
  "dependencies": {
720
720
  "@anthropic-ai/sdk": "0.91.1",
@@ -735,8 +735,8 @@
735
735
  }
736
736
  },
737
737
  "node_modules/@openeryc/pi-tui": {
738
- "version": "0.75.51",
739
- "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.51.tgz",
738
+ "version": "0.75.52",
739
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.52.tgz",
740
740
  "license": "MIT",
741
741
  "dependencies": {
742
742
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openeryc/pi-coding-agent",
3
- "version": "0.75.51",
3
+ "version": "0.75.52",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
40
40
  },
41
41
  "dependencies": {
42
- "@openeryc/pi-agent-core": "^0.75.51",
43
- "@openeryc/pi-ai": "^0.75.51",
44
- "@openeryc/pi-tui": "^0.75.51",
42
+ "@openeryc/pi-agent-core": "^0.75.52",
43
+ "@openeryc/pi-ai": "^0.75.52",
44
+ "@openeryc/pi-tui": "^0.75.52",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",