aws-runtime-bridge 1.9.107 → 1.9.109
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/dist/adapter/AcodeSdkAdapter.d.ts +4 -0
- package/dist/adapter/AcodeSdkAdapter.js +2 -2
- package/dist/adapter/types.d.ts +2 -0
- package/dist/routes/sessions.js +1 -1
- package/dist/routes/terminal.js +25 -25
- package/package/acode/dist/runtime.d.ts +6 -0
- package/package/acode/dist/runtime.js +21 -21
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +17 -16
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +85 -19
- package/package/aws-client-agent-mcp/dist/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import E from"node:fs";import j from"node:os";import f from"node:path";import{Server as U}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as x}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as q,ListToolsRequestSchema as W}from"@modelcontextprotocol/sdk/types.js";import{AgentClient as H}from"./agent-client.js";import{MCP_SERVER_NAME as z,MCP_SERVER_VERSION as G,REGISTER_MAX_RETRIES as P,REGISTER_RETRY_INTERVAL_MS as Y,DEGRADED_MODE_ENABLED as L,DEGRADED_MODE_RETRY_INTERVAL_MS as J,DEGRADED_MODE_MAX_RETRIES as K}from"./constants.js";import{logger as
|
|
1
|
+
import E from"node:fs";import j from"node:os";import f from"node:path";import{Server as U}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as x}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as q,ListToolsRequestSchema as W}from"@modelcontextprotocol/sdk/types.js";import{AgentClient as H}from"./agent-client.js";import{MCP_SERVER_NAME as z,MCP_SERVER_VERSION as G,REGISTER_MAX_RETRIES as P,REGISTER_RETRY_INTERVAL_MS as Y,DEGRADED_MODE_ENABLED as L,DEGRADED_MODE_RETRY_INTERVAL_MS as J,DEGRADED_MODE_MAX_RETRIES as K}from"./constants.js";import{logger as o}from"./logger.js";import{MCP_TOOLS as O,MEMORY_TOOL_NAMES as F}from"./mcp-tools.js";import{MessageBuffer as V}from"./message-buffer.js";import{ContextManager as X}from"./context-manager.js";import{MemoryCapacityError as Z,MemoryStore as N}from"./memory-store.js";import{ServerMemoryStore as Q}from"./server-memory-store.js";const ee=3e3,te=10,re=new Set(["<",">",":",'"',"/","\\","|","?","*"]);class v{server;agentClient;messageBuffer;lastTodoFingerprint=null;memoryStore;roleMemoryStore;sessionContextMemoryStore;initialRoleName="";hasRoleName=!1;contextManager;isSubAgent;leaseRenewalTimer=null;activeLeaseUris=new Set;static LEASE_DURATION_MINUTES=10;static LEASE_RENEW_INTERVAL_MS=6e4;profileSynced=!1;pendingPollMessage=null;syncRoleMemoryStore(e){if(o.info(`[McpServer] syncRoleMemoryStore \u88AB\u8C03\u7528: displayName=${e.displayName}, roleName="${e.roleName??"(\u7A7A)"}", id=${e.id}`),!e.roleName){o.warn(`[McpServer] syncRoleMemoryStore: profile.roleName \u4E3A\u7A7A\uFF0ChasRoleName \u4FDD\u6301 false\u3002profile=${JSON.stringify({id:e.id,displayName:e.displayName,roleName:e.roleName,instanceName:e.instanceName})}`);return}const t=v.sanitizeRoleDirectoryName(e.roleName);this.hasRoleName=!0,t!==this.initialRoleName&&(o.info(`[McpServer] \u89D2\u8272\u76EE\u5F55\u4E0E\u521D\u59CB\u503C\u4E0D\u4E00\u81F4\uFF0C\u91CD\u5EFA roleMemoryStore: "${this.initialRoleName}" -> "${t}"`),this.roleMemoryStore=new N(v.buildRoleMemoryStorePath(t)),this.initialRoleName=t)}maybeSyncPermanentMemoryToServer(){this.doMaybeSyncPermanentMemoryToServer().catch(e=>{o.warn("[McpServer] maybeSyncPermanentMemoryToServer \u5F02\u5E38: %s, stack=%s",e instanceof Error?e.message:String(e),e instanceof Error?e.stack:"(no stack)")})}async doMaybeSyncPermanentMemoryToServer(){o.info("[McpServer] maybeSyncPermanentMemoryToServer \u5F00\u59CB\uFF0CinitialRoleName="+this.initialRoleName);let e;try{e=await this.memoryStore.getStats()}catch(s){o.warn("[McpServer] \u83B7\u53D6 server \u8BB0\u5FC6\u7EDF\u8BA1\u5931\u8D25\uFF0C\u8DF3\u8FC7\u540C\u6B65:",s instanceof Error?s.message:s);return}const t=e.by_type.project_memory?.count??0;if(o.info(`[McpServer] server memory \u8BA1\u6570=${t}\uFF0C\u662F\u5426\u9700\u8981\u540C\u6B65=${t===0}`),t>0)return;let r=[];try{r=await this.roleMemoryStore.listByType("project_memory"),o.info(`[McpServer] \u89D2\u8272\u5B50\u76EE\u5F55\u8BFB\u53D6\u5230 ${r.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`)}catch(s){o.warn("[McpServer] \u8BFB\u53D6\u89D2\u8272\u5B50\u76EE\u5F55\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25:",s instanceof Error?s.message:s)}if(r.length===0){const s=f.dirname(v.buildRoleMemoryStorePath(this.initialRoleName));o.info(`[McpServer] \u5C1D\u8BD5\u4ECE\u9057\u7559\u76EE\u5F55\u8BFB\u53D6: ${s}`);try{const l=await new N(s).listByType("project_memory");l.length>0&&(o.info(`[McpServer] \u4ECE\u9057\u7559\u76EE\u5F55\u8BFB\u53D6\u5230 ${l.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`),r=l)}catch(a){o.warn("[McpServer] \u8BFB\u53D6\u9057\u7559\u76EE\u5F55\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25:",a instanceof Error?a.message:a)}}if(r.length===0){o.info("[McpServer] \u672C\u5730\u4E5F\u65E0\u6C38\u4E45\u8BB0\u5FC6\uFF0C\u8DF3\u8FC7\u540C\u6B65");return}let n=0,i=0;for(const s of r)try{await this.memoryStore.createMemory({memory_type:"project_memory",uri:s.uri,title:s.title,content:s.content,keywords:s.keywords,bound_files:s.bound_files,file_md5_map:s.file_md5_map,last_check_time:s.last_check_time}),n++,o.info(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u6210\u529F: ${s.uri}`)}catch(a){const l=a instanceof Error?a.message:String(a);l.includes("already exists")||l.includes("Memory already exists")||o.warn(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25 (${s.uri}): ${l}`),i++}o.info(`[McpServer] \u6C38\u4E45\u8BB0\u5FC6\u540C\u6B65\u5B8C\u6210: \u6210\u529F ${n}, \u8DF3\u8FC7 ${i}, \u5171 ${r.length}`),await this.reportMemoryStatsToServer()}normalizeBoundFilePath(e){const t=String(e||"").trim().replace(/\\/g,"/");if(!t)return t;const r=t.startsWith("@/")?t.substring(1):t;return r.startsWith("./")?r:`./${r.replace(/^\/+/,"")}`}normalizeBoundFiles(e){return!e||!Array.isArray(e)?e:e.map(t=>this.normalizeBoundFilePath(String(t)))}async validateBoundFilesExist(e){if(!e||e.length===0)return;const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),{access:r}=await import("node:fs/promises"),n=[];for(const i of e)try{const s=f.resolve(t,i);await r(s)}catch{n.push(i)}if(n.length>0)throw new Error(`\u7ED1\u5B9A\u7684\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84\u6B63\u786E\u540E\u518D\u91CD\u65B0\u63D0\u4EA4:
|
|
2
2
|
${n.map(i=>` - ${i}`).join(`
|
|
3
|
-
`)}`)}toMemoryWriteResult(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files}}toMemoryRecordWithMetadata(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,content:e.content,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files,file_md5_map:e.file_md5_map,memory_status:e.memory_status,updating_agent_id:e.updating_agent_id,last_check_time:e.last_check_time}}constructor(){this.agentClient=new H,this.messageBuffer=new V,this.initialRoleName=v.resolveRoleMemoryDirectoryName(),this.hasRoleName=!!process.env.AWS_ROLE_NAME;const e=v.resolveMemoryStorePaths();this.memoryStore=new Q(this.agentClient),this.roleMemoryStore=new N(e.rolePath),this.sessionContextMemoryStore=new N(f.join(f.dirname(e.rolePath),"..","session_context")),this.isSubAgent=process.env.AWS_SUB_AGENT==="true",this.contextManager=new X(this.sessionContextMemoryStore),this.server=new U({name:z,version:G},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(W,async()=>({tools:this.isSubAgent?O.filter(t=>F.has(t.name)):O})),this.server.setRequestHandler(q,async e=>{const{name:t,arguments:r}=e.params;try{const n=await this.handleToolCall(t,r);return{content:this.buildToolResultContent(n)}}catch(n){const i=n instanceof Error?n.message:String(n),s=n instanceof Z?{error:i,...n.details}:{error:i};return a.error("[McpServer] \u5DE5\u5177\u8C03\u7528\u5F02\u5E38: tool=%s, error=%s, stack=%s",t,i,n instanceof Error?n.stack:"(no stack)"),{content:[{type:"text",text:JSON.stringify(s,null,2)}],isError:!0}}})}buildToolResultContent(e){if(e&&typeof e=="object"&&!Array.isArray(e)){const t=e;if(typeof t.image=="string"&&typeof t.mimeType=="string")return[{type:"image",data:t.image,mimeType:t.mimeType}]}return[{type:"text",text:JSON.stringify(e,null,2)}]}async handleToolCall(e,t){const r=t?._aws_sub_agent===!0,n=t?._aws_silent_poll===!0,i={...t};delete i._aws_sub_agent,delete i._aws_silent_poll;const s=r?null:this.agentClient.getStatusReporter();s&&!n&&s.reportToolUse(e,this.sanitizeToolUseArgs(e,i));try{const o=await this.executeToolCall(e,i);if(s&&!n){const l=this.stripAutoDelegateForReporting(o);l!==null&&(s.reportToolResult(l),s.reportThinking())}return o}catch(o){throw s&&!n&&s.reportToolError(o),o}}async executeToolCall(e,t){if(this.isSubAgent&&!F.has(e))throw new Error(`Sub-agent is not allowed to call tool: ${e}`);switch(e){case"get_colleague":return await this.agentClient.discoverColleagues();case"get_group_rooms":return await this.agentClient.discoverGroupRooms();case"get_profile":return await this.agentClient.getProfile();case"hire_colleague":return await this.handleHireColleague(t);case"hire_colleagues_batch":return await this.handleHireColleaguesBatch(t);case"send_group":return await this.handleSendGroup(t);case"send_private":return await this.handleSendPrivate(t);case"poll_message":return await this.handlePollMessage();case"get_message":return this.handleGetMessage();case"get_dm_history":return await this.handleGetDmHistory(t);case"get_group_history":return await this.handleGetGroupHistory(t);case"my_task":return await this.agentClient.getMyTasks();case"create_task_instance_comment":return await this.handleCreateTaskInstanceComment(t);case"submit_task_result":return await this.handleSubmitTaskResult(t);case"start_task_rollback":return await this.handleStartTaskRollback(t);case"finish_task_rollback":return await this.handleFinishTaskRollback(t);case"reject_task":return await this.handleRejectTask(t);case"terminate_task":return await this.handleTerminateTask(t);case"create_memory":return await this.handleCreateMemory(t);case"read_memory":return await this.handleReadMemory(t);case"update_memory":return await this.handleUpdateMemory(t);case"delete_memory":return await this.handleDeleteMemory(t);case"search_memory":return await this.handleSearchMemory(t);case"list_memories":return await this.handleListMemories(t);case"get_memory_stats":return await this.handleGetMemoryStats(t);case"list_memories_by_file":return await this.handleListMemoriesByFile(t);case"abandon_memory_update":return await this.handleAbandonMemoryUpdate(t);case"memory_recurrence":return await this.handleMemoryRecurrence(t);case"memory_recurrence_complete":return await this.handleMemoryRecurrenceComplete();case"get_debug_log_config":return await this.agentClient.getDebugLogConfig();case"query_debug_logs":return await this.agentClient.queryDebugLogs(t);case"query_file_changes_history":return await this.agentClient.queryFileChangesHistory(t);default:throw new Error(`Unknown tool: ${e}`)}}static MEMORY_TYPES=["recently_memory","agent_memory","project_memory","session_context"];static MEMORY_TOOL_NAMES=new Set(["create_memory","read_memory","update_memory","delete_memory","search_memory","list_memories","get_memory_stats","list_memories_by_file","abandon_memory_update","memory_recurrence","memory_recurrence_complete"]);static findAgentsworkDirSync(e){let t=f.resolve(e);for(;;){if(E.existsSync(f.join(t,".agentswork")))return f.join(t,".agentswork");const r=f.dirname(t);if(r===t)break;t=r}return f.join(f.resolve(e),".agentswork")}static sanitizeRoleDirectoryName(e){return String(e||"").trim().split("").map(r=>re.has(r)||r.charCodeAt(0)<32?"_":r).join("").replace(/^\.+|\.+$/g,"").trim()||"default-role"}static resolveRoleMemoryDirectoryName(){return v.sanitizeRoleDirectoryName(process.env.AWS_ROLE_NAME||"")}static buildRoleMemoryStorePath(e){const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t);return f.join(r,"memory","memory",v.sanitizeRoleDirectoryName(e))}static resolveMemoryStorePaths(){const e=String(process.env.AWS_MEMORY_STORE_PATH||"").trim();if(e)return{instancePath:e,rolePath:e};const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t),n=String(process.env.AWS_INSTANCE_MEMORY_STORE_PATH||"").trim()||f.join(r,"memory","server-managed"),i=String(process.env.AWS_ROLE_MEMORY_STORE_PATH||"").trim()||f.join(r,"memory","memory",v.resolveRoleMemoryDirectoryName());return{instancePath:n,rolePath:i}}getRequiredString(e,t){const r=e[t];if(typeof r!="string"||r.trim().length===0)throw new Error(`${t} is required`);return r}getOptionalString(e,t){const r=e[t];return typeof r=="string"?r:void 0}getOptionalNumber(e,t){const r=e[t];return typeof r=="number"?r:void 0}getOptionalStringArray(e,t){const r=e[t];return Array.isArray(r)?r.map(n=>String(n)):void 0}getOptionalMemoryType(e){const t=e.memory_type;if(t!==void 0)return this.parseMemoryType(t)}parseMemoryType(e){if(typeof e!="string")throw new Error(`Invalid memory_type: ${String(e)}`);const t=e.trim();if(!this.isMemoryType(t))throw new Error(`Invalid memory_type: ${e}`);return t}isMemoryType(e){return v.MEMORY_TYPES.some(t=>t===e)}getMemoryStoreForType(e){return this.memoryStore}async readMemoryFromScopedStores(e){if(e.memory_type)return await this.memoryStore.readMemory(e);const t=await this.memoryStore.readMemory({...e,memory_type:"project_memory"}).catch(()=>null);return t||await this.memoryStore.readMemory(e)}async backupProjectMemoryToLocal(e){try{const t=await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"});t&&(await this.roleMemoryStore.deleteMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>{}),await this.roleMemoryStore.createMemory(t))}catch(t){a.warn("[McpServer] backupProjectMemoryToLocal \u5931\u8D25: uri=%s",e.uri,t)}}async removeProjectMemoryLocalBackup(e){try{await this.roleMemoryStore.deleteMemory({uri:e,memory_type:"project_memory"})}catch(t){a.warn("[McpServer] removeProjectMemoryLocalBackup \u5931\u8D25: uri=%s",e,t)}}async findMemoryStoreForMutation(e){return e.memory_type?{store:this.memoryStore,memory_type:e.memory_type}:await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>null)?{store:this.memoryStore,memory_type:"project_memory"}:{store:this.memoryStore}}async reportMemoryStatsToServer(){const e=await this.memoryStore.getStats();try{await this.agentClient.reportMemoryStats(e)}catch(t){a.warn("[McpServer] \u4E0A\u62A5 memory \u7EDF\u8BA1\u5931\u8D25:",t)}}sanitizeToolUseArgs(e,t){if(!v.MEMORY_TOOL_NAMES.has(e))return t;const r={},n=this.getOptionalString(t,"uri"),i=this.getOptionalString(t,"memory_type"),s=this.getOptionalNumber(t,"limit");return n&&(r.uri=n),i&&(r.memory_type=i),typeof s=="number"&&(r.limit=s),e==="search_memory"&&(r.queryProvided=typeof t.query=="string"&&t.query.length>0,t.keyword_mode!==void 0&&(r.keyword_mode=t.keyword_mode)),r}stripAutoDelegateForReporting(e){if(!e||typeof e!="object")return e;const t=e,r=Array.isArray(t.directMessages)?t.directMessages:[],n=Array.isArray(t.groupMessages)?t.groupMessages:[],i=l=>!!l&&typeof l=="object"&&"_acodeAutoDelegate"in l;if(!r.some(i))return e;const o=r.filter(l=>!i(l));return o.length===0&&n.length===0?null:{...t,directMessages:o}}async handleCreateMemory(e){const t=this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"));await this.validateBoundFilesExist(t);let r,n;if(t&&t.length>0){r={};for(const o of t){const l=await this.calculateFileMd5(o);l&&(r[o]=l)}n=new Date().toISOString()}const i={memory_type:this.parseMemoryType(this.getRequiredString(e,"memory_type")),uri:this.getRequiredString(e,"uri"),title:this.getOptionalString(e,"title"),content:this.getRequiredString(e,"content"),keywords:this.getOptionalStringArray(e,"keywords"),bound_files:t,file_md5_map:r,last_check_time:n},s=await this.memoryStore.createMemory(i);return i.memory_type==="project_memory"&&this.backupProjectMemoryToLocal(i).catch(o=>{a.warn("[McpServer] \u5907\u4EFD project_memory \u5230\u672C\u5730\u5931\u8D25:",o)}),await this.reportMemoryStatsToServer(),this.toMemoryWriteResult(s)}async handleReadMemory(e){const t={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e)};return await this.readMemoryFromScopedStores(t)}async handleUpdateMemory(e){const t=typeof e.old_string=="string"||typeof e.new_string=="string",r=typeof e.old_string=="string"&&typeof e.new_string=="string",n=this.getOptionalString(e,"append");if(t&&!r)throw new Error("old_string and new_string must be provided together");const i={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e),title:this.getOptionalString(e,"title"),old_string:this.getOptionalString(e,"old_string"),new_string:this.getOptionalString(e,"new_string"),append:n,keywords:this.getOptionalStringArray(e,"keywords"),bound_files:this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"))},s=await this.findMemoryStoreForMutation(i),o=await s.store.readMemory({uri:i.uri,memory_type:s.memory_type??i.memory_type});i.bound_files!==void 0?await this.validateBoundFilesExist(i.bound_files):o?.bound_files&&o.bound_files.length>0&&await this.validateBoundFilesExist(o.bound_files);const l=i.bound_files??o?.bound_files;let m,u;if(l&&l.length>0){m={};for(const d of l){const p=await this.calculateFileMd5(d);p&&(m[d]=p)}u=new Date().toISOString()}const h=await s.store.updateMemory({...i,memory_type:s.memory_type??i.memory_type,file_md5_map:m,last_check_time:u});if(s.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({...i,memory_type:"project_memory",file_md5_map:m,last_check_time:u}).catch(d=>{a.warn("[McpServer] \u5907\u4EFD project_memory \u66F4\u65B0\u5230\u672C\u5730\u5931\u8D25:",d)}),await this.reportMemoryStatsToServer(),s.memory_type==="project_memory"&&o?.memory_status==="updating")try{await this.memoryStore.releaseMemoryClaim(i.uri),this.activeLeaseUris.delete(i.uri),this.activeLeaseUris.size===0&&this.stopLeaseRenewal()}catch(d){a.warn("[McpServer] \u91CA\u653E memory claim \u5931\u8D25: uri=%s, error=%s",i.uri,d instanceof Error?d.message:String(d))}return this.toMemoryWriteResult(h)}async handleDeleteMemory(e){const t=typeof e.uri=="string"?e.uri:void 0,r=Array.isArray(e.memories)?e.memories:void 0;if(t&&r&&r.length>0)throw new Error("delete_memory \u4E0D\u652F\u6301\u540C\u65F6\u4F20\u5165 uri \u548C memories\uFF0C\u8BF7\u4E8C\u9009\u4E00");if(!t&&(!r||r.length===0))throw new Error("delete_memory \u9700\u8981 uri \u6216 memories \u53C2\u6570");if(t){const o=this.getOptionalMemoryType(e);return{deleted:await this.deleteMemorySingle(t,o)}}let n=0;const i=[],s=[];for(let o=0;o<r.length;o++){const l=r[o];if(typeof l!="object"||l===null){s.push({uri:String(l),reason:`memories[${o}] \u4E0D\u662F\u5BF9\u8C61`});continue}const m=l,u=typeof m.uri=="string"?m.uri:"";if(!u){s.push({uri:"",reason:`memories[${o}].uri \u7F3A\u5931`});continue}const h=typeof m.memory_type=="string"?this.parseMemoryType(m.memory_type):void 0;try{await this.deleteMemorySingle(u,h,!0)?n++:i.push(u)}catch(d){s.push({uri:u,reason:d instanceof Error?d.message:String(d)})}}return n>0&&await this.reportMemoryStatsToServer(),{deleted:n,not_found:i,failed:s}}async deleteMemorySingle(e,t,r=!1){const n={uri:e,memory_type:t},i=await this.findMemoryStoreForMutation(n),s=i.memory_type??n.memory_type,o=await i.store.deleteMemory({...n,memory_type:s});return o&&(s==="project_memory"&&this.removeProjectMemoryLocalBackup(e).catch(l=>{a.warn("[McpServer] \u5220\u9664 project_memory \u672C\u5730\u5907\u4EFD\u5931\u8D25:",l)}),r||await this.reportMemoryStatsToServer()),o}async handleSearchMemory(e){const t=this.getOptionalString(e,"scope"),r=t==="children"||t==="all"?t:"own",n=this.getOptionalString(e,"keyword_mode"),i=n==="all"?"all":n==="any"?"any":void 0,s={query:this.getRequiredString(e,"query"),limit:this.getOptionalNumber(e,"limit"),memory_type:this.getOptionalMemoryType(e),keywords:this.getOptionalStringArray(e,"keywords"),keyword_mode:i,scope:r};if(r==="children")return await this.memoryStore.searchMemory(s);const o=await this.searchOwnMemories(s);if(r==="own")return o;const l=await this.memoryStore.searchMemory({...s,scope:"children"});return this.mergeSearchResults(o,l,s.limit)}async searchOwnMemories(e){return await this.memoryStore.searchMemory(e)}mergeSearchResults(e,t,r){const n=new Set,i=typeof r=="number"&&r>0?r:10,s=[...e.results,...t.results].filter(o=>{const l=o.uri;return n.has(l)?!1:(n.add(l),!0)}).sort((o,l)=>l.updatedAt.localeCompare(o.updatedAt)).slice(0,i);return{query:e.query||t.query,results:s}}async handleListMemories(e){const t=this.getRequiredString(e,"memory_type");if(t==="project_memory"||t==="session_context")throw new Error(`Invalid memory_type: ${t}`);return await this.memoryStore.listByType(t)}async handleGetMemoryStats(e){const t=await this.memoryStore.getStats(),r=this.getOptionalMemoryType(e);return r?{memory_type:r,total:t.by_type[r].count,by_type:{[r]:t.by_type[r]}}:t}async handleListMemoriesByFile(e){const t=this.getRequiredString(e,"file_path"),r=this.normalizeBoundFilePath(t),n=this.getOptionalMemoryType(e),i=this.getOptionalString(e,"scope"),s=i==="own"||i==="children"?i:"all",o=async()=>await this.memoryStore.listByFile(r,{memory_type:n,scope:"children"}).catch(()=>[]);if(s==="children"){const d=await o();return{file_path:r,count:d.length,memories:d.map(p=>this.toMemoryRecordWithMetadata(p))}}const l=await this.collectOwnMemoriesByFile(r,n);if(s==="own")return{file_path:r,count:l.length,memories:l.map(d=>this.toMemoryRecordWithMetadata(d))};const m=await o(),u=new Set(l.map(d=>d.id)),h=[...l];for(const d of m)u.has(d.id)||(h.push(d),u.add(d.id));return h.sort((d,p)=>p.updatedAt.localeCompare(d.updatedAt)),{file_path:r,count:h.length,memories:h.map(d=>this.toMemoryRecordWithMetadata(d))}}mergeIdDedupe(e,t){const r=new Set;for(const i of e)i.id&&r.add(i.id);const n=[...e];for(const i of t)i.id&&!r.has(i.id)&&(n.push(i),r.add(i.id));return n.sort((i,s)=>s.updatedAt.localeCompare(i.updatedAt)),n}async searchChildMemoriesByFileLocal(e,t){const r=process.env.AWS_WORKSPACE_PATH||process.cwd(),n=this.normalizeBoundFilePath(e),s=(n.startsWith("./")?n.substring(2):n).split("/"),o=[],l=new Set,{access:m,readdir:u,readFile:h}=await import("node:fs/promises");for(let d=s.length-1;d>=1;d--){const p=s.slice(0,d),R="./"+s.slice(d).join("/"),A=f.join(r,...p);if(A===r)continue;const c=f.join(A,".agentswork");try{await m(c)}catch{continue}const g=f.join(c,"memory","memory");let b;try{b=(await u(g,{withFileTypes:!0})).filter(_=>_.isDirectory()).map(_=>_.name)}catch{continue}for(const M of b){const _=f.join(g,M);let C;try{C=await u(_)}catch{continue}for(const y of C){if(!y.endsWith(".json"))continue;const w=f.join(_,y);let S;try{S=await h(w,"utf-8")}catch{continue}let T;try{T=JSON.parse(S)}catch{continue}if(T.by_uri)for(const[$,I]of Object.entries(T.by_uri)){if(!I)continue;const D=I.id||$;l.has(D)||t&&I.memory_type!==t||I.bound_files&&I.bound_files.length>0&&I.bound_files.some(B=>this.normalizeBoundFilePath(B)===R)&&(l.add(D),o.push({...I,uri:I.uri||$}))}}}}return o.sort((d,p)=>p.updatedAt.localeCompare(d.updatedAt)),o}async collectOwnMemoriesByFile(e,t){return await this.memoryStore.listByFile(e,{memory_type:t})}async handleAbandonMemoryUpdate(e){const t=this.getRequiredString(e,"uri"),r=this.getOptionalMemoryType(e),n=await this.memoryStore.readMemory({uri:t,memory_type:r});return n?.bound_files&&n.bound_files.length>0&&await this.validateBoundFilesExist(n.bound_files),await this.memoryStore.releaseMemoryClaim(t),n?.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({uri:t,memory_type:"project_memory"}).catch(i=>{a.info(`[McpServer] abandon_memory_update: \u540C\u6B65\u672C\u5730\u5907\u4EFD\u5931\u8D25: ${t}`,i)}),this.activeLeaseUris.delete(t),this.activeLeaseUris.size===0&&this.stopLeaseRenewal(),{success:!0,uri:t,message:"Memory \u66F4\u65B0\u72B6\u6001\u5DF2\u6E05\u9664"}}async handleMemoryRecurrence(e){const t=Math.max(1,e?.limit??10),r=await this.memoryStore.listExpiredRecentlyMemories();if(r.length===0)return{memories:[],count:0,remaining:0,totalExpired:0,message:"\u6CA1\u6709\u5F85\u56DE\u5FC6\u7684\u5373\u5C06\u9057\u5FD8\u8BB0\u5FC6\u3002"};const i=[...r].sort((m,u)=>{const h=new Date(m.expiresAt||m.createdAt||0).getTime(),d=new Date(u.expiresAt||u.createdAt||0).getTime();return h-d}).slice(0,t),s=i.map(m=>m.uri);try{await this.memoryStore.markExpiredMemoriesRecalling(s)}catch(m){a.warn("[McpServer] \u6807\u8BB0\u8FC7\u671F\u8BB0\u5FC6\u4E3A recalling \u5931\u8D25: uriCount=%s, error=%s",s.length,m instanceof Error?m.message:String(m))}const o=Math.max(0,r.length-i.length);return{memories:i.map(m=>({uri:m.uri,title:m.title,domain:m.domain,path:m.path,memory_status:"recalling",contentPreview:typeof m.content=="string"&&m.content.length>200?m.content.substring(0,200)+"...":m.content,createdAt:m.createdAt,updatedAt:m.updatedAt})),count:i.length,limit:t,remaining:o,totalExpired:r.length,message:o>0?`\u672C\u6B21\u5904\u7406 ${i.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\uFF08\u6700\u65E9\u8FC7\u671F\u4F18\u5148\uFF09\uFF0C\u5269\u4F59 ${o} \u6761\u5F85\u5904\u7406\u3002
|
|
3
|
+
`)}`)}toMemoryWriteResult(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files}}toMemoryRecordWithMetadata(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,content:e.content,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files,file_md5_map:e.file_md5_map,memory_status:e.memory_status,updating_agent_id:e.updating_agent_id,last_check_time:e.last_check_time}}constructor(){this.agentClient=new H,this.messageBuffer=new V,this.initialRoleName=v.resolveRoleMemoryDirectoryName(),this.hasRoleName=!!process.env.AWS_ROLE_NAME;const e=v.resolveMemoryStorePaths();this.memoryStore=new Q(this.agentClient),this.roleMemoryStore=new N(e.rolePath),this.sessionContextMemoryStore=new N(f.join(f.dirname(e.rolePath),"..","session_context")),this.isSubAgent=process.env.AWS_SUB_AGENT==="true",this.contextManager=new X(this.sessionContextMemoryStore),this.server=new U({name:z,version:G},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(W,async()=>({tools:this.isSubAgent?O.filter(t=>F.has(t.name)):O})),this.server.setRequestHandler(q,async e=>{const{name:t,arguments:r}=e.params;try{const n=await this.handleToolCall(t,r);return{content:this.buildToolResultContent(n)}}catch(n){const i=n instanceof Error?n.message:String(n),s=n instanceof Z?{error:i,...n.details}:{error:i};return o.error("[McpServer] \u5DE5\u5177\u8C03\u7528\u5F02\u5E38: tool=%s, error=%s, stack=%s",t,i,n instanceof Error?n.stack:"(no stack)"),{content:[{type:"text",text:JSON.stringify(s,null,2)}],isError:!0}}})}buildToolResultContent(e){if(e&&typeof e=="object"&&!Array.isArray(e)){const t=e;if(typeof t.image=="string"&&typeof t.mimeType=="string")return[{type:"image",data:t.image,mimeType:t.mimeType}]}return[{type:"text",text:JSON.stringify(e,null,2)}]}async handleToolCall(e,t){const r=t?._aws_sub_agent===!0,n=t?._aws_silent_poll===!0,i={...t};delete i._aws_sub_agent,delete i._aws_silent_poll;const s=r?null:this.agentClient.getStatusReporter();s&&!n&&s.reportToolUse(e,this.sanitizeToolUseArgs(e,i));try{const a=await this.executeToolCall(e,i);if(s&&!n){const l=this.stripAutoDelegateForReporting(a);l!==null&&(s.reportToolResult(l),s.reportThinking())}return a}catch(a){throw s&&!n&&s.reportToolError(a),a}}async executeToolCall(e,t){if(this.isSubAgent&&!F.has(e))throw new Error(`Sub-agent is not allowed to call tool: ${e}`);switch(e){case"get_colleague":return await this.agentClient.discoverColleagues();case"get_group_rooms":return await this.agentClient.discoverGroupRooms();case"get_profile":return await this.agentClient.getProfile();case"hire_colleague":return await this.handleHireColleague(t);case"hire_colleagues_batch":return await this.handleHireColleaguesBatch(t);case"send_group":return await this.handleSendGroup(t);case"send_private":return await this.handleSendPrivate(t);case"poll_message":return await this.handlePollMessage();case"get_message":return this.handleGetMessage();case"get_dm_history":return await this.handleGetDmHistory(t);case"get_group_history":return await this.handleGetGroupHistory(t);case"my_task":return await this.agentClient.getMyTasks();case"create_task_instance_comment":return await this.handleCreateTaskInstanceComment(t);case"submit_task_result":return await this.handleSubmitTaskResult(t);case"start_task_rollback":return await this.handleStartTaskRollback(t);case"finish_task_rollback":return await this.handleFinishTaskRollback(t);case"reject_task":return await this.handleRejectTask(t);case"terminate_task":return await this.handleTerminateTask(t);case"create_memory":return await this.handleCreateMemory(t);case"read_memory":return await this.handleReadMemory(t);case"update_memory":return await this.handleUpdateMemory(t);case"delete_memory":return await this.handleDeleteMemory(t);case"search_memory":return await this.handleSearchMemory(t);case"list_memories":return await this.handleListMemories(t);case"get_memory_stats":return await this.handleGetMemoryStats(t);case"list_memories_by_file":return await this.handleListMemoriesByFile(t);case"abandon_memory_update":return await this.handleAbandonMemoryUpdate(t);case"memory_recurrence":return await this.handleMemoryRecurrence(t);case"memory_recurrence_complete":return await this.handleMemoryRecurrenceComplete();case"get_debug_log_config":return await this.agentClient.getDebugLogConfig();case"query_debug_logs":return await this.agentClient.queryDebugLogs(t);case"query_file_changes_history":return await this.agentClient.queryFileChangesHistory(t);default:throw new Error(`Unknown tool: ${e}`)}}static MEMORY_TYPES=["recently_memory","agent_memory","project_memory","session_context"];static MEMORY_TOOL_NAMES=new Set(["create_memory","read_memory","update_memory","delete_memory","search_memory","list_memories","get_memory_stats","list_memories_by_file","abandon_memory_update","memory_recurrence","memory_recurrence_complete"]);static findAgentsworkDirSync(e){let t=f.resolve(e);for(;;){if(E.existsSync(f.join(t,".agentswork")))return f.join(t,".agentswork");const r=f.dirname(t);if(r===t)break;t=r}return f.join(f.resolve(e),".agentswork")}static sanitizeRoleDirectoryName(e){return String(e||"").trim().split("").map(r=>re.has(r)||r.charCodeAt(0)<32?"_":r).join("").replace(/^\.+|\.+$/g,"").trim()||"default-role"}static resolveRoleMemoryDirectoryName(){return v.sanitizeRoleDirectoryName(process.env.AWS_ROLE_NAME||"")}static buildRoleMemoryStorePath(e){const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t);return f.join(r,"memory","memory",v.sanitizeRoleDirectoryName(e))}static resolveMemoryStorePaths(){const e=String(process.env.AWS_MEMORY_STORE_PATH||"").trim();if(e)return{instancePath:e,rolePath:e};const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t),n=String(process.env.AWS_INSTANCE_MEMORY_STORE_PATH||"").trim()||f.join(r,"memory","server-managed"),i=String(process.env.AWS_ROLE_MEMORY_STORE_PATH||"").trim()||f.join(r,"memory","memory",v.resolveRoleMemoryDirectoryName());return{instancePath:n,rolePath:i}}getRequiredString(e,t){const r=e[t];if(typeof r!="string"||r.trim().length===0)throw new Error(`${t} is required`);return r}getOptionalString(e,t){const r=e[t];return typeof r=="string"?r:void 0}getOptionalNumber(e,t){const r=e[t];return typeof r=="number"?r:void 0}getOptionalStringArray(e,t){const r=e[t];return Array.isArray(r)?r.map(n=>String(n)):void 0}getOptionalMemoryType(e){const t=e.memory_type;if(t!==void 0)return this.parseMemoryType(t)}parseMemoryType(e){if(typeof e!="string")throw new Error(`Invalid memory_type: ${String(e)}`);const t=e.trim();if(!this.isMemoryType(t))throw new Error(`Invalid memory_type: ${e}`);return t}isMemoryType(e){return v.MEMORY_TYPES.some(t=>t===e)}getMemoryStoreForType(e){return this.memoryStore}async readMemoryFromScopedStores(e){if(e.memory_type)return await this.memoryStore.readMemory(e);const t=await this.memoryStore.readMemory({...e,memory_type:"project_memory"}).catch(()=>null);return t||await this.memoryStore.readMemory(e)}async backupProjectMemoryToLocal(e){try{const t=await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"});t&&(await this.roleMemoryStore.deleteMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>{}),await this.roleMemoryStore.createMemory(t))}catch(t){o.warn("[McpServer] backupProjectMemoryToLocal \u5931\u8D25: uri=%s",e.uri,t)}}async removeProjectMemoryLocalBackup(e){try{await this.roleMemoryStore.deleteMemory({uri:e,memory_type:"project_memory"})}catch(t){o.warn("[McpServer] removeProjectMemoryLocalBackup \u5931\u8D25: uri=%s",e,t)}}async findMemoryStoreForMutation(e){return e.memory_type?{store:this.memoryStore,memory_type:e.memory_type}:await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>null)?{store:this.memoryStore,memory_type:"project_memory"}:{store:this.memoryStore}}async reportMemoryStatsToServer(){const e=await this.memoryStore.getStats();try{await this.agentClient.reportMemoryStats(e)}catch(t){o.warn("[McpServer] \u4E0A\u62A5 memory \u7EDF\u8BA1\u5931\u8D25:",t)}}sanitizeToolUseArgs(e,t){if(!v.MEMORY_TOOL_NAMES.has(e))return t;const r={},n=this.getOptionalString(t,"uri"),i=this.getOptionalString(t,"memory_type"),s=this.getOptionalNumber(t,"limit");return n&&(r.uri=n),i&&(r.memory_type=i),typeof s=="number"&&(r.limit=s),e==="search_memory"&&(r.queryProvided=typeof t.query=="string"&&t.query.length>0,t.keyword_mode!==void 0&&(r.keyword_mode=t.keyword_mode)),r}stripAutoDelegateForReporting(e){if(!e||typeof e!="object")return e;const t=e,r=Array.isArray(t.directMessages)?t.directMessages:[],n=Array.isArray(t.groupMessages)?t.groupMessages:[],i=l=>!!l&&typeof l=="object"&&"_acodeAutoDelegate"in l;if(!r.some(i))return e;const a=r.filter(l=>!i(l));return a.length===0&&n.length===0?null:{...t,directMessages:a}}async handleCreateMemory(e){const t=this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"));await this.validateBoundFilesExist(t);let r,n;if(t&&t.length>0){r={};for(const a of t){const l=await this.calculateFileMd5(a);l&&(r[a]=l)}n=new Date().toISOString()}const i={memory_type:this.parseMemoryType(this.getRequiredString(e,"memory_type")),uri:this.getRequiredString(e,"uri"),title:this.getOptionalString(e,"title"),content:this.getRequiredString(e,"content"),keywords:this.getOptionalStringArray(e,"keywords"),bound_files:t,file_md5_map:r,last_check_time:n},s=await this.memoryStore.createMemory(i);return i.memory_type==="project_memory"&&this.backupProjectMemoryToLocal(i).catch(a=>{o.warn("[McpServer] \u5907\u4EFD project_memory \u5230\u672C\u5730\u5931\u8D25:",a)}),await this.reportMemoryStatsToServer(),this.toMemoryWriteResult(s)}async handleReadMemory(e){const t={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e)};return await this.readMemoryFromScopedStores(t)}async handleUpdateMemory(e){const t=typeof e.old_string=="string"||typeof e.new_string=="string",r=typeof e.old_string=="string"&&typeof e.new_string=="string",n=this.getOptionalString(e,"append");if(t&&!r)throw new Error("old_string and new_string must be provided together");const i={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e),title:this.getOptionalString(e,"title"),old_string:this.getOptionalString(e,"old_string"),new_string:this.getOptionalString(e,"new_string"),append:n,keywords:this.getOptionalStringArray(e,"keywords"),bound_files:this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"))},s=await this.findMemoryStoreForMutation(i),a=await s.store.readMemory({uri:i.uri,memory_type:s.memory_type??i.memory_type});i.bound_files!==void 0?await this.validateBoundFilesExist(i.bound_files):a?.bound_files&&a.bound_files.length>0&&await this.validateBoundFilesExist(a.bound_files);const l=i.bound_files??a?.bound_files;let m,u;if(l&&l.length>0){m={};for(const d of l){const p=await this.calculateFileMd5(d);p&&(m[d]=p)}u=new Date().toISOString()}const h=await s.store.updateMemory({...i,memory_type:s.memory_type??i.memory_type,file_md5_map:m,last_check_time:u});if(s.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({...i,memory_type:"project_memory",file_md5_map:m,last_check_time:u}).catch(d=>{o.warn("[McpServer] \u5907\u4EFD project_memory \u66F4\u65B0\u5230\u672C\u5730\u5931\u8D25:",d)}),await this.reportMemoryStatsToServer(),s.memory_type==="project_memory"&&a?.memory_status==="updating")try{await this.memoryStore.releaseMemoryClaim(i.uri),this.activeLeaseUris.delete(i.uri),this.activeLeaseUris.size===0&&this.stopLeaseRenewal()}catch(d){o.warn("[McpServer] \u91CA\u653E memory claim \u5931\u8D25: uri=%s, error=%s",i.uri,d instanceof Error?d.message:String(d))}return this.toMemoryWriteResult(h)}async handleDeleteMemory(e){const t=typeof e.uri=="string"?e.uri:void 0,r=Array.isArray(e.memories)?e.memories:void 0;if(t&&r&&r.length>0)throw new Error("delete_memory \u4E0D\u652F\u6301\u540C\u65F6\u4F20\u5165 uri \u548C memories\uFF0C\u8BF7\u4E8C\u9009\u4E00");if(!t&&(!r||r.length===0))throw new Error("delete_memory \u9700\u8981 uri \u6216 memories \u53C2\u6570");if(t){const a=this.getOptionalMemoryType(e);return{deleted:await this.deleteMemorySingle(t,a)}}let n=0;const i=[],s=[];for(let a=0;a<r.length;a++){const l=r[a];if(typeof l!="object"||l===null){s.push({uri:String(l),reason:`memories[${a}] \u4E0D\u662F\u5BF9\u8C61`});continue}const m=l,u=typeof m.uri=="string"?m.uri:"";if(!u){s.push({uri:"",reason:`memories[${a}].uri \u7F3A\u5931`});continue}const h=typeof m.memory_type=="string"?this.parseMemoryType(m.memory_type):void 0;try{await this.deleteMemorySingle(u,h,!0)?n++:i.push(u)}catch(d){s.push({uri:u,reason:d instanceof Error?d.message:String(d)})}}return n>0&&await this.reportMemoryStatsToServer(),{deleted:n,not_found:i,failed:s}}async deleteMemorySingle(e,t,r=!1){const n={uri:e,memory_type:t},i=await this.findMemoryStoreForMutation(n),s=i.memory_type??n.memory_type,a=await i.store.deleteMemory({...n,memory_type:s});return a&&(s==="project_memory"&&this.removeProjectMemoryLocalBackup(e).catch(l=>{o.warn("[McpServer] \u5220\u9664 project_memory \u672C\u5730\u5907\u4EFD\u5931\u8D25:",l)}),r||await this.reportMemoryStatsToServer()),a}async handleSearchMemory(e){const t=this.getOptionalString(e,"scope"),r=t==="children"||t==="all"?t:"own",n=this.getOptionalString(e,"keyword_mode"),i=n==="all"?"all":n==="any"?"any":void 0,s={query:this.getRequiredString(e,"query"),limit:this.getOptionalNumber(e,"limit"),memory_type:this.getOptionalMemoryType(e),keywords:this.getOptionalStringArray(e,"keywords"),keyword_mode:i,scope:r};if(r==="children")return await this.memoryStore.searchMemory(s);const a=await this.searchOwnMemories(s);if(r==="own")return a;const l=await this.memoryStore.searchMemory({...s,scope:"children"});return this.mergeSearchResults(a,l,s.limit)}async searchOwnMemories(e){return await this.memoryStore.searchMemory(e)}mergeSearchResults(e,t,r){const n=new Set,i=typeof r=="number"&&r>0?r:10,s=[...e.results,...t.results].filter(a=>{const l=a.uri;return n.has(l)?!1:(n.add(l),!0)}).sort((a,l)=>l.updatedAt.localeCompare(a.updatedAt)).slice(0,i);return{query:e.query||t.query,results:s}}async handleListMemories(e){const t=this.getRequiredString(e,"memory_type");if(t==="project_memory"||t==="session_context")throw new Error(`Invalid memory_type: ${t}`);return await this.memoryStore.listByType(t)}async handleGetMemoryStats(e){const t=await this.memoryStore.getStats(),r=this.getOptionalMemoryType(e);return r?{memory_type:r,total:t.by_type[r].count,by_type:{[r]:t.by_type[r]}}:t}async handleListMemoriesByFile(e){const t=this.getRequiredString(e,"file_path"),r=this.normalizeBoundFilePath(t),n=this.getOptionalMemoryType(e),i=this.getOptionalString(e,"scope"),s=i==="own"||i==="children"?i:"all",a=async()=>await this.memoryStore.listByFile(r,{memory_type:n,scope:"children"}).catch(()=>[]);if(s==="children"){const d=await a();return{file_path:r,count:d.length,memories:d.map(p=>this.toMemoryRecordWithMetadata(p))}}const l=await this.collectOwnMemoriesByFile(r,n);if(s==="own")return{file_path:r,count:l.length,memories:l.map(d=>this.toMemoryRecordWithMetadata(d))};const m=await a(),u=new Set(l.map(d=>d.id)),h=[...l];for(const d of m)u.has(d.id)||(h.push(d),u.add(d.id));return h.sort((d,p)=>p.updatedAt.localeCompare(d.updatedAt)),{file_path:r,count:h.length,memories:h.map(d=>this.toMemoryRecordWithMetadata(d))}}mergeIdDedupe(e,t){const r=new Set;for(const i of e)i.id&&r.add(i.id);const n=[...e];for(const i of t)i.id&&!r.has(i.id)&&(n.push(i),r.add(i.id));return n.sort((i,s)=>s.updatedAt.localeCompare(i.updatedAt)),n}async searchChildMemoriesByFileLocal(e,t){const r=process.env.AWS_WORKSPACE_PATH||process.cwd(),n=this.normalizeBoundFilePath(e),s=(n.startsWith("./")?n.substring(2):n).split("/"),a=[],l=new Set,{access:m,readdir:u,readFile:h}=await import("node:fs/promises");for(let d=s.length-1;d>=1;d--){const p=s.slice(0,d),R="./"+s.slice(d).join("/"),A=f.join(r,...p);if(A===r)continue;const c=f.join(A,".agentswork");try{await m(c)}catch{continue}const g=f.join(c,"memory","memory");let b;try{b=(await u(g,{withFileTypes:!0})).filter(_=>_.isDirectory()).map(_=>_.name)}catch{continue}for(const M of b){const _=f.join(g,M);let $;try{$=await u(_)}catch{continue}for(const y of $){if(!y.endsWith(".json"))continue;const w=f.join(_,y);let S;try{S=await h(w,"utf-8")}catch{continue}let T;try{T=JSON.parse(S)}catch{continue}if(T.by_uri)for(const[C,I]of Object.entries(T.by_uri)){if(!I)continue;const D=I.id||C;l.has(D)||t&&I.memory_type!==t||I.bound_files&&I.bound_files.length>0&&I.bound_files.some(B=>this.normalizeBoundFilePath(B)===R)&&(l.add(D),a.push({...I,uri:I.uri||C}))}}}}return a.sort((d,p)=>p.updatedAt.localeCompare(d.updatedAt)),a}async collectOwnMemoriesByFile(e,t){return await this.memoryStore.listByFile(e,{memory_type:t})}async handleAbandonMemoryUpdate(e){const t=this.getRequiredString(e,"uri"),r=this.getOptionalMemoryType(e),n=await this.memoryStore.readMemory({uri:t,memory_type:r});return n?.bound_files&&n.bound_files.length>0&&await this.validateBoundFilesExist(n.bound_files),await this.memoryStore.releaseMemoryClaim(t),n?.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({uri:t,memory_type:"project_memory"}).catch(i=>{o.info(`[McpServer] abandon_memory_update: \u540C\u6B65\u672C\u5730\u5907\u4EFD\u5931\u8D25: ${t}`,i)}),this.activeLeaseUris.delete(t),this.activeLeaseUris.size===0&&this.stopLeaseRenewal(),{success:!0,uri:t,message:"Memory \u66F4\u65B0\u72B6\u6001\u5DF2\u6E05\u9664"}}async handleMemoryRecurrence(e){const t=Math.max(1,e?.limit??10),r=await this.memoryStore.listExpiredRecentlyMemories();if(r.length===0)return{memories:[],count:0,remaining:0,totalExpired:0,message:"\u6CA1\u6709\u5F85\u56DE\u5FC6\u7684\u5373\u5C06\u9057\u5FD8\u8BB0\u5FC6\u3002"};const i=[...r].sort((m,u)=>{const h=new Date(m.expiresAt||m.createdAt||0).getTime(),d=new Date(u.expiresAt||u.createdAt||0).getTime();return h-d}).slice(0,t),s=i.map(m=>m.uri);try{await this.memoryStore.markExpiredMemoriesRecalling(s)}catch(m){o.warn("[McpServer] \u6807\u8BB0\u8FC7\u671F\u8BB0\u5FC6\u4E3A recalling \u5931\u8D25: uriCount=%s, error=%s",s.length,m instanceof Error?m.message:String(m))}const a=Math.max(0,r.length-i.length);return{memories:i.map(m=>({uri:m.uri,title:m.title,domain:m.domain,path:m.path,memory_status:"recalling",contentPreview:typeof m.content=="string"&&m.content.length>200?m.content.substring(0,200)+"...":m.content,createdAt:m.createdAt,updatedAt:m.updatedAt})),count:i.length,limit:t,remaining:a,totalExpired:r.length,message:a>0?`\u672C\u6B21\u5904\u7406 ${i.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\uFF08\u6700\u65E9\u8FC7\u671F\u4F18\u5148\uFF09\uFF0C\u5269\u4F59 ${a} \u6761\u5F85\u5904\u7406\u3002
|
|
4
4
|
\u8BF7\u5BA1\u67E5\u540E\u51B3\u5B9A\uFF08update_memory \u7EED\u671F\uFF0CTTL \xD72 / \u4E0D\u5904\u7406\uFF09\uFF0C
|
|
5
5
|
\u5B8C\u6210\u540E\u8C03\u7528 memory_recurrence_complete \u6E05\u7406\uFF0C\u518D\u8C03 memory_recurrence \u5904\u7406\u4E0B\u4E00\u6279\u3002`:`\u5DF2\u5904\u7406\u5168\u90E8 ${i.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\u3002
|
|
6
6
|
\u8BF7\u5BA1\u67E5\u540E\u51B3\u5B9A\uFF08update_memory \u7EED\u671F\uFF0CTTL \xD72 / \u4E0D\u5904\u7406\uFF09\uFF0C
|
|
7
|
-
\u5B8C\u6210\u540E\u8C03\u7528 memory_recurrence_complete \u6E05\u7406\u3002`}}async handleMemoryRecurrenceComplete(){let e={deleted:0,uris:[]};try{e=await this.memoryStore.clearRecallingExpiredMemories()}catch(r){
|
|
7
|
+
\u5B8C\u6210\u540E\u8C03\u7528 memory_recurrence_complete \u6E05\u7406\u3002`}}async handleMemoryRecurrenceComplete(){let e={deleted:0,uris:[]};try{e=await this.memoryStore.clearRecallingExpiredMemories()}catch(r){o.warn("[McpServer] \u6E05\u7406\u56DE\u5FC6\u4E2D\u8BB0\u5FC6\u5931\u8D25: %s",r instanceof Error?r.message:String(r))}await this.reportMemoryStatsToServer();const t=e.deleted??0;return{deleted:t,uris:e.uris??[],message:t===0?"\u6CA1\u6709\u5F85\u758F\u7406\u7684\u56DE\u5FC6\u4E2D\u8BB0\u5FC6\u3002":`\u5DF2\u6E05\u7406 ${t} \u6761\u5DF2\u8FC7\u671F\u8FD1\u671F\u8BB0\u5FC6\u3002`}}async fetchRecentMessages(){const[e,t]=await Promise.all([this.agentClient.getDmHistory(void 0,200).catch(()=>({messages:[],count:0})),this.agentClient.getGroupHistory(200).catch(()=>({messages:[],count:0}))]),r=[...e.messages,...t.messages];return r.sort((n,i)=>new Date(n.timestamp).getTime()-new Date(i.timestamp).getTime()),r}async handleSendGroup(e){if(!e.content||typeof e.content!="string")throw new Error("content is required");return this.recordMessageTokenUsage(e.content).catch(t=>o.warn("[McpServer] Failed to record token usage",t)),await this.agentClient.sendGroupMessage(e.content,e.projectId,e.roomId)}async recordMessageTokenUsage(e){try{const t=await this.agentClient.getProfile().catch(()=>null);if(!t?.runtimeSessionId)return;const r=this.contextManager.estimateTokens(e);await this.contextManager.recordUsage(t.runtimeSessionId,{input:0,output:r})}catch{}}async handleHireColleague(e){if(!e.workspacePath||typeof e.workspacePath!="string")throw new Error("workspacePath is required");return await this.agentClient.hireColleague(e)}async handleHireColleaguesBatch(e){if(!Array.isArray(e.items)||e.items.length===0)throw new Error("items is required and must contain at least one hire item");return e.items.forEach((t,r)=>{if(!t.workspacePath||typeof t.workspacePath!="string")throw new Error(`items[${r}].workspacePath is required`)}),await this.agentClient.hireColleaguesBatch(e)}async handleSendPrivate(e){if(!e.targetId||typeof e.targetId!="string")throw new Error("targetId is required");if(!e.content||typeof e.content!="string")throw new Error("content is required");return this.recordMessageTokenUsage(e.content).catch(t=>o.warn("[McpServer] Failed to record token usage",t)),await this.agentClient.sendDirectMessage(e.targetId,e.content,e.waitReply,e.replyToCallId,e.timeoutMs)}async handleCreateTaskInstanceComment(e){const t=this.getRequiredString(e,"taskInstanceId"),r=this.getRequiredString(e,"content");return await this.agentClient.createTaskInstanceComment({taskInstanceId:t,content:r})}async prependMemoryNotification(e,t){const r=await t();return r?(e.directMessages.unshift(r),!0):!1}async handlePollMessage(){if(o.info("[McpServer] handlePollMessage \u88AB\u8C03\u7528"),this.pendingPollMessage)return o.info("[McpServer] poll_message \u5DF2\u6709\u8FDB\u884C\u4E2D\u7684\u8C03\u7528\uFF0C\u7B49\u5F85\u5176\u5B8C\u6210"),await this.pendingPollMessage;this.pendingPollMessage=(async()=>await this.doPollMessage())();try{return await this.pendingPollMessage}finally{this.pendingPollMessage=null}}async doPollMessage(){o.info("[McpServer] doPollMessage \u88AB\u8C03\u7528"),await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0}),await this.hydrateBrowserListenerEvents();const e=await this.buildTodoReminderNotification(),t=await this.buildBackgroundTaskNotification(),r=this.messageBuffer.get();if(r.directMessages.length>0||r.groupMessages.length>0)return t&&r.directMessages.unshift(t),e&&r.directMessages.unshift(e),this.buildPollResult(r);if(e||t)return this.buildPollResult({directMessages:[e,t].filter(Boolean),groupMessages:[]});const n=await this.checkAllLocalNonUrgentNotifications();if(n.length>0)return this.buildPollResult({directMessages:n,groupMessages:[]});let i=!1,s=0;for(;;){if(!i){const p=this.agentClient.getStatusReporter();if(p){const k=p.getCurrentStatus();k.status==="tool_using"&&k.actionDetail!=null&&k.actionDetail.includes("poll_message")||p.reportToolUse("poll_message",{}),i=!0}}const a=await this.messageBuffer.poll(ee);if(a.directMessages.length>0||a.groupMessages.length>0)return this.buildPollResult(a);if(o.debug("[McpServer] poll \u8D85\u65F6\uFF0C\u68C0\u67E5\u6D4F\u89C8\u5668\u76D1\u542C\u5668\u4E8B\u4EF6"),await this.hydrateBrowserListenerEvents()){const p=this.messageBuffer.get();if(p.directMessages.length>0||p.groupMessages.length>0)return o.info("[McpServer] \u76D1\u542C\u5668\u4E8B\u4EF6\u5DF2\u6CE8\u5165\uFF0Cpoll_message \u8FD4\u56DE %d \u6761\u79C1\u4FE1",p.directMessages.length),this.buildPollResult(p)}if(s++,s<te)continue;s=0,await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0});const m=await this.buildTodoReminderNotification(),u=await this.buildBackgroundTaskNotification(),h=this.messageBuffer.get();if(h.directMessages.length>0||h.groupMessages.length>0)return u&&h.directMessages.unshift(u),m&&h.directMessages.unshift(m),this.buildPollResult(h);if(m||u)return this.buildPollResult({directMessages:[m,u].filter(Boolean),groupMessages:[]});const d=await this.checkAllLocalNonUrgentNotifications(!1);if(d.length>0)return this.buildPollResult({directMessages:d,groupMessages:[]})}}async checkAllLocalNonUrgentNotifications(e=!0){const t=[],r=await this.buildMemoryRecurrenceNotification();if(r&&t.push(r),e){const i=await this.buildMemoryFileChangeNotification();i&&t.push(i)}const n=await this.buildMemoryCleanupNotification();return n&&t.push(n),t}buildPollResult(e,t=!0){return{directMessages:e.directMessages,groupMessages:e.groupMessages,fromBuffer:t,systemMessages:[]}}parsePositiveIntegerEnv(e,t){const r=process.env[e];if(!r||!r.trim())return t;const n=Number.parseInt(r,10);return!Number.isFinite(n)||n<=0?(o.warn(`[McpServer] ${e} \u65E0\u6548\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C ${t}ms`),t):n}sleep(e){return e<=0?Promise.resolve():new Promise(t=>setTimeout(t,e))}async buildMemoryCleanupNotification(){try{const e=await this.memoryStore.getStats(),t=Object.entries(e.by_type).filter(([,a])=>a.listable&&a.exceeded),r=Object.entries(e.by_type).flatMap(([a,l])=>l.listable?Object.entries(l.by_domain).filter(([,m])=>m.exceeded).map(([m,u])=>({memoryType:a,domain:m,domainStats:u})):[]);if(t.length===0&&r.length===0)return null;const n=t.map(([a,l])=>`${a} \u5F53\u524D ${l.domainCount} \u4E2A URI domain \u7C7B\u578B\uFF0C\u5EFA\u8BAE\u4E0A\u9650 ${l.limit} \u4E2A\uFF0C\u786C\u4E0A\u9650 ${l.hardLimit} \u4E2A`),i=r.map(({memoryType:a,domain:l,domainStats:m})=>`${a}/${l} \u7C7B\u578B\u4E0B\u5F53\u524D ${m.count} \u6761 memory\uFF0C\u5EFA\u8BAE\u4E0A\u9650 ${m.limit} \u6761\uFF0C\u786C\u4E0A\u9650 ${m.hardLimit} \u6761`),s=["\u8BF7\u8C03\u7528 get_memory_stats \u67E5\u770B\u5BB9\u91CF\u8BE6\u60C5","\u8C03\u7528 list_memories \u6309\u7C7B\u578B\u67E5\u770B\u8D85\u9650\u8BB0\u5FC6","\u5220\u9664\u4E0D\u518D\u9700\u8981\u7684 recently_memory","\u66F4\u65B0 agent_memory \u7EED\u671F\uFF08TTL \xD72\uFF09\u6216\u5220\u9664\u8FC7\u671F\u9879"].join("\uFF1B");return{msgId:`memory-cleanup-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:this.getCurrentAgentId()||void 0,content:`\u672C\u5730 memory \u5DF2\u8D85\u8FC7\u5EFA\u8BAE\u5BB9\u91CF\uFF0C\u9700\u8981\u6574\u7406\u3002${[...n,...i].join("\uFF1B")}\u3002${s}\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u672C\u5730 memory \u5BB9\u91CF\u6574\u7406\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u4F18\u5148\u6574\u7406\u5DF2\u8D85\u8FC7\u5EFA\u8BAE\u4E0A\u9650\u7684\u77ED\u671F/\u8FD1\u671F memory \u7C7B\u578B\uFF0C\u6216\u5177\u4F53\u7C7B\u578B\u4E0B\u7684 memory \u6761\u76EE\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_cleanup",uris:r.map(a=>`${a.memoryType}/${a.domain}`)}}}catch(e){return o.warn("[McpServer] \u6784\u5EFA\u672C\u5730 memory \u6574\u7406\u63D0\u9192\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async buildMemoryRecurrenceNotification(){try{const e=await this.memoryStore.listExpiredRecentlyMemories();if(e.length===0)return null;const t=this.getCurrentAgentId();if(!t)return null;const r=e.filter(i=>i.memory_status==="recalling"),n=e.filter(i=>!r.includes(i));if(r.length>0){const i=r.slice(0,5).map(s=>s.uri).join(`
|
|
8
8
|
`);return{msgId:`memory-recurrence-pending-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:t,content:`\u5B58\u5728\u5F85\u758F\u7406\u7684\u5373\u5C06\u9057\u5FD8\u8BB0\u5FC6\uFF08\u5171 ${r.length} \u6761\uFF09\uFF0C\u8BF7\u901A\u8FC7 memory_recurrence \u83B7\u53D6\u8FDB\u884C\u6574\u7406\u3002
|
|
9
9
|
|
|
10
10
|
\u5F85\u758F\u7406\u8BB0\u5FC6\uFF1A
|
|
@@ -14,36 +14,37 @@ ${i}${r.length>5?`
|
|
|
14
14
|
|
|
15
15
|
\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\uFF1A
|
|
16
16
|
${i}${n.length>5?`
|
|
17
|
-
...\u53CA\u5176\u4ED6 ${n.length-5} \u6761`:""}`,requireReply:!1,prompt:`\u8FD9\u662F\u4E00\u6761\u8FD1\u671F\u8BB0\u5FC6\u8FC7\u671F\u63D0\u9192\uFF0C\u5171\u6709 ${n.length} \u6761\u8BB0\u5FC6\u5373\u5C06\u9057\u5FD8\uFF0C\u8BF7\u8C03\u7528 memory_recurrence \u5206\u6279\u6B21\u83B7\u53D6\u5E76\u51B3\u5B9A\u5904\u7406\u65B9\u5F0F\u3002`,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:n.map(s=>s.uri)}}}return null}catch(e){return
|
|
17
|
+
...\u53CA\u5176\u4ED6 ${n.length-5} \u6761`:""}`,requireReply:!1,prompt:`\u8FD9\u662F\u4E00\u6761\u8FD1\u671F\u8BB0\u5FC6\u8FC7\u671F\u63D0\u9192\uFF0C\u5171\u6709 ${n.length} \u6761\u8BB0\u5FC6\u5373\u5C06\u9057\u5FD8\uFF0C\u8BF7\u8C03\u7528 memory_recurrence \u5206\u6279\u6B21\u83B7\u53D6\u5E76\u51B3\u5B9A\u5904\u7406\u65B9\u5F0F\u3002`,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:n.map(s=>s.uri)}}}return null}catch(e){return o.warn("[McpServer] \u6784\u5EFA\u8FD1\u671F\u8BB0\u5FC6\u8FC7\u671F\u56DE\u5FC6\u901A\u77E5\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async buildMemoryFileChangeNotification(){if(o.info(`[McpServer] buildMemoryFileChangeNotification \u88AB\u8C03\u7528: hasRoleName=${this.hasRoleName}, agentId=${this.getCurrentAgentId()??"(\u7A7A)"}`),!this.hasRoleName)return o.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u8DF3\u8FC7: hasRoleName=false"),null;try{const e=this.getCurrentAgentId();if(!e)return o.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u8DF3\u8FC7: agentId \u4E3A\u7A7A"),null;const t=await this.memoryStore.listByType("project_memory"),r=t.filter(c=>c.memory_status==="updating"&&c.updating_agent_id===e);if(r.length>0){this.startLeaseRenewal();const c=await this.checkMemoriesFileChanges(r);if(c.length>0){for(const S of c){const T=r.find(C=>C.id===S.memoryId);T&&await this.memoryStore.updateMemory({uri:T.uri,memory_type:"project_memory",memory_status:"updating",updating_agent_id:e,file_md5_map:T.file_md5_map,last_check_time:T.last_check_time,bound_files:T.bound_files})}const y=Math.min(c.length,5),w=c.slice(0,5).map(S=>`- ${S.memoryUri} (\u6587\u4EF6: ${S.changedFiles.join(", ")})`).join(`
|
|
18
18
|
`);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B ${y} \u4E2A\u5904\u4E8E\u66F4\u65B0\u4E2D\u7684 Memory \u7ED1\u5B9A\u7684\u6587\u4EF6\u518D\u6B21\u53D8\u66F4\uFF0C\u5DF2\u66F4\u65B0 MD5 \u8BB0\u5F55\uFF1A
|
|
19
19
|
|
|
20
20
|
${w}
|
|
21
21
|
|
|
22
|
-
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u6700\u65B0\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u6807\u8BB0\u4E3A\u66F4\u65B0\u4E2D\u7684\u8BB0\u5FC6\u7ED1\u5B9A\u7684\u6587\u4EF6\u53C8\u6709\u65B0\u53D8\u66F4\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:c.slice(0,5).map(S=>S.memoryUri),fileChanges:c.slice(0,5).map(S=>{const T=r.find(
|
|
23
|
-
`);return
|
|
22
|
+
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u6700\u65B0\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u6807\u8BB0\u4E3A\u66F4\u65B0\u4E2D\u7684\u8BB0\u5FC6\u7ED1\u5B9A\u7684\u6587\u4EF6\u53C8\u6709\u65B0\u53D8\u66F4\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:c.slice(0,5).map(S=>S.memoryUri),fileChanges:c.slice(0,5).map(S=>{const T=r.find(C=>C.id===S.memoryId);return{uri:S.memoryUri,boundFiles:T?.bound_files??[],fileStatuses:S.fileStatuses,changedFiles:S.changedFiles}})}}}const g=r,b=new Map;for(const y of g.slice(0,5))b.set(y.uri,await this.computeBoundFileStatuses(y));const M=g.filter(y=>{const w=b.get(y.uri);return!w||w.length===0?!1:w.some(S=>S.status==="content_changed"||S.status==="not_found")}),_=g.filter(y=>!M.includes(y));if(_.length>0){o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: ${_.length} \u4E2A updating memory \u7ED1\u5B9A\u6587\u4EF6\u65E0\u771F\u5B9E\u53D8\u66F4\uFF0C\u81EA\u52A8\u91CA\u653E: ${_.map(y=>y.uri).join(", ")}`);for(const y of _)try{await this.memoryStore.releaseMemoryClaim(y.uri),this.activeLeaseUris.delete(y.uri),await this.memoryStore.updateMemory({uri:y.uri,memory_type:"project_memory"})}catch(w){o.info(`[McpServer] \u81EA\u52A8\u91CA\u653E stale updating memory \u5931\u8D25: ${y.uri}`,w)}this.activeLeaseUris.size===0&&this.stopLeaseRenewal()}if(M.length===0)return o.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: \u6240\u6709 updating memory \u7ED1\u5B9A\u6587\u4EF6\u5747\u65E0\u771F\u5B9E\u53D8\u66F4\uFF0C\u5DF2\u81EA\u52A8\u91CA\u653E\uFF0C\u8DF3\u8FC7\u59D4\u6258"),null;const $=M.slice(0,5).map(y=>`- ${y.uri} (\u6587\u4EF6: ${y.bound_files?.join(", ")||"\u672A\u77E5"})`).join(`
|
|
23
|
+
`);return o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: ${M.length}/${r.length} \u4E2A updating memory \u6709\u771F\u5B9E\u6587\u4EF6\u53D8\u66F4\uFF0C\u89E6\u53D1\u59D4\u6258`),{msgId:`memory-update-pending-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u60A8\u6709\u4EE5\u4E0B Memory \u6B63\u5904\u4E8E\u66F4\u65B0\u4E2D\u72B6\u6001\u4F46\u5C1A\u672A\u5B8C\u6210\u66F4\u65B0\uFF1A
|
|
24
24
|
|
|
25
|
-
${
|
|
25
|
+
${$}
|
|
26
26
|
|
|
27
|
-
\u8BF7\u7EE7\u7EED\u5B8C\u6210\u66F4\u65B0\u5DE5\u4F5C\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u66F4\u65B0\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u5DF2\u6807\u8BB0\u8FD9\u4E9B\u8BB0\u5FC6\u4E3A\u66F4\u65B0\u4E2D\uFF0C\u8BF7\u7EE7\u7EED\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:M.slice(0,5).map(y=>y.uri),fileChanges:M.slice(0,5).map(y=>({uri:y.uri,boundFiles:y.bound_files??[],fileStatuses:b.get(y.uri)??[]}))}}}const n=await this.memoryStore.listByType("recently_memory"),i=await this.memoryStore.listByType("agent_memory"),s=[...n,...i],
|
|
27
|
+
\u8BF7\u7EE7\u7EED\u5B8C\u6210\u66F4\u65B0\u5DE5\u4F5C\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u66F4\u65B0\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u5DF2\u6807\u8BB0\u8FD9\u4E9B\u8BB0\u5FC6\u4E3A\u66F4\u65B0\u4E2D\uFF0C\u8BF7\u7EE7\u7EED\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:M.slice(0,5).map(y=>y.uri),fileChanges:M.slice(0,5).map(y=>({uri:y.uri,boundFiles:y.bound_files??[],fileStatuses:b.get(y.uri)??[]}))}}}const n=await this.memoryStore.listByType("recently_memory"),i=await this.memoryStore.listByType("agent_memory"),s=[...n,...i],a=s.filter(c=>c.bound_files&&c.bound_files.length>0);o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: recently=${n.length}, agent=${i.length}, \u6709\u7ED1\u5B9A\u6587\u4EF6=${a.length}/${s.length}\u3002\u8BE6\u60C5: ${s.map(c=>`${c.uri}(bound=${c.bound_files?.length??0},md5=${Object.keys(c.file_md5_map??{}).length})`).join("; ")||"\u65E0 memory"}`);const l=new Map;for(const c of s)c.bound_files&&c.bound_files.length>0&&l.set(c.id,JSON.stringify(c.file_md5_map??{}));const m=await this.checkMemoriesFileChanges(s),u=m;o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: \u68C0\u6D4B\u5230\u53D8\u66F4=${m.length}`+(m.length>0?` \u53D8\u66F4\u9879: ${m.map(c=>`${c.memoryUri}(${c.changedFiles.join(",")})`).join("; ")}`:""));const h=s.filter(c=>{if(!c.bound_files||c.bound_files.length===0||m.some(M=>M.memoryId===c.id))return!1;const g=l.get(c.id),b=JSON.stringify(c.file_md5_map??{});return g!==b});if(h.length>0){o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: \u56DE\u5199 ${h.length} \u4E2A listable memory \u7684\u4FEE\u6B63 MD5 map`);for(const c of h)try{await this.memoryStore.updateMemory({uri:c.uri,memory_type:c.memory_type,file_md5_map:c.file_md5_map,last_check_time:c.last_check_time})}catch(g){o.info(`[McpServer] \u56DE\u5199 listable MD5 map \u5931\u8D25: ${c.uri}`,g)}}if(u.length>0){const c=u.map(g=>`- ${g.memoryUri} (\u6587\u4EF6: ${g.changedFiles.join(", ")})`).join(`
|
|
28
28
|
`);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B Memory \u7ED1\u5B9A\u7684\u6587\u4EF6\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u53CA\u65F6\u66F4\u65B0\uFF1A
|
|
29
29
|
|
|
30
30
|
${c}
|
|
31
31
|
|
|
32
|
-
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:u.map(g=>g.memoryUri),fileChanges:u.map(g=>{const b=s.find(M=>M.id===g.memoryId);return{uri:g.memoryUri,boundFiles:b?.bound_files??[],fileStatuses:g.fileStatuses,changedFiles:g.changedFiles}})}}}const d=t.filter(c=>c.bound_files&&c.bound_files.length>0&&(c.memory_status==="normal"||!c.memory_status)&&!this.isClaimCached(c.uri)),p=t.filter(c=>c.bound_files&&c.bound_files.length>0);
|
|
32
|
+
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:u.map(g=>g.memoryUri),fileChanges:u.map(g=>{const b=s.find(M=>M.id===g.memoryId);return{uri:g.memoryUri,boundFiles:b?.bound_files??[],fileStatuses:g.fileStatuses,changedFiles:g.changedFiles}})}}}const d=t.filter(c=>c.bound_files&&c.bound_files.length>0&&(c.memory_status==="normal"||!c.memory_status)&&!this.isClaimCached(c.uri)),p=t.filter(c=>c.bound_files&&c.bound_files.length>0);o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: project_memory \u603B\u6570=${t.length}, \u6709\u7ED1\u5B9A\u6587\u4EF6=${p.length}, \u5F85\u68C0\u6D4B(\u8FC7\u6EE4updating/claimed\u540E)=${d.length}\u3002\u88AB\u8FC7\u6EE4\u539F\u56E0: ${p.length-d.length} \u9879(memory_status=updating \u6216 isClaimCached)`);const k=new Map;for(const c of d)k.set(c.id,JSON.stringify(c.file_md5_map??{}));const R=await this.checkMemoriesFileChanges(d),A=d.filter(c=>{if(R.some(M=>M.memoryId===c.id))return!1;const g=k.get(c.id),b=JSON.stringify(c.file_md5_map??{});return g!==b&&c.file_md5_map&&Object.keys(c.file_md5_map).length>0});if(A.length>0){o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: \u56DE\u5199 ${A.length} \u4E2A memory \u7684\u4FEE\u6B63 MD5 map \u5230\u670D\u52A1\u7AEF\uFF08\u65E0\u57FA\u7EBF/\u952E\u7F3A\u5931\u4FEE\u590D\uFF09`);for(const c of A)try{await this.memoryStore.updateMemory({uri:c.uri,memory_type:"project_memory",file_md5_map:c.file_md5_map,last_check_time:c.last_check_time})}catch(g){o.info(`[McpServer] \u56DE\u5199\u57FA\u7EBF MD5 \u5931\u8D25: ${c.uri}`,g)}}if(o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: \u68C0\u6D4B\u5230\u53D8\u66F4=${R.length}`+(R.length>0?` \u53D8\u66F4\u9879: ${R.map(c=>`${c.memoryUri}(${c.changedFiles.join(",")})`).join("; ")}`:"")),R.length>0){const c=R.slice(0,5),g=c.map(_=>_.memoryUri);let b=!1,M=new Set;try{const _=await this.memoryStore.claimMemoryUpdate(g,v.LEASE_DURATION_MINUTES,"project_memory");M=new Set(_.claimed_uris??[]),b=!0}catch(_){o.info("[McpServer] Server claim API \u4E0D\u53EF\u7528\uFF0C\u964D\u7EA7\u5230\u672C\u5730\u76F4\u63A5\u6807\u8BB0:",_),M=new Set(g)}if(M.size>0){const _=c.filter(y=>M.has(y.memoryUri));for(const y of _){const w=t.find(S=>S.id===y.memoryId);w&&(await this.memoryStore.updateMemory({uri:w.uri,memory_type:"project_memory",memory_status:"updating",updating_agent_id:e,file_md5_map:w.file_md5_map,last_check_time:w.last_check_time,bound_files:w.bound_files}),this.activeLeaseUris.add(w.uri))}b&&M.size>0&&this.startLeaseRenewal();const $=_.map(y=>`- ${y.memoryUri} (\u6587\u4EF6: ${y.changedFiles.join(", ")})`).join(`
|
|
33
33
|
`);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B ${_.length} \u4E2A\u6C38\u4E45\u8BB0\u5FC6\u7ED1\u5B9A\u7684\u6587\u4EF6\u5DF2\u53D8\u66F4\uFF0C\u5DF2\u4E3A\u60A8\u6807\u8BB0\u4E3A\u66F4\u65B0\u4E2D\uFF1A
|
|
34
34
|
|
|
35
|
-
${
|
|
35
|
+
${$}
|
|
36
36
|
|
|
37
|
-
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002\u66F4\u65B0\u5B8C\u6210\u540E\u72B6\u6001\u5C06\u81EA\u52A8\u6062\u590D\u4E3A\u6B63\u5E38\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u5DF2\u81EA\u52A8\u6807\u8BB0\u66F4\u65B0\u72B6\u6001\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:_.map(y=>y.memoryUri),fileChanges:_.map(y=>{const w=t.find(S=>S.id===y.memoryId);return{uri:y.memoryUri,boundFiles:w?.bound_files??[],fileStatuses:y.fileStatuses,changedFiles:y.changedFiles}})}}}}return
|
|
38
|
-
`:"",i=this.formatChangeType(e.changeType),s=new Date(e.triggeredAt).toISOString(),
|
|
37
|
+
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002\u66F4\u65B0\u5B8C\u6210\u540E\u72B6\u6001\u5C06\u81EA\u52A8\u6062\u590D\u4E3A\u6B63\u5E38\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u5DF2\u81EA\u52A8\u6807\u8BB0\u66F4\u65B0\u72B6\u6001\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:_.map(y=>y.memoryUri),fileChanges:_.map(y=>{const w=t.find(S=>S.id===y.memoryId);return{uri:y.memoryUri,boundFiles:w?.bound_files??[],fileStatuses:y.fileStatuses,changedFiles:y.changedFiles}})}}}}return o.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u5B8C\u6210: \u65E0\u53D8\u66F4\u901A\u77E5\u4EA7\u751F"),null}catch(e){return o.warn("[McpServer] \u6784\u5EFA Memory \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u901A\u77E5\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}isClaimCached(e){return this.activeLeaseUris.has(e)}async computeBoundFileStatuses(e){const t=[];if(!e.bound_files||e.bound_files.length===0)return t;const r=e.file_md5_map||{},n=process.env.AWS_WORKSPACE_PATH||process.cwd();for(const i of e.bound_files)try{const s=await this.calculateFileMd5(i);if(s===null){const l=f.resolve(n,i);await this.verifyFileNotExist(l)&&r[i]?t.push({path:i,status:"not_found",serverMd5:r[i]}):t.push({path:i,status:"no_change"});continue}const a=r[i];a?a!==s?t.push({path:i,status:"content_changed",localMd5:s,serverMd5:a}):t.push({path:i,status:"no_change"}):t.push({path:i,status:"no_change"})}catch{t.push({path:i,status:"no_change"})}return t}async checkMemoriesFileChanges(e){const t=[];for(const r of e)if(!(!r.bound_files||r.bound_files.length===0))try{const n=[],i=[],s={},a=r.file_md5_map||{},l=process.env.AWS_WORKSPACE_PATH||process.cwd(),m=Object.keys(a).length===0;let u=!1;for(const h of r.bound_files)try{const d=await this.calculateFileMd5(h);if(d===null){const k=f.resolve(l,h);o.info(`[McpServer] MD5 \u8BA1\u7B97\u8FD4\u56DE null: ${h} -> ${k} (workspace=${l})`);const R=a[h];R?await this.verifyFileNotExist(k)?(i.push({path:h,status:"not_found",serverMd5:R}),n.push(`${h}\uFF08\u6587\u4EF6\u627E\u4E0D\u5230 Server: ${R}\uFF09`),r.bound_files&&(r.bound_files=r.bound_files.filter(c=>c!==h))):(o.info(`[McpServer] \u6587\u4EF6\u5B58\u5728\u4F46 MD5 \u8BA1\u7B97\u5931\u8D25\uFF08\u77AC\u6001\u9519\u8BEF\uFF09\uFF0C\u8DF3\u8FC7: ${h}`),i.push({path:h,status:"no_change"})):i.push({path:h,status:"no_change"});continue}s[h]=d;const p=a[h];p?p!==d?(o.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4: ${h} old=${p} new=${d} (workspace=${l})`),i.push({path:h,status:"content_changed",localMd5:d,serverMd5:p}),n.push(`${h}\uFF08\u5185\u5BB9\u53D8\u66F4 \u672C\u5730: ${d} Server: ${p}\uFF09`)):i.push({path:h,status:"no_change"}):(i.push({path:h,status:"no_change"}),u=!0,o.info(`[McpServer] \u5EFA\u7ACB\u5355\u6587\u4EF6\u57FA\u7EBF: ${h} new=${d} (workspace=${l}, memory=${r.uri})`))}catch(d){o.info(`[McpServer] \u8BA1\u7B97\u6587\u4EF6 MD5 \u5931\u8D25: ${h}`,d),i.push({path:h,status:"no_change"})}if(m&&Object.keys(s).length>0){r.file_md5_map=s,r.last_check_time=new Date().toISOString();continue}u&&Object.keys(s).length>0&&(r.file_md5_map=s,r.last_check_time=new Date().toISOString()),n.length>0&&(t.push({memoryId:r.id,memoryUri:r.uri,fileStatuses:i,changedFiles:n}),r.last_check_time=new Date().toISOString())}catch(n){o.warn(`[McpServer] \u68C0\u67E5 Memory ${r.id} \u6587\u4EF6\u53D8\u66F4\u5931\u8D25:`,n)}return t}async calculateFileMd5(e){try{const{createReadStream:t}=await import("node:fs"),{createHash:r}=await import("node:crypto"),n=process.env.AWS_WORKSPACE_PATH||process.cwd(),i=f.resolve(n,e),s=f.resolve(n),a=f.resolve(i);return!a.startsWith(s+f.sep)&&a!==s?(o.warn(`[McpServer] calculateFileMd5: \u8DEF\u5F84\u9003\u9038\u88AB\u62D2\u7EDD: ${e}`),null):new Promise(l=>{const m=r("md5"),u=t(i);u.on("data",h=>m.update(h)),u.on("end",()=>l(m.digest("hex"))),u.on("error",()=>l(null))})}catch(t){return o.info(`[McpServer] \u8BA1\u7B97\u6587\u4EF6 MD5 \u5931\u8D25: ${e}`,t),null}}async verifyFileNotExist(e){try{return await E.promises.access(e,E.constants.F_OK),!1}catch{return!0}}async handleGetMessage(){const e=this.messageBuffer.size()>0;await this.hydrateUnreadMessagesFromServer();const t=this.messageBuffer.get(),r=t.directMessages.length>0||t.groupMessages.length>0,n=e||!r;return await this.prependMemoryNotification(t,()=>this.buildMemoryRecurrenceNotification())?this.buildPollResult(t,n):await this.prependMemoryNotification(t,()=>this.buildMemoryCleanupNotification())?this.buildPollResult(t,n):this.buildPollResult(t,n)}async hydrateUnreadMessagesFromServer(e={}){try{const t=await this.agentClient.fetchUnreadMessages({blockIfEmpty:e.blockIfEmpty??!1,blockTimeoutMs:e.blockTimeoutMs,includePipelineTasks:e.includePipelineTasks});if(t.directMessages.length===0&&t.groupMessages.length===0)return!0;const r=t.directMessages.filter(i=>this.shouldBufferIncomingMessage(i)),n=t.groupMessages.filter(i=>this.shouldBufferIncomingMessage(i));return r.forEach(i=>{this.messageBuffer.push(i)}),n.forEach(i=>{this.messageBuffer.push(i)}),o.info(`[McpServer] \u5DF2\u8865\u62C9\u672A\u8BFB\u6D88\u606F\uFF0C\u79C1\u804A: ${r.length}\uFF0C\u7FA4\u804A: ${n.length}`),!0}catch(t){return o.error("[McpServer] \u8865\u62C9\u672A\u8BFB\u6D88\u606F\u5931\u8D25: %s, stack=%s",t instanceof Error?t.message:String(t),t instanceof Error?t.stack:"(no stack)"),!1}}async hydrateBrowserListenerEvents(){try{const e=await this.agentClient.fetchBrowserListenerEvents();if(e.length===0)return!1;o.info(`[McpServer] \u62C9\u53D6\u5230 ${e.length} \u6761\u6D4F\u89C8\u5668\u76D1\u542C\u5668\u4E8B\u4EF6\uFF0C\u6CE8\u5165\u6D88\u606F\u7F13\u51B2`);for(const t of e){const r=this.buildListenerEventNotification(t);r&&this.messageBuffer.push(r)}return!0}catch(e){return o.warn("[McpServer] \u62C9\u53D6\u6D4F\u89C8\u5668\u76D1\u542C\u5668\u4E8B\u4EF6\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),!1}}buildListenerEventNotification(e){try{const t=this.getCurrentAgentId(),r=e.browserId&&e.browserId!=="default"?`\u6D4F\u89C8\u5668[${e.browserId}]`:"\u6D4F\u89C8\u5668",n=e.selector?`\u5143\u7D20\u9009\u62E9\u5668: ${e.selector}
|
|
38
|
+
`:"",i=this.formatChangeType(e.changeType),s=new Date(e.triggeredAt).toISOString(),a=`\u3010\u6D4F\u89C8\u5668\u76D1\u542C\u5668\u89E6\u53D1 | ${r} | URL: ${e.url}\u3011
|
|
39
39
|
${n}\u53D8\u5316\u7C7B\u578B: ${i}
|
|
40
40
|
\u89E6\u53D1\u65F6\u95F4: ${s}
|
|
41
|
-
\u63D0\u793A\u8BCD: ${e.prompt}`;return{msgId:`browser-listener-${e.listenerId}-${e.triggeredAt}`,senderId:"MCP-BrowserListener",senderName:"\u6D4F\u89C8\u5668\u76D1\u542C\u5668",receiverId:t||void 0,content:
|
|
41
|
+
\u63D0\u793A\u8BCD: ${e.prompt}`;return{msgId:`browser-listener-${e.listenerId}-${e.triggeredAt}`,senderId:"MCP-BrowserListener",senderName:"\u6D4F\u89C8\u5668\u76D1\u542C\u5668",receiverId:t||void 0,content:a,requireReply:!1,role:"system",timestamp:s}}catch{return null}}formatChangeType(e){switch(e){case"added":return"\u65B0\u589E";case"removed":return"\u5220\u9664";case"changed":return"\u6539\u53D8";case"exists":return"\u5B58\u5728";default:return"\u6539\u53D8"}}async handleGetDmHistory(e){return await this.agentClient.getDmHistory(e.targetUserId,e.limit,e.since)}async handleGetGroupHistory(e){return await this.agentClient.getGroupHistory(e.limit,e.roomId,e.since)}async handleSubmitTaskResult(e){if(!e.nodeId||typeof e.nodeId!="string")throw new Error("nodeId is required");if(!e.outputPayload||typeof e.outputPayload!="string")throw new Error("outputPayload is required");const t=Array.isArray(e.changeLog)?e.changeLog:void 0;return await this.agentClient.submitTaskResult(e.nodeId,e.outputPayload,t)}async handleStartTaskRollback(e){const t={rollbackTaskId:this.getRequiredString(e,"rollbackTaskId")};return await this.agentClient.startTaskRollback(t)}async handleFinishTaskRollback(e){const t={rollbackTaskId:this.getRequiredString(e,"rollbackTaskId"),resultMessage:this.getOptionalString(e,"resultMessage")};return await this.agentClient.finishTaskRollback(t)}async handleRejectTask(e){if(!e.nodeId||typeof e.nodeId!="string")throw new Error("nodeId is required");if(!e.reason||typeof e.reason!="string")throw new Error("reason is required");return await this.agentClient.rejectTask(e.nodeId,e.reason)}async handleTerminateTask(e){if(!e.taskInstanceId||typeof e.taskInstanceId!="string")throw new Error("taskInstanceId is required");if(!e.reason||typeof e.reason!="string")throw new Error("reason is required");return await this.agentClient.terminateTask(e.taskInstanceId,e.reason)}async handleIncomingMessage(e){if(!this.shouldBufferIncomingMessage(e)){!this.isGroupMessage(e)&&this.isPipelineTaskAssignedNotification(e)&&(o.info("[McpServer] \u6536\u5230 pipeline_task_assigned \u5524\u9192\u4FE1\u53F7\uFF0C\u7ACB\u5373\u5524\u9192 poll \u8D70 hydrate \u91CD\u67E5"),this.messageBuffer.wakeUpWaiters());return}o.info(`[McpServer] \u6536\u5230\u6D88\u606F: ${e.content?.substring(0,50)||"(\u65E0\u5185\u5BB9)"}`),this.messageBuffer.push(e),!this.isGroupMessage(e)&&e.force===!0&&this.agentClient.notifyBridgeForceMessage().catch(()=>{})}shouldBufferIncomingMessage(e){return!e||typeof e!="object"?(o.warn("[McpServer] \u5FFD\u7565\u7ED3\u6784\u5F02\u5E38\u7684\u6D88\u606F\uFF0C\u907F\u514D\u7F13\u51B2\u533A\u6C61\u67D3"),!1):this.isSelfSentMessage(e)?(o.info("[McpServer] \u5FFD\u7565\u5F53\u524D Agent \u81EA\u5DF1\u53D1\u9001\u7684\u6D88\u606F\uFF0C\u907F\u514D poll_message/get_message \u56DE\u73AF"),!1):this.isGroupMessage(e)?!0:this.isPipelineTaskAssignedNotification(e)?(o.info("[McpServer] \u5FFD\u7565\u6301\u4E45\u5316 pipeline_task_assigned \u79C1\u4FE1\uFF0C\u4EC5\u4F5C\u4E3A poll_message \u5524\u9192\u4FE1\u53F7\u5904\u7406"),!1):!0}isGroupMessage(e){return!!e&&typeof e=="object"&&typeof e.msgId=="number"}isPipelineTaskAssignedNotification(e){const t=typeof e.content=="string"?e.content:"",r=typeof e.prompt=="string"?e.prompt:"";return t.includes("pipeline_task_assigned")||r.includes("pipeline_task_assigned")}isSelfSentMessage(e){if(!e||typeof e!="object")return!1;const t=this.getCurrentAgentId();return!!t&&typeof e.senderId=="string"&&e.senderId===t}getCurrentAgentId(){const e=this.agentClient;return typeof e.getState!="function"?null:e.getState().agentId??null}sanitizeAgentIdForTodoFile(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}async buildTodoReminderNotification(){try{const e=this.getCurrentAgentId();if(!e)return null;const t=f.join(j.homedir(),".acode","todos",`${this.sanitizeAgentIdForTodoFile(e)}.json`);let r;try{r=await E.promises.readFile(t,"utf-8")}catch(l){if(l?.code==="ENOENT")return null;throw l}const n=JSON.parse(r),s=(Array.isArray(n.todos)?n.todos:[]).filter(l=>l&&typeof l=="object"&&typeof l.content=="string"&&(l.status==="pending"||l.status==="in_progress"||l.status==="cancelled"));if(s.length===0)return this.lastTodoFingerprint=null,null;const a=s.map(l=>`${l.content}|${l.status}|${l.priority??""}`).join(`
|
|
42
|
+
`);return a===this.lastTodoFingerprint?null:(this.lastTodoFingerprint=a,{msgId:`todo-reminder-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`Todo \u5217\u8868\u5B58\u5728 ${s.length} \u9879\u672A\u5B8C\u6210\uFF0C\u8BF7\u8C03\u7528 todo_read \u67E5\u770B\u5E76\u5904\u7406\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761 Todo \u672A\u5B8C\u6210\u9879\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u8BC4\u4F30\u662F\u5426\u9700\u8981\u7EE7\u7EED\u5904\u7406\u672A\u5B8C\u6210\u7684 Todo\uFF0C\u53EF\u8C03\u7528 todo_read \u83B7\u53D6\u5B8C\u6574\u5217\u8868\u3002",timestamp:new Date().toISOString()})}catch(e){return o.warn("[McpServer] \u6784\u5EFA Todo \u63D0\u9192\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async buildBackgroundTaskNotification(){try{const e=this.getCurrentAgentId();if(!e)return null;const t=f.join(j.homedir(),".acode","bg-tasks",this.sanitizeAgentIdForTodoFile(e),"state.json");let r;try{r=await E.promises.readFile(t,"utf-8")}catch(u){if(u?.code==="ENOENT")return null;throw u}const n=JSON.parse(r),i=Array.isArray(n.tasks)?n.tasks:[],s=i.filter(u=>u&&typeof u=="object"&&typeof u.taskId=="string"&&u.status!=="running"&&!u.notified);if(s.length===0)return null;const a=s.slice(0,5),l=a.map(u=>`[bg:${u.taskId}] ${u.status} (exitCode=${u.exitCode??"?"}) ${u.commandPreview??""}
|
|
42
43
|
lines=${u.outputLineCount??0} bytes=${u.outputBytes??0}
|
|
43
44
|
Use read_background_output(task_id="${u.taskId}", mode="tail", lines=50) for full output.`).join(`
|
|
44
45
|
|
|
45
|
-
`),m=s.length>
|
|
46
|
+
`),m=s.length>a.length?`
|
|
46
47
|
|
|
47
|
-
\uFF08\u53E6\u6709 ${s.length-
|
|
48
|
-
${l}${m}`,requireReply:!1,prompt:"\u8FD9\u662F\u540E\u53F0\u547D\u4EE4\u5B8C\u6210\u901A\u77E5\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u8BC4\u4F30\u662F\u5426\u9700\u8981\u8BFB\u53D6\u547D\u4EE4\u8F93\u51FA\uFF08read_background_output\uFF09\u6216\u7EE7\u7EED\u540E\u7EED\u5DE5\u4F5C\u3002",timestamp:new Date().toISOString()}}catch(e){return
|
|
48
|
+
\uFF08\u53E6\u6709 ${s.length-a.length} \u6761\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u7528 ListBackgroundCommands \u67E5\u770B\uFF09`:"";return await E.promises.writeFile(t,JSON.stringify({...n,updatedAt:Date.now(),tasks:i.map(u=>s.some(h=>h.taskId===u.taskId)?{...u,notified:!0}:u)},null,2),"utf-8").catch(u=>{o.warn("[McpServer] \u6807\u8BB0\u540E\u53F0\u4EFB\u52A1\u5DF2\u901A\u77E5\u5931\u8D25: %s",u?.message??u)}),{msgId:`bg-notif-${Date.now()}`,senderId:"MCP-System",senderName:"ACode Background",receiverId:e,content:`[Background commands completed]
|
|
49
|
+
${l}${m}`,requireReply:!1,prompt:"\u8FD9\u662F\u540E\u53F0\u547D\u4EE4\u5B8C\u6210\u901A\u77E5\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u8BC4\u4F30\u662F\u5426\u9700\u8981\u8BFB\u53D6\u547D\u4EE4\u8F93\u51FA\uFF08read_background_output\uFF09\u6216\u7EE7\u7EED\u540E\u7EED\u5DE5\u4F5C\u3002",timestamp:new Date().toISOString()}}catch(e){return o.warn("[McpServer] \u6784\u5EFA\u540E\u53F0\u547D\u4EE4\u5B8C\u6210\u901A\u77E5\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async start(){const e=new x;await this.server.connect(e),await this.agentClient.initialize(),o.info("[McpServer] MCP Server \u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u8FDE\u63A5..."),o.info(`[McpServer] \u53EF\u7528\u5DE5\u5177: ${O.map(t=>t.name).join(", ")}`),o.info(`[McpServer] \u5DE5\u4F5C\u76EE\u5F55: ${process.env.AWS_WORKSPACE_PATH||process.cwd()}`),L&&o.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\u5DF2\u542F\u7528\uFF0C\u6CE8\u518C\u5931\u8D25\u65F6\u5C06\u4EE5\u79BB\u7EBF\u6A21\u5F0F\u7EE7\u7EED\u8FD0\u884C"),this.autoRegisterInBackground(),o.info("[McpServer] MCP Server \u5DF2\u5C31\u7EEA\uFF0C\u5DE5\u5177\u5DF2\u53EF\u7528")}startLeaseRenewal(){this.leaseRenewalTimer||(this.leaseRenewalTimer=setInterval(async()=>{try{if(!this.getCurrentAgentId())return;(await this.memoryStore.renewMemoryLease(v.LEASE_DURATION_MINUTES)).renewed===0&&(o.warn("[McpServer] \u79DF\u7EA6\u5DF2\u4E22\u5931\uFF08renewed=0\uFF09\uFF0C\u505C\u6B62\u7EED\u671F\u5B9A\u65F6\u5668"),this.stopLeaseRenewal())}catch(e){o.warn("[McpServer] \u7EED\u671F\u79DF\u7EA6\u5931\u8D25: %s",e instanceof Error?e.message:String(e))}},v.LEASE_RENEW_INTERVAL_MS),o.info("[McpServer] \u79DF\u7EA6\u7EED\u671F\u5B9A\u65F6\u5668\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: 60s"))}stopLeaseRenewal(){this.leaseRenewalTimer&&(clearInterval(this.leaseRenewalTimer),this.leaseRenewalTimer=null,o.info("[McpServer] \u79DF\u7EA6\u7EED\u671F\u5B9A\u65F6\u5668\u5DF2\u505C\u6B62"))}stop(){this.stopLeaseRenewal(),this.getCurrentAgentId()&&this.memoryStore.releaseAllMemoryClaims().catch(t=>o.warn("[McpServer] \u91CA\u653E claim \u5931\u8D25: %s",t instanceof Error?t.message:String(t))),this.agentClient.stop(),o.info("[McpServer] MCP Server \u5DF2\u505C\u6B62")}async autoRegister(){const e=this.agentClient.getAgentConfig();o.info(`[McpServer] \u89D2\u8272: "${e.roleName}"`),o.info(`[McpServer] AgentID: "${e.agentId||"(\u81EA\u52A8\u5206\u914D)"}"`),o.info("[McpServer] \u6B63\u5728\u8BF7\u6C42\u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C...");let t=0;for(;;)try{const r=await this.agentClient.register();o.info(`[McpServer] \u6CE8\u518C\u6210\u529F: ${r.displayName} (${r.agentId})`),o.info(`[McpServer] \u72B6\u6001: ${r.status}`),this.startListeningInternal(),o.info("[McpServer] \u6D88\u606F\u76D1\u542C\u5DF2\u81EA\u52A8\u542F\u52A8");const n=this.agentClient.getStatusReporter();n&&n.reportWaitingInput(),await this.reportMemoryStatsToServer(),this.maybeSyncPermanentMemoryToServer();return}catch(r){const n=r instanceof Error?r.message:String(r);if(t++,P>0&&t>=P){if(L){o.warn(`[McpServer] \u6CE8\u518C\u8D85\u8FC7\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${P}\u6B21)\uFF0C\u8FDB\u5165\u964D\u7EA7\u6A21\u5F0F`),this.agentClient.enterDegradedMode(),this.startListeningInternal(),o.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6D88\u606F\u76D1\u542C\u5DF2\u542F\u52A8"),this.agentClient.retryRegistrationInBackground(J,K);const i=this.agentClient.getStatusReporter();i&&i.reportWaitingInput(),await this.reportMemoryStatsToServer();return}throw new Error(`\u6CE8\u518C\u8D85\u8FC7\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${P}\u6B21)\uFF0C\u653E\u5F03\u542F\u52A8: ${n}`)}o.warn(`[McpServer] \u6CE8\u518C\u5931\u8D25\uFF0C\u7B49\u5F85\u91CD\u8BD5... (${t}/${P}): ${n}`),await this.delay(Y)}}autoRegisterInBackground(){this.autoRegister().catch(e=>{o.error("[McpServer] \u540E\u53F0\u6CE8\u518C\u5F02\u5E38: %s, stack=%s",e instanceof Error?e.message:String(e),e instanceof Error?e.stack:"(no stack)")})}startListeningInternal(){o.info("[McpServer] startListeningInternal \u88AB\u8C03\u7528");const e=t=>{o.info(`[McpServer] profileHandler \u88AB\u8C03\u7528\uFF0CdisplayName: ${t.displayName}`),this.messageBuffer.setProfileMessage(t),this.profileSynced=!0,this.syncRoleMemoryStore(t),this.maybeSyncPermanentMemoryToServer()};if(this.agentClient.isInDegradedMode()){o.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u8DF3\u8FC7 WebSocket \u8FDE\u63A5\uFF0C\u4EC5\u542F\u52A8\u672C\u5730\u6D88\u606F\u76D1\u542C"),this.agentClient.start(this.handleIncomingMessage.bind(this),{pollMode:!1,profileHandler:e}),this.activelySyncProfile();return}this.agentClient.start(this.handleIncomingMessage.bind(this),{pollMode:!1,profileHandler:e}),this.activelySyncProfile()}activelySyncProfile(){this.profileSynced||this.agentClient.getProfile().then(e=>{this.profileSynced||(this.profileSynced=!0,o.info(`[McpServer] \u4E3B\u52A8\u83B7\u53D6 profile \u6210\u529F: displayName=${e.displayName}, roleName="${e.roleName??"(\u7A7A)"}"`),this.messageBuffer.setProfileMessage(e),this.syncRoleMemoryStore(e),this.maybeSyncPermanentMemoryToServer())}).catch(e=>{o.warn("[McpServer] \u4E3B\u52A8\u83B7\u53D6 profile \u5931\u8D25: %s",e instanceof Error?e.message:String(e))})}delay(e){return new Promise(t=>setTimeout(t,e))}}export{v as McpServer};
|
|
49
50
|
|
|
@@ -56,6 +56,15 @@ async function withScopedMemoryServers(callback) {
|
|
|
56
56
|
function getHandleToolCall(server) {
|
|
57
57
|
return Reflect.get(server, "handleToolCall");
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* 测试辅助:访问 McpServer 的私有 messageBuffer(WebSocket 本地消息队列),
|
|
61
|
+
* 用于模拟外部消息到达后唤醒阻塞中的 poll_message。
|
|
62
|
+
* 注意:直接依赖私有字段名,若 mcp-server.ts 重命名该字段需同步修改本函数,
|
|
63
|
+
* 避免测试在字段改名后静默失效(Reflect 访问不会报编译错误)。
|
|
64
|
+
*/
|
|
65
|
+
function getMessageBuffer(server) {
|
|
66
|
+
return Reflect.get(server, "messageBuffer");
|
|
67
|
+
}
|
|
59
68
|
function attachMemoryStatsReporter(server, reportedStats) {
|
|
60
69
|
const existingClient = Reflect.get(server, "agentClient");
|
|
61
70
|
Reflect.set(server, "agentClient", {
|
|
@@ -167,7 +176,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
167
176
|
});
|
|
168
177
|
// Simulate a WebSocket message arriving after hydration returns empty.
|
|
169
178
|
// poll_message must keep blocking on the local buffer until the message arrives.
|
|
170
|
-
const buffer =
|
|
179
|
+
const buffer = getMessageBuffer(server);
|
|
171
180
|
setTimeout(() => {
|
|
172
181
|
buffer.push(directMessage);
|
|
173
182
|
}, 50);
|
|
@@ -202,7 +211,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
202
211
|
},
|
|
203
212
|
});
|
|
204
213
|
// Simulate WebSocket message arriving after hydration fails
|
|
205
|
-
const buffer =
|
|
214
|
+
const buffer = getMessageBuffer(server);
|
|
206
215
|
setTimeout(() => {
|
|
207
216
|
buffer.push(directMessage);
|
|
208
217
|
}, 50);
|
|
@@ -233,7 +242,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
233
242
|
directMessages: [],
|
|
234
243
|
groupMessages: [groupMessage],
|
|
235
244
|
});
|
|
236
|
-
const messageBuffer =
|
|
245
|
+
const messageBuffer = getMessageBuffer(server);
|
|
237
246
|
messageBuffer.setProfileMessage({
|
|
238
247
|
id: "agent-1",
|
|
239
248
|
displayName: "Agent One",
|
|
@@ -469,7 +478,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
469
478
|
return { directMessages: [taskNotification], groupMessages: [] };
|
|
470
479
|
},
|
|
471
480
|
});
|
|
472
|
-
const messageBuffer =
|
|
481
|
+
const messageBuffer = getMessageBuffer(server);
|
|
473
482
|
messageBuffer.push(cachedMessage);
|
|
474
483
|
const handleToolCall = getHandleToolCall(server);
|
|
475
484
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
@@ -501,7 +510,7 @@ describe("McpServer unread backlog hydration", () => {
|
|
|
501
510
|
},
|
|
502
511
|
});
|
|
503
512
|
// Simulate WebSocket message arriving after backend returns empty
|
|
504
|
-
const buffer =
|
|
513
|
+
const buffer = getMessageBuffer(server);
|
|
505
514
|
setTimeout(() => {
|
|
506
515
|
buffer.push(delayedMessage);
|
|
507
516
|
}, 50);
|
|
@@ -1129,7 +1138,7 @@ describe("McpServer memory tools", () => {
|
|
|
1129
1138
|
});
|
|
1130
1139
|
}
|
|
1131
1140
|
// Override poll to resolve immediately with empty (simulating timeout without waiting 30s)
|
|
1132
|
-
const buffer =
|
|
1141
|
+
const buffer = getMessageBuffer(server);
|
|
1133
1142
|
let pollCallCount = 0;
|
|
1134
1143
|
buffer.poll = ((_timeoutMs) => {
|
|
1135
1144
|
pollCallCount++;
|
|
@@ -1737,7 +1746,7 @@ describe("McpServer incoming group message filtering", () => {
|
|
|
1737
1746
|
};
|
|
1738
1747
|
const handleIncomingMessage = Reflect.get(server, "handleIncomingMessage");
|
|
1739
1748
|
await handleIncomingMessage.call(server, selfGroupMessage);
|
|
1740
|
-
const messageBuffer =
|
|
1749
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1741
1750
|
const drained = messageBuffer.get();
|
|
1742
1751
|
assert.deepEqual(drained, {
|
|
1743
1752
|
directMessages: [],
|
|
@@ -1767,7 +1776,7 @@ describe("McpServer incoming group message filtering", () => {
|
|
|
1767
1776
|
};
|
|
1768
1777
|
const handleIncomingMessage = Reflect.get(server, "handleIncomingMessage");
|
|
1769
1778
|
await handleIncomingMessage.call(server, taskAssignedMessage);
|
|
1770
|
-
const messageBuffer =
|
|
1779
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1771
1780
|
const drained = messageBuffer.get();
|
|
1772
1781
|
assert.deepEqual(drained, {
|
|
1773
1782
|
directMessages: [],
|
|
@@ -2030,7 +2039,7 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2030
2039
|
try {
|
|
2031
2040
|
const server = createServerForTodo();
|
|
2032
2041
|
// 先把消息放入 buffer,模拟已有未读消息
|
|
2033
|
-
const buffer =
|
|
2042
|
+
const buffer = getMessageBuffer(server);
|
|
2034
2043
|
const msg = makeDirectMessage("msg-before-poll");
|
|
2035
2044
|
buffer.push(msg);
|
|
2036
2045
|
const handleToolCall = getHandleToolCall(server);
|
|
@@ -2051,7 +2060,7 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2051
2060
|
]);
|
|
2052
2061
|
try {
|
|
2053
2062
|
const server = createServerForTodo();
|
|
2054
|
-
const buffer =
|
|
2063
|
+
const buffer = getMessageBuffer(server);
|
|
2055
2064
|
const wake = makeDirectMessage("wake-completed");
|
|
2056
2065
|
setTimeout(() => buffer.push(wake), 50);
|
|
2057
2066
|
const handleToolCall = getHandleToolCall(server);
|
|
@@ -2064,43 +2073,48 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2064
2073
|
await cleanupTodoFile();
|
|
2065
2074
|
}
|
|
2066
2075
|
});
|
|
2067
|
-
it("
|
|
2076
|
+
it("does not re-inject todo reminder on subsequent poll_message when todo fingerprint unchanged", async () => {
|
|
2068
2077
|
await writeTodoFile([
|
|
2069
2078
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2070
2079
|
]);
|
|
2071
2080
|
try {
|
|
2072
2081
|
const server = createServerForTodo();
|
|
2073
2082
|
const handleToolCall = getHandleToolCall(server);
|
|
2074
|
-
// 第一次 poll:无未读消息 -> todo 单独返回(组合2
|
|
2083
|
+
// 第一次 poll:无未读消息 -> todo 单独返回(组合2),指纹被记录
|
|
2075
2084
|
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2076
2085
|
assert.equal(result1.directMessages.length, 1);
|
|
2077
2086
|
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2078
|
-
// 第二次 poll
|
|
2087
|
+
// 第二次 poll(待办指纹未变):不注入 todo 提醒,进入 while 阻塞等待真实消息
|
|
2088
|
+
// 模拟外部消息到达后唤醒
|
|
2089
|
+
const buffer = getMessageBuffer(server);
|
|
2090
|
+
const wake = makeDirectMessage("wake-after-dedup");
|
|
2091
|
+
setTimeout(() => buffer.push(wake), 50);
|
|
2079
2092
|
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2093
|
+
// 指纹去重生效:只返回真实消息,不再附带 todo 提醒
|
|
2080
2094
|
assert.equal(result2.directMessages.length, 1);
|
|
2081
|
-
assert.
|
|
2095
|
+
assert.equal(result2.directMessages[0].msgId, "wake-after-dedup");
|
|
2082
2096
|
}
|
|
2083
2097
|
finally {
|
|
2084
2098
|
await cleanupTodoFile();
|
|
2085
2099
|
}
|
|
2086
2100
|
});
|
|
2087
|
-
it("
|
|
2101
|
+
it("re-injects todo reminder when todo fingerprint changes (new item added)", async () => {
|
|
2088
2102
|
await writeTodoFile([
|
|
2089
2103
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2090
2104
|
]);
|
|
2091
2105
|
try {
|
|
2092
2106
|
const server = createServerForTodo();
|
|
2093
2107
|
const handleToolCall = getHandleToolCall(server);
|
|
2094
|
-
// 第一次 poll:1
|
|
2108
|
+
// 第一次 poll:1 项未完成,返回 todo 提醒
|
|
2095
2109
|
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2096
2110
|
assert.equal(result1.directMessages.length, 1);
|
|
2097
2111
|
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2098
|
-
// 修改 todo
|
|
2112
|
+
// 修改 todo:新增一项未完成,指纹变化
|
|
2099
2113
|
await writeTodoFile([
|
|
2100
2114
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2101
2115
|
{ content: "任务B", status: "in_progress", priority: "medium" },
|
|
2102
2116
|
]);
|
|
2103
|
-
// 第二次 poll
|
|
2117
|
+
// 第二次 poll:指纹变化,重新注入 todo 提醒
|
|
2104
2118
|
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2105
2119
|
assert.equal(result2.directMessages.length, 1);
|
|
2106
2120
|
assert.match(result2.directMessages[0].content, /Todo 列表存在 2 项未完成/);
|
|
@@ -2109,11 +2123,63 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2109
2123
|
await cleanupTodoFile();
|
|
2110
2124
|
}
|
|
2111
2125
|
});
|
|
2126
|
+
it("re-injects todo reminder when todo fingerprint changes (status changed)", async () => {
|
|
2127
|
+
await writeTodoFile([
|
|
2128
|
+
{ content: "任务A", status: "pending", priority: "high" },
|
|
2129
|
+
]);
|
|
2130
|
+
try {
|
|
2131
|
+
const server = createServerForTodo();
|
|
2132
|
+
const handleToolCall = getHandleToolCall(server);
|
|
2133
|
+
// 第一次 poll:pending 状态,返回 todo 提醒
|
|
2134
|
+
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2135
|
+
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2136
|
+
// 修改 todo:状态从 pending -> in_progress,指纹变化
|
|
2137
|
+
await writeTodoFile([
|
|
2138
|
+
{ content: "任务A", status: "in_progress", priority: "high" },
|
|
2139
|
+
]);
|
|
2140
|
+
// 第二次 poll:状态变化导致指纹变化,重新注入
|
|
2141
|
+
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2142
|
+
assert.match(result2.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2143
|
+
}
|
|
2144
|
+
finally {
|
|
2145
|
+
await cleanupTodoFile();
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
it("resets fingerprint when all todos become completed", async () => {
|
|
2149
|
+
await writeTodoFile([
|
|
2150
|
+
{ content: "任务A", status: "pending", priority: "high" },
|
|
2151
|
+
]);
|
|
2152
|
+
try {
|
|
2153
|
+
const server = createServerForTodo();
|
|
2154
|
+
const handleToolCall = getHandleToolCall(server);
|
|
2155
|
+
// 第一次 poll:有未完成项,返回 todo 提醒,记录指纹
|
|
2156
|
+
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2157
|
+
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2158
|
+
// 全部完成:指纹重置
|
|
2159
|
+
await writeTodoFile([
|
|
2160
|
+
{ content: "任务A", status: "completed", priority: "high" },
|
|
2161
|
+
]);
|
|
2162
|
+
const buffer = getMessageBuffer(server);
|
|
2163
|
+
const wake1 = makeDirectMessage("wake-all-completed");
|
|
2164
|
+
setTimeout(() => buffer.push(wake1), 50);
|
|
2165
|
+
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2166
|
+
assert.equal(result2.directMessages[0].msgId, "wake-all-completed");
|
|
2167
|
+
// 再次新增未完成项:指纹与已重置的 null 不同,应重新提醒
|
|
2168
|
+
await writeTodoFile([
|
|
2169
|
+
{ content: "任务B", status: "pending", priority: "medium" },
|
|
2170
|
+
]);
|
|
2171
|
+
const result3 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2172
|
+
assert.match(result3.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2173
|
+
}
|
|
2174
|
+
finally {
|
|
2175
|
+
await cleanupTodoFile();
|
|
2176
|
+
}
|
|
2177
|
+
});
|
|
2112
2178
|
it("does not inject todo reminder when todo file is absent", async () => {
|
|
2113
2179
|
// 确保文件不存在
|
|
2114
2180
|
await cleanupTodoFile();
|
|
2115
2181
|
const server = createServerForTodo();
|
|
2116
|
-
const buffer =
|
|
2182
|
+
const buffer = getMessageBuffer(server);
|
|
2117
2183
|
const wake = makeDirectMessage("wake-no-file");
|
|
2118
2184
|
setTimeout(() => buffer.push(wake), 50);
|
|
2119
2185
|
const handleToolCall = getHandleToolCall(server);
|