From 51ceacbc108d7e9486bf640557c8b4a5dd8a04a5 Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Fri, 20 Jun 2025 17:04:26 +0800 Subject: [PATCH] upgrade taskloop --- renderer/scripts/task-loop.build.mjs | 6 ++-- .../main-panel/chat/core/task-loop.ts | 29 +++++++++++++--- renderer/src/views/connect/core.ts | 8 ++--- renderer/vite.config.task-loop.mjs | 4 +-- resources/openmcp-sdk-release/package.json | 1 + service/src/hook/adapter.ts | 33 ++++++++++++++----- service/src/mcp/connect-monitor.service.ts | 4 +-- service/src/mcp/file-monitor.service.ts | 4 +-- tsconfig.json | 3 ++ 9 files changed, 66 insertions(+), 26 deletions(-) diff --git a/renderer/scripts/task-loop.build.mjs b/renderer/scripts/task-loop.build.mjs index a0608ff..96af48a 100644 --- a/renderer/scripts/task-loop.build.mjs +++ b/renderer/scripts/task-loop.build.mjs @@ -3,11 +3,11 @@ import * as fs from 'node:fs'; const targetFile = './openmcp-sdk/task-loop.js'; if (fs.existsSync(targetFile)) { - let content = fs.readFileSync(targetFile, 'utf8'); + let content = fs.readFileSync(targetFile, 'utf-8'); // Replace element-plus with ./tools.js - content = content.replace(/'element-plus'/g, "'./tools.js'"); - content = content.replace(/"element-plus"/g, "\"./tools.js\""); + content = content.replace(/'element-plus'/g, "'./tools.mjs'"); + content = content.replace(/"element-plus"/g, "\"./tools.mjs\""); // content = content.replace(/const chalk = require\("chalk"\);/g, 'const chalk = require("chalk").default;'); diff --git a/renderer/src/components/main-panel/chat/core/task-loop.ts b/renderer/src/components/main-panel/chat/core/task-loop.ts index bb7ddfc..253dfaf 100644 --- a/renderer/src/components/main-panel/chat/core/task-loop.ts +++ b/renderer/src/components/main-panel/chat/core/task-loop.ts @@ -34,6 +34,8 @@ export interface IErrorMssage { msg: string } +export { MessageState }; + export interface IDoConversationResult { stop: boolean; } @@ -83,12 +85,18 @@ export class TaskLoop { throw new Error('adapter is required'); } + // 根据 adapter 创建 nodejs 下特殊的、基于 event 的 message bridge (不占用任何端口) createMessageBridge(adapter.emitter); + + // 用于进行连接同步 this.nodejsStatus.connectionFut = mcpClientAdapter.launch(); } // web 环境下 bridge 会自动加载完成 this.bridge = useMessageBridge(); + + // 注册 HMR + mcpClientAdapter.addConnectRefreshListener(); } /** @@ -381,7 +389,7 @@ export class TaskLoop { if (verbose > 0) { console.log( chalk.gray(`[${new Date().toLocaleString()}]`), - chalk.blueBright('🔧 calling tool'), + chalk.blueBright('🔧 using tool'), chalk.blueBright(toolCall.function!.name) ); } @@ -395,13 +403,13 @@ export class TaskLoop { if (result.state === 'success') { console.log( chalk.gray(`[${new Date().toLocaleString()}]`), - chalk.green('✓ call tools okey dockey'), + chalk.green('✓ use tools'), chalk.green(result.state) ); } else { console.log( chalk.gray(`[${new Date().toLocaleString()}]`), - chalk.red('× fail to call tools'), + chalk.red('× use tools'), chalk.red(result.content.map(item => item.text).join(', ')) ); } @@ -413,7 +421,12 @@ export class TaskLoop { const { verbose = 0 } = this.taskOptions; if (verbose > 0) { console.log( - chalk.gray(`[${new Date().toLocaleString()}]`), + chalk.gray(`[${new Date().toLocaleString()}]`) + ); + } + + if (verbose > 1) { + console.log( chalk.blue('task loop enters a new epoch') ); } @@ -425,9 +438,15 @@ export class TaskLoop { if (verbose > 0) { console.log( chalk.gray(`[${new Date().toLocaleString()}]`), + ); + } + + if (verbose > 1) { + console.log( chalk.green('task loop finish a epoch') ); } + return this.onDone(); } @@ -559,7 +578,7 @@ export class TaskLoop { if (verbose > 0) { console.log( chalk.gray(`[${new Date().toLocaleString()}]`), - chalk.yellow('🤖 llm wants to call these tools'), + chalk.yellow('🤖 Agent wants to use these tools'), chalk.yellow(this.streamingToolCalls.value.map(tool => tool.function!.name || '').join(', ')) ); } diff --git a/renderer/src/views/connect/core.ts b/renderer/src/views/connect/core.ts index af41b1c..31d44ee 100644 --- a/renderer/src/views/connect/core.ts +++ b/renderer/src/views/connect/core.ts @@ -506,9 +506,7 @@ class McpClientAdapter { constructor( public platform: string - ) { - this.addConnectRefreshListener(); - } + ) {} /** * @description 获取连接参数签名 @@ -562,7 +560,9 @@ class McpClientAdapter { return index; } - + /** + * @description register HMR + */ public addConnectRefreshListener() { // 创建对于 connect/refresh 的监听 if (!this.connectrefreshListener) { diff --git a/renderer/vite.config.task-loop.mjs b/renderer/vite.config.task-loop.mjs index 3f1c264..b821c89 100644 --- a/renderer/vite.config.task-loop.mjs +++ b/renderer/vite.config.task-loop.mjs @@ -32,8 +32,8 @@ export default defineConfig({ lib: { entry: resolve(__dirname, '..', 'renderer/src/components/main-panel/chat/core/task-loop.ts'), name: 'TaskLoop', - fileName: 'task-loop', - formats: ['cjs'] + fileName: (format) => `task-loop.js`, + formats: ['es'] }, outDir: resolve(__dirname, '..', 'openmcp-sdk'), emptyOutDir: false, diff --git a/resources/openmcp-sdk-release/package.json b/resources/openmcp-sdk-release/package.json index a482bc1..b343e5d 100644 --- a/resources/openmcp-sdk-release/package.json +++ b/resources/openmcp-sdk-release/package.json @@ -1,6 +1,7 @@ { "name": "openmcp-sdk", "version": "0.0.8", + "type": "module", "description": "openmcp-sdk", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/service/src/hook/adapter.ts b/service/src/hook/adapter.ts index bd1788c..7c0e957 100644 --- a/service/src/hook/adapter.ts +++ b/service/src/hook/adapter.ts @@ -195,7 +195,14 @@ export interface DefaultLLM { model: string; } -import { MessageState, TaskLoopOptions, type ChatMessage, type ChatSetting, type TaskLoop, type TextMessage } from '../../task-loop.js'; +import { + MessageState, + type TaskLoopOptions, + type ChatMessage, + type ChatSetting, + type TaskLoop, + type TextMessage +} from '../../task-loop.js'; export function UserMessage(content: string): TextMessage { return { @@ -274,7 +281,7 @@ export class OmAgent { const commandString = ( mcpConfig.command + ' ' + mcpConfig.args.join(' ') ).trim(); - + this._adapter.addMcp({ commandString, connectionType: 'STDIO', @@ -283,7 +290,7 @@ export class OmAgent { prompt: mcpConfig.prompt, }); } else { - const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP': 'SSE'; + const connectionType: ConnectionType = mcpConfig.type === 'http' ? 'STREAMABLE_HTTP' : 'SSE'; this._adapter.addMcp({ url: mcpConfig.url, env: mcpConfig.env, @@ -309,6 +316,7 @@ export class OmAgent { const adapter = this._adapter; const { TaskLoop } = await import('../../task-loop.js'); this._loop = new TaskLoop({ adapter, verbose, maxEpochs, maxJsonParseRetry }); + return this._loop; } @@ -316,9 +324,14 @@ export class OmAgent { this._defaultLLM = option; } - public async start( - messages: ChatMessage[] | string, - settings?: ChatSetting & Partial + /** + * @description Asynchronous invoking agent by string or messages + * @param messages Chat message or string + * @param settings Chat setting and task loop options + * @returns + */ + public async ainvoke( + { messages, settings }: { messages: ChatMessage[] | string; settings?: ChatSetting & Partial; } ) { if (messages.length === 0) { throw new Error('messages is empty'); @@ -333,7 +346,7 @@ export class OmAgent { const loop = await this.getLoop({ maxEpochs, maxJsonParseRetry, verbose }); const storage = await loop.createStorage(settings); - + // set input message // user can invoke [UserMessage("CONTENT")] to make messages quickly // or use string directly @@ -363,6 +376,10 @@ export class OmAgent { throw new Error('default LLM is not set, please set it via omagent.setDefaultLLM() or write "defaultLLM" in mcpconfig.json'); } - return await loop.start(storage, userMessage); + await loop.start(storage, userMessage); + + // get response from last message in message list + const lastMessage = storage.messages.at(-1)?.content; + return lastMessage; } } diff --git a/service/src/mcp/connect-monitor.service.ts b/service/src/mcp/connect-monitor.service.ts index 2a3f57e..e228d6a 100644 --- a/service/src/mcp/connect-monitor.service.ts +++ b/service/src/mcp/connect-monitor.service.ts @@ -38,7 +38,7 @@ export class McpServerConnectMonitor { this.filePath = getFilePath(options); // 记录实例创建 - logger.info({ uuid, connectionType: options.connectionType }, 'Created new connection monitor instance'); + // logger.info({ uuid, connectionType: options.connectionType }, 'Created new connection monitor instance'); switch (options.connectionType) { case 'STDIO': @@ -94,7 +94,7 @@ export class McpServerConnectMonitor { }, onStart: () => { // 使用 info 级别记录监控开始 - logger.info({ uuid: this.uuid, filePath: path.resolve(fileConfig.filePath) }, 'Started monitoring file'); + // logger.info({ uuid: this.uuid, filePath: path.resolve(fileConfig.filePath) }, 'Started monitoring file'); try { const stats = fs.statSync(fileConfig.filePath); diff --git a/service/src/mcp/file-monitor.service.ts b/service/src/mcp/file-monitor.service.ts index cc045fe..d5413fb 100644 --- a/service/src/mcp/file-monitor.service.ts +++ b/service/src/mcp/file-monitor.service.ts @@ -71,7 +71,7 @@ class SingleFileMonitor { this.handleFileChange(true); } }); - console.log(`正在监控文件: ${this.filePath}`); + // console.log(`正在监控文件: ${this.filePath}`); } catch (error) { this.onError(error as Error); } @@ -171,7 +171,7 @@ class SingleFileMonitor { if (this.watcher) { // 明确指定close方法的类型,解决TS2554错误 (this.watcher.close as (callback?: () => void) => void)(() => { - console.log(`已停止监控文件: ${this.filePath}`); + // console.log(`已停止监控文件: ${this.filePath}`); }); this.watcher = null; } diff --git a/tsconfig.json b/tsconfig.json index f48397a..8855718 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,9 @@ "noEmitHelpers": false, "experimentalDecorators": true, "sourceMap": true, + "types": [ + "./service/task-loop.d.ts" + ], // 允许访问 openmcp-sdk 目录 "paths": { "@openmcp-sdk/*": [