aws-runtime-bridge 1.9.109 → 1.9.110
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/package/acode/dist/runtime.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +1 -3
- package/package/aws-client-agent-mcp/dist/mcp-server.js +17 -18
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +81 -103
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.js +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js +39 -0
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import{EventEmitter as je}from"node:events";import J from"node:fs";import We fro
|
|
|
8
8
|
`),L=[...S(this.config.globalPrompt),...S(M),...S(this.config.memory),...E?[{role:"system",content:E}]:[]],O=L.map(p=>p.content).filter(Boolean).join(`
|
|
9
9
|
|
|
10
10
|
`);this.initialConversation=ie(L),this.state={started:!0,terminated:!1,configMcpCount:Object.keys(e.mcpServers??{}).length,skills:t,provider:a,permissions:i,mcpConnections:c,ownsMcpConnections:r,exposedTools:k,commands:$,commandRegistry:N,conversation:ie(L),accumulatedInputTokens:0,accumulatedOutputTokens:0},this.exposedToolsInitialized=!0,this.emitAcodeEvent("startup_phase",{phase:"session_start_hook"}),await this.hookBus.dispatch("SessionStart",{workingDirectory:this.config.workingDirectory,model:a.model,commandCount:$.length});const D=ae(a);this.emitAcodeEvent("session_started",{workingDirectory:this.config.workingDirectory,model:a.model,commandCount:$.length,estimatedContextTokens:St(O,k),contextLimit:D.contextLimit,maxOutput:D.maxOutput,...D.inputLimit!==void 0?{inputLimit:D.inputLimit}:{},compactionThreshold:D.threshold}),this.emitAcodeEvent("mcp_status",{configPath:this.config.configPath,installedCount:this.state.configMcpCount,servers:Object.keys(e.mcpServers??{}),connectedCount:c.length,builtInToolCount:g.length,toolCount:k.length,permissionMode:i.mode,allowedToolRules:i.allow,deniedToolRules:i.deny,tools:k.map(p=>({name:p.exposedName,source:p.kind==="mcp"?`${p.serverName}.${p.toolName}`:`builtin.${p.toolName}`,description:p.description||""}))}),this.emitAcodeEvent("skill_status",{skillsDir:this.config.skillsDir,installedCount:t.length,skills:t.map(p=>({name:p.name,description:p.description,directory:p.directory})),skillGroups:o.map(p=>({dir:p.dir,exists:p.exists,skills:p.skills.map(z=>({name:z.name,description:z.description,directory:z.directory}))}))}),this.emitAcodeEvent("command_status",{action:"commands_loaded",installedCommandCount:$.length,commandDirs:A,commands:$.map(p=>({name:p.name,description:p.description}))}),this.config.initialPrompt&&await this.submit(this.config.initialPrompt),this.startCompactionDebugLog(),this.startSkillWatchers(s)}async loadAllSkills(){const e=this.config.skillsDir??"",t=V.join(this.config.workingDirectory,".acode","skills"),o=V.join(this.config.workingDirectory,".agentswork","skills"),s=new Set,a=[],i=[];for(const u of[e,t,o]){if(!u)continue;const m=V.resolve(u);if(!s.has(m)){s.add(m),a.push(await mt(u));try{await J.promises.access(m),i.push(m)}catch{}}}const c=new Map;for(const u of a)for(const m of u.skills)c.set(m.name,m);return{skills:[...c.values()],skillGroups:a,watchedDirs:i}}startSkillWatchers(e){for(const t of e){let o=null;try{o=J.watch(t,{recursive:!0},()=>{this.scheduleSkillRefresh()})}catch{try{o=J.watch(t,()=>{this.scheduleSkillRefresh()})}catch{continue}}o&&(o.on("error",()=>{}),this.skillWatchers.push(o))}}scheduleSkillRefresh(){this.skillRefreshTimer&&clearTimeout(this.skillRefreshTimer),this.skillRefreshTimer=setTimeout(()=>{this.skillRefreshTimer=null,this.refreshSkills()},j.SKILL_REFRESH_DEBOUNCE_MS)}async refreshSkills(){if(!(!this.state.started||this.state.terminated))try{const{skills:e,skillGroups:t}=await this.loadAllSkills();this.skillTool&&this.skillTool.refresh(e),this.state.skills=e,this.emitAcodeEvent("skill_status",{skillsDir:this.config.skillsDir,installedCount:e.length,skills:e.map(o=>({name:o.name,description:o.description,directory:o.directory})),skillGroups:t.map(o=>({dir:o.dir,exists:o.exists,skills:o.skills.map(s=>({name:s.name,description:s.description,directory:s.directory}))}))}),l(`[skill-watch] Skills refreshed: ${e.length} skills (${e.map(o=>o.name).join(", ")||"none"})`,{sessionId:this.config.sessionId})}catch(e){console.error("[skill-watch] Failed to refresh skills:",e instanceof Error?e.message:e)}}stopSkillWatchers(){this.skillRefreshTimer&&(clearTimeout(this.skillRefreshTimer),this.skillRefreshTimer=null);for(const e of this.skillWatchers)try{e.close()}catch{}this.skillWatchers=[]}startCompactionDebugLog(){if(!te||this.compactionDebugTimer)return;const e=this.state.provider,{contextLimit:t,maxOutput:o,inputLimit:s,threshold:a}=ae(e);this.compactionDebugTimer=setInterval(()=>{try{const i=this.state.accumulatedInputTokens+this.state.accumulatedOutputTokens,c=X(this.state.conversation)+Ee(this.state.exposedTools),r=Math.max(i,c),u=H(this.state.conversation),m=this.state.started&&!this.state.terminated,g=[`[${new Date().toISOString()}]`,`started=${this.state.started}`,`terminated=${this.state.terminated}`,`activeTurn=${this.activeTurn}`,`conversationSize=${this.state.conversation.length}msgs`,`sizeKB=${u}KB`,`estimation=${c} tokens`,`actualInput=${this.state.accumulatedInputTokens}`,`actualOutput=${this.state.accumulatedOutputTokens}`,`actualUsed=${i}`,`contextLimit=${t}`,`inputLimit=${s??"n/a"}`,`maxOutput=${o}`,`triggerThreshold=${a}`,`remainingToTrigger=${Math.max(0,a-i)}`,`isRunning=${m}`,`shouldCompact=${m?r>=a:!1}`,"---"].join(" | ");J.promises.appendFile(ke,g+`
|
|
11
|
-
`,"utf-8").catch(()=>{})}catch{}},5e3)}stopCompactionDebugLog(){this.compactionDebugTimer&&(clearInterval(this.compactionDebugTimer),this.compactionDebugTimer=null)}async submit(e){const t=this.contextVersion;l(`[ACodeRuntime] \u{1F4EC} submit() queued: contextVersion=${t}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, terminated=${this.state.terminated}, message.length=${e.length}`);const o=this.submitQueue.then(()=>(l(`[ACodeRuntime] \u25B6\uFE0F executeSubmit dequeued: contextVersion=${t}`),this.executeSubmit(e,t)));return this.submitQueue=o.catch(()=>{l("[ACodeRuntime] \u{1F4EC} submit() queued item rejected (catch swallowed)")}),l("[ACodeRuntime] \u{1F4EC} submit() returning queued promise"),o}notifyForceMessage(){this.forceMessagePending=!0,l("[ACodeRuntime] \u{1F514} Force message pending flag set")}async injectForceMessage(){if(!this.forceMessagePending)return!1;this.forceMessagePending=!1;const e=this.state.mcpConnections.find(o=>o.tools.some(s=>s.toolName==="get_dm_messages"));if(!e)return l("[ACodeRuntime] injectForceMessage: no connection with get_dm_messages found"),!1;const t=this.config.agentId;if(!t)return l("[ACodeRuntime] injectForceMessage: no agentId in config"),!1;try{const o=await x(e.client.callTool({name:"get_dm_messages",arguments:{agentId:t}},void 0,{timeout:1e4}),1e4,"injectForceMessage get_dm_messages"),s=P(o);let a=[];try{const u=JSON.parse(s);Array.isArray(u?.messages)&&(a=u.messages.filter(m=>m?.force===!0))}catch{return!1}if(a.length===0)return!1;const i=JSON.stringify({messages:a}),c=`forced_get_msg_${Date.now()}`,r=`${e.serverName}__get_dm_messages`;return this.state.conversation.push({role:"assistant",content:null,tool_calls:[{id:c,type:"function",function:{name:r,arguments:"{}"}}]}),this.state.conversation.push({role:"tool",tool_call_id:c,content:i}),l(`[ACodeRuntime] injectForceMessage: injected ${a.length} force message(s) into conversation`),!0}catch(o){return l(`[ACodeRuntime] injectForceMessage failed: ${o}`),!1}}async resetContext(){if(!this.state.started||this.state.terminated)throw new Error("ACode runtime is not active");this.contextVersion+=1,this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.state.conversation=ie(this.initialConversation),this.state.accumulatedInputTokens=0,this.state.accumulatedOutputTokens=0}isTurnSuperseded(e){return e!==this.contextVersion}async executeSubmit(e,t){const o=Date.now();if(l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),l("[ACodeRuntime] \u25B6\uFE0F executeSubmit STARTED"),l(`[ACodeRuntime] Message: <redacted, length=${e.length}>`),l(`[ACodeRuntime] \u{1F522} Context version: ${t}`),l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),!this.state.started||this.state.terminated)throw console.error(`[ACodeRuntime] \u274C Runtime not active (started: ${this.state.started}, terminated: ${this.state.terminated})`),new Error("ACode runtime is not active");this.activeTurn=!0,this.abortRequested=!1;const s=e.trim();let a=!1;try{l("[ACodeRuntime] \u{1F50D} Dispatching UserPromptSubmit hook...");const i=Date.now(),c=await this.hookBus.dispatch("UserPromptSubmit",{prompt:s}),r=Date.now()-i;if(l(`[ACodeRuntime] \u2705 UserPromptSubmit hook completed in ${r}ms, results: ${c.length}`),this.isTurnSuperseded(t)){l("[ACodeRuntime] \u26A0\uFE0F Turn superseded after hook, aborting");return}const u=se(c);if(u){console.error(`[ACodeRuntime] \u274C Prompt denied by hook: ${u.message}`),this.emitAcodeEvent("error",{message:u.message||"ACode prompt was denied by hook"}),this.emitAcodeEvent("turn_complete",{failed:!0}),a=!0,l("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (failed=true) due to hook deny");return}l("[ACodeRuntime] \u{1F50D} Expanding command if any...");const m=this.state.commandRegistry.expand(s),g=m?.prompt.trim()||s,y=m?.command.allowedTools;if(m&&(l(`[ACodeRuntime] \u{1F4CB} Slash command detected: ${m.command.name}`),this.emitAcodeEvent("command_status",{action:"slash_command",command:m.command.name,arguments:m.argumentsText,sourcePath:m.command.sourcePath})),!this.state.provider.baseURL||!this.state.provider.apiKey){l("[ACodeRuntime] \u26A0\uFE0F No provider configured, using fallback response");const _=Qt(g,this.state);this.emitAcodeEvent("assistant_delta",{text:_}),this.emitAcodeEvent("turn_complete",{usage:{inputTokens:g.length,outputTokens:_.length}}),a=!0,l("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (fallback mode)");return}l("[ACodeRuntime] \u{1F4CE} Parsing attachment message...");const w=le(g);if(w.hasImages&&!this.state.provider.supportsImageInput)throw console.error("[ACodeRuntime] \u274C Image input not supported by current provider"),new Error("ACode provider/model is not configured for image input. Enable provider.default.models[model].modalities.input including 'image' or options.supportsImageInput=true.");l("[ACodeRuntime] \u{1F5BC}\uFE0F Materializing image content if any...");const d=await Ie(w.content);if(this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded after materializing content, aborting");return}l("[ACodeRuntime] \u{1F4AC} Adding user message to conversation"),this.state.conversation.push({role:"user",content:d}),l(`[ACodeRuntime] Conversation size: ${this.state.conversation.length} messages`),l("[ACodeRuntime] Starting runModelTurn...");const b=Date.now(),h=await this.runModelTurn(y,t),f=Date.now()-b;if(l(`[ACodeRuntime] \u2705 runModelTurn completed in ${f}ms`),l(`[ACodeRuntime] \u{1F4DD} Result: text=${h.text?h.text.slice(0,80)+(h.text.length>80?"...":""):"(empty)"}, finishReason=${h.finishReason||"(none)"}`),this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded after runModelTurn, aborting");return}h.text&&!this.state.provider.stream&&(l("[ACodeRuntime] \u{1F4E4} Emitting assistant_delta (non-streaming mode)"),this.emitAcodeEvent("assistant_delta",{text:h.text})),l("[ACodeRuntime] \u{1F4E4} Emitting turn_complete..."),this.emitAcodeEvent("turn_complete",{usage:h.usage??{inputTokens:g.length,outputTokens:h.totalOutputTextLength??h.text.length},...h.responseBody?{responseBody:h.responseBody}:{},...h.finishReason?{finishReason:h.finishReason}:{}}),l(`[ACodeRuntime] \u{1F4B0} Current context usage: input=${this.state.accumulatedInputTokens}, output=${this.state.accumulatedOutputTokens}`+(h.usage?"":" (estimated, no provider usage)")),a=!0;const C=Date.now()-o;l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),l(`[ACodeRuntime] \u2705 executeSubmit COMPLETED in ${C}ms`),l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}catch(i){const c=Date.now()-o;if(console.error(`[ACodeRuntime] \u274C executeSubmit FAILED after ${c}ms:`,i instanceof Error?i.message:String(i)),console.error("[ACodeRuntime] \u{1F50D} Error stack:",i instanceof Error?i.stack:"(no stack)"),this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded in error handler, skipping recovery");return}if(this.abortRequested){l("[ACodeRuntime] \u26A0\uFE0F Abort requested, emitting aborted turn_complete"),this.emitAcodeEvent("turn_complete",{aborted:!0}),a=!0;return}l("[ACodeRuntime] Emitting error and failed turn_complete..."),this.emitAcodeEvent("error",{message:i instanceof Error?i.message:String(i)}),this.emitAcodeEvent("turn_complete",{failed:!0}),a=!0}finally{const i=Date.now()-o;l(`[ACodeRuntime] \u{1F51A} executeSubmit finally block entered after ${i}ms`),l(`[ACodeRuntime] \u{1F50D} turnCompleted=${a}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, contextVersion=${t}, currentContextVersion=${this.contextVersion}`),this.activeTurn=!1,this.abortRequested=!1,!a&&!this.isTurnSuperseded(t)&&(console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: executeSubmit finally block reached without turn_complete event! Duration: ${i}ms. This indicates a code path that should have emitted turn_complete but didn't. Forcing failed turn_complete to prevent state stuck.`),this.emitAcodeEvent("error",{message:"ACode turn completed without proper completion event (internal error)"}),this.emitAcodeEvent("turn_complete",{failed:!0}),l("[ACodeRuntime] Emergency turn_complete (failed=true) emitted"))}}async abort(){const e=this.abortController!==null,t=this.activeTurn;this.abortRequested=this.activeTurn,this.abortController?(l(`[ACodeRuntime] \u26D4 abort() calling abortController.abort() (activeTurn=${t})`),this.abortController.abort()):console.warn(`[ACodeRuntime] \u26D4 abort() called but abortController is null! activeTurn=${t}, abortRequested will be set to ${t}. This means abort() CANNOT cancel any running fetch/stream \u2014 if a built-in tool is executing, it will NOT be interrupted.`),this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop()}buildAskUserQuestionDeps(){return{onQuestion:(e,t)=>{this.emitAcodeEvent("question_request",{toolCallId:e,questions:t})},waitForAnswer:(e,t)=>new Promise((o,s)=>{this.questionWaiters.set(e,{resolve:o,reject:s,contextVersion:t})})}}buildSubAgentToolDeps(e){const t=this.config.agentId??"unknown",o=this.config.sessionId,s=this.config.workingDirectory,a=this.config;return{spawnSubAgent:async i=>{const c=await e.spawn({parentAgentId:t,parentSessionId:o,subagentType:i.subagentType,description:i.description,prompt:i.prompt,workingDirectory:s,parentConfig:{...a,sharedMcpConnections:this.state?.mcpConnections}});return await e.start(c),{subId:c}},listSubAgents:async()=>{const i=e.listByParent(t),c=[],r=[];for(const u of i){const m={subId:u.subId,subagentType:u.subagentType,description:u.description,status:u.status,startedAt:u.startedAt,endedAt:u.endedAt,durationMs:u.durationMs};u.status==="pending"||u.status==="running"?c.push(m):r.push(m)}return r.sort((u,m)=>(m.endedAt??0)-(u.endedAt??0)),{running:c,history:r}},getSubAgentStatus:async i=>{const c=e.getSubAgent(i);return c?{subId:c.subId,subagentType:c.subagentType,description:c.description,status:c.status,startedAt:c.startedAt,endedAt:c.endedAt,durationMs:c.durationMs,prompt:c.prompt,result:c.result,errorMessage:c.errorMessage,contextSnapshot:c.contextSnapshot}:null},awaitSubAgents:async(i,c)=>{const r=new Set;for(const u of i)this.pendingAwaitedSubIds.add(u),r.add(u);try{return await e.awaitCompletion(i,c)}finally{for(const u of r)this.pendingAwaitedSubIds.delete(u)}},abortSubAgent:i=>e.abort(i)}}buildAwaitCompleteDeps(){const e=this.config.agentId??"unknown",t=this.config.subAgentManager;return{agentId:e,awaitSubAgents:t?(o,s)=>{for(const a of o)this.pendingAwaitedSubIds.add(a);try{return this.buildSubAgentToolDeps(t).awaitSubAgents(o,s)}finally{for(const a of o)this.pendingAwaitedSubIds.delete(a)}}:null,awaitBgCommands:(o,s)=>{for(const a of o)this.pendingAwaitedBgTaskIds.add(a);try{return K.awaitCompletion(e,o,s)}finally{for(const a of o)this.pendingAwaitedBgTaskIds.delete(a)}}}}submitQuestionAnswer(e,t){const o=this.questionWaiters.get(e);return o?(this.questionWaiters.delete(e),this.isTurnSuperseded(o.contextVersion)?(o.reject(new Error("turn superseded before user answered")),!1):(o.resolve(t),!0)):!1}rejectAllQuestionWaiters(e){if(this.questionWaiters.size!==0){for(const[,t]of this.questionWaiters)t.reject(new Error(`ask_user_question ${e}`));this.questionWaiters.clear()}}cancelPendingAwaitedSubAgents(){if(this.pendingAwaitedSubIds.size===0)return;const e=this.config.subAgentManager;if(!e)return;const t=Array.from(this.pendingAwaitedSubIds);l(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited sub-agent(s): ${t.join(", ")}`);for(const o of t)e.abort(o).catch(s=>{l(`[ACodeRuntime] \u26A0\uFE0F manager.abort(${o}) threw: ${s instanceof Error?s.message:String(s)}`)})}cancelPendingAwaitedBgTasks(){if(this.pendingAwaitedBgTaskIds.size===0)return;const e=this.config.agentId??"unknown",t=Array.from(this.pendingAwaitedBgTaskIds);l(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited background command(s): ${t.join(", ")}`),K.interruptAwaits(e,t)}detachCurrentToolExecution(){const e=this.toolSettleSlot;return!e||!e.settlable||e.settled?!1:(e.settled=!0,e.resolveSettle?.(),l(`[ACodeRuntime] \u{1F680} detachCurrentToolExecution: settling tool ${e.toolName} (toolCallId=${e.toolCallId}) to background`),!0)}beginToolSettle(e,t,o){const s=e.toolName==="Bash"&&typeof t.wait_seconds=="number"&&t.wait_seconds>0,a=++this.toolSettleSeq;let i;const c=s?new Promise(r=>{i=r}):void 0;return this.toolSettleSlot={seq:a,toolName:e.toolName,toolCallId:o,settlable:s,settled:!1,resolveSettle:i??null},{seq:a,signal:c}}clearToolSettle(e){this.toolSettleSlot?.seq===e&&(this.toolSettleSlot=null)}async stop(){if(this.state.terminated)return;this.state.terminated=!0,this.notifyRuntimeStop(),this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.rejectAllQuestionWaiters("session terminated"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),await Q("ACode active turn shutdown",this.submitQueue,t=>{this.emitAcodeEvent("error",{message:t})}),await this.disconnectMcpServers();const e=this.config.agentId||"unknown";if(await Q("ACode background commands shutdown",K.disposeAgent(e),t=>{this.emitAcodeEvent("error",{message:t})}),Q("ACode agent PTY sessions shutdown",Promise.resolve(this.config.terminalManager?.killByAgent?.(e)),t=>{this.emitAcodeEvent("error",{message:t})}),await Q("ACode Stop hooks",this.hookBus.dispatch("Stop",{reason:"session_stop"}),t=>{this.emitAcodeEvent("error",{message:t})}),this.state.terminated=!0,this.sessionCompleteEmitted||(this.sessionCompleteEmitted=!0,this.emitAcodeEvent("session_complete",{exitCode:0})),this.stopCompactionDebugLog(),this.stopSkillWatchers(),te)try{J.promises.appendFile(ke,`[${new Date().toISOString()}] runtime stopped | ---
|
|
11
|
+
`,"utf-8").catch(()=>{})}catch{}},5e3)}stopCompactionDebugLog(){this.compactionDebugTimer&&(clearInterval(this.compactionDebugTimer),this.compactionDebugTimer=null)}async submit(e){const t=this.contextVersion;l(`[ACodeRuntime] \u{1F4EC} submit() queued: contextVersion=${t}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, terminated=${this.state.terminated}, message.length=${e.length}`);const o=this.submitQueue.then(()=>(l(`[ACodeRuntime] \u25B6\uFE0F executeSubmit dequeued: contextVersion=${t}`),this.executeSubmit(e,t)));return this.submitQueue=o.catch(()=>{l("[ACodeRuntime] \u{1F4EC} submit() queued item rejected (catch swallowed)")}),l("[ACodeRuntime] \u{1F4EC} submit() returning queued promise"),o}notifyForceMessage(){this.forceMessagePending=!0,l("[ACodeRuntime] \u{1F514} Force message pending flag set")}async injectForceMessage(){if(!this.forceMessagePending)return!1;this.forceMessagePending=!1;const e=this.state.mcpConnections.find(t=>t.tools.some(o=>o.toolName==="get_message"));if(!e)return l("[ACodeRuntime] injectForceMessage: no connection with get_message found"),!1;try{const t=await x(e.client.callTool({name:"get_message",arguments:{_aws_force_inject:!0}},void 0,{timeout:1e4}),1e4,"injectForceMessage get_message"),o=P(t);let s=[];try{const r=JSON.parse(o);Array.isArray(r?.directMessages)&&(s=r.directMessages.filter(u=>u?.force===!0))}catch{return!1}if(s.length===0)return!1;const a=JSON.stringify({directMessages:s,groupMessages:[]}),i=`forced_get_msg_${Date.now()}`,c=`${e.serverName}__get_message`;return this.state.conversation.push({role:"assistant",content:null,tool_calls:[{id:i,type:"function",function:{name:c,arguments:"{}"}}]}),this.state.conversation.push({role:"tool",tool_call_id:i,content:a}),l(`[ACodeRuntime] injectForceMessage: injected ${s.length} force message(s) into conversation`),!0}catch(t){return l(`[ACodeRuntime] injectForceMessage failed: ${t}`),!1}}async resetContext(){if(!this.state.started||this.state.terminated)throw new Error("ACode runtime is not active");this.contextVersion+=1,this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.state.conversation=ie(this.initialConversation),this.state.accumulatedInputTokens=0,this.state.accumulatedOutputTokens=0}isTurnSuperseded(e){return e!==this.contextVersion}async executeSubmit(e,t){const o=Date.now();if(l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),l("[ACodeRuntime] \u25B6\uFE0F executeSubmit STARTED"),l(`[ACodeRuntime] Message: <redacted, length=${e.length}>`),l(`[ACodeRuntime] \u{1F522} Context version: ${t}`),l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),!this.state.started||this.state.terminated)throw console.error(`[ACodeRuntime] \u274C Runtime not active (started: ${this.state.started}, terminated: ${this.state.terminated})`),new Error("ACode runtime is not active");this.activeTurn=!0,this.abortRequested=!1;const s=e.trim();let a=!1;try{l("[ACodeRuntime] \u{1F50D} Dispatching UserPromptSubmit hook...");const i=Date.now(),c=await this.hookBus.dispatch("UserPromptSubmit",{prompt:s}),r=Date.now()-i;if(l(`[ACodeRuntime] \u2705 UserPromptSubmit hook completed in ${r}ms, results: ${c.length}`),this.isTurnSuperseded(t)){l("[ACodeRuntime] \u26A0\uFE0F Turn superseded after hook, aborting");return}const u=se(c);if(u){console.error(`[ACodeRuntime] \u274C Prompt denied by hook: ${u.message}`),this.emitAcodeEvent("error",{message:u.message||"ACode prompt was denied by hook"}),this.emitAcodeEvent("turn_complete",{failed:!0}),a=!0,l("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (failed=true) due to hook deny");return}l("[ACodeRuntime] \u{1F50D} Expanding command if any...");const m=this.state.commandRegistry.expand(s),g=m?.prompt.trim()||s,y=m?.command.allowedTools;if(m&&(l(`[ACodeRuntime] \u{1F4CB} Slash command detected: ${m.command.name}`),this.emitAcodeEvent("command_status",{action:"slash_command",command:m.command.name,arguments:m.argumentsText,sourcePath:m.command.sourcePath})),!this.state.provider.baseURL||!this.state.provider.apiKey){l("[ACodeRuntime] \u26A0\uFE0F No provider configured, using fallback response");const _=Qt(g,this.state);this.emitAcodeEvent("assistant_delta",{text:_}),this.emitAcodeEvent("turn_complete",{usage:{inputTokens:g.length,outputTokens:_.length}}),a=!0,l("[ACodeRuntime] \u{1F4E4} Emitted turn_complete (fallback mode)");return}l("[ACodeRuntime] \u{1F4CE} Parsing attachment message...");const w=le(g);if(w.hasImages&&!this.state.provider.supportsImageInput)throw console.error("[ACodeRuntime] \u274C Image input not supported by current provider"),new Error("ACode provider/model is not configured for image input. Enable provider.default.models[model].modalities.input including 'image' or options.supportsImageInput=true.");l("[ACodeRuntime] \u{1F5BC}\uFE0F Materializing image content if any...");const d=await Ie(w.content);if(this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded after materializing content, aborting");return}l("[ACodeRuntime] \u{1F4AC} Adding user message to conversation"),this.state.conversation.push({role:"user",content:d}),l(`[ACodeRuntime] Conversation size: ${this.state.conversation.length} messages`),l("[ACodeRuntime] Starting runModelTurn...");const b=Date.now(),h=await this.runModelTurn(y,t),f=Date.now()-b;if(l(`[ACodeRuntime] \u2705 runModelTurn completed in ${f}ms`),l(`[ACodeRuntime] \u{1F4DD} Result: text=${h.text?h.text.slice(0,80)+(h.text.length>80?"...":""):"(empty)"}, finishReason=${h.finishReason||"(none)"}`),this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded after runModelTurn, aborting");return}h.text&&!this.state.provider.stream&&(l("[ACodeRuntime] \u{1F4E4} Emitting assistant_delta (non-streaming mode)"),this.emitAcodeEvent("assistant_delta",{text:h.text})),l("[ACodeRuntime] \u{1F4E4} Emitting turn_complete..."),this.emitAcodeEvent("turn_complete",{usage:h.usage??{inputTokens:g.length,outputTokens:h.totalOutputTextLength??h.text.length},...h.responseBody?{responseBody:h.responseBody}:{},...h.finishReason?{finishReason:h.finishReason}:{}}),l(`[ACodeRuntime] \u{1F4B0} Current context usage: input=${this.state.accumulatedInputTokens}, output=${this.state.accumulatedOutputTokens}`+(h.usage?"":" (estimated, no provider usage)")),a=!0;const C=Date.now()-o;l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"),l(`[ACodeRuntime] \u2705 executeSubmit COMPLETED in ${C}ms`),l("[ACodeRuntime] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}catch(i){const c=Date.now()-o;if(console.error(`[ACodeRuntime] \u274C executeSubmit FAILED after ${c}ms:`,i instanceof Error?i.message:String(i)),console.error("[ACodeRuntime] \u{1F50D} Error stack:",i instanceof Error?i.stack:"(no stack)"),this.isTurnSuperseded(t)){l("[ACodeRuntime] \uFE0F Turn superseded in error handler, skipping recovery");return}if(this.abortRequested){l("[ACodeRuntime] \u26A0\uFE0F Abort requested, emitting aborted turn_complete"),this.emitAcodeEvent("turn_complete",{aborted:!0}),a=!0;return}l("[ACodeRuntime] Emitting error and failed turn_complete..."),this.emitAcodeEvent("error",{message:i instanceof Error?i.message:String(i)}),this.emitAcodeEvent("turn_complete",{failed:!0}),a=!0}finally{const i=Date.now()-o;l(`[ACodeRuntime] \u{1F51A} executeSubmit finally block entered after ${i}ms`),l(`[ACodeRuntime] \u{1F50D} turnCompleted=${a}, activeTurn=${this.activeTurn}, abortRequested=${this.abortRequested}, contextVersion=${t}, currentContextVersion=${this.contextVersion}`),this.activeTurn=!1,this.abortRequested=!1,!a&&!this.isTurnSuperseded(t)&&(console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: executeSubmit finally block reached without turn_complete event! Duration: ${i}ms. This indicates a code path that should have emitted turn_complete but didn't. Forcing failed turn_complete to prevent state stuck.`),this.emitAcodeEvent("error",{message:"ACode turn completed without proper completion event (internal error)"}),this.emitAcodeEvent("turn_complete",{failed:!0}),l("[ACodeRuntime] Emergency turn_complete (failed=true) emitted"))}}async abort(){const e=this.abortController!==null,t=this.activeTurn;this.abortRequested=this.activeTurn,this.abortController?(l(`[ACodeRuntime] \u26D4 abort() calling abortController.abort() (activeTurn=${t})`),this.abortController.abort()):console.warn(`[ACodeRuntime] \u26D4 abort() called but abortController is null! activeTurn=${t}, abortRequested will be set to ${t}. This means abort() CANNOT cancel any running fetch/stream \u2014 if a built-in tool is executing, it will NOT be interrupted.`),this.abortController=null,this.rejectAllQuestionWaiters("aborted"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),this.notifyRuntimeStop()}buildAskUserQuestionDeps(){return{onQuestion:(e,t)=>{this.emitAcodeEvent("question_request",{toolCallId:e,questions:t})},waitForAnswer:(e,t)=>new Promise((o,s)=>{this.questionWaiters.set(e,{resolve:o,reject:s,contextVersion:t})})}}buildSubAgentToolDeps(e){const t=this.config.agentId??"unknown",o=this.config.sessionId,s=this.config.workingDirectory,a=this.config;return{spawnSubAgent:async i=>{const c=await e.spawn({parentAgentId:t,parentSessionId:o,subagentType:i.subagentType,description:i.description,prompt:i.prompt,workingDirectory:s,parentConfig:{...a,sharedMcpConnections:this.state?.mcpConnections}});return await e.start(c),{subId:c}},listSubAgents:async()=>{const i=e.listByParent(t),c=[],r=[];for(const u of i){const m={subId:u.subId,subagentType:u.subagentType,description:u.description,status:u.status,startedAt:u.startedAt,endedAt:u.endedAt,durationMs:u.durationMs};u.status==="pending"||u.status==="running"?c.push(m):r.push(m)}return r.sort((u,m)=>(m.endedAt??0)-(u.endedAt??0)),{running:c,history:r}},getSubAgentStatus:async i=>{const c=e.getSubAgent(i);return c?{subId:c.subId,subagentType:c.subagentType,description:c.description,status:c.status,startedAt:c.startedAt,endedAt:c.endedAt,durationMs:c.durationMs,prompt:c.prompt,result:c.result,errorMessage:c.errorMessage,contextSnapshot:c.contextSnapshot}:null},awaitSubAgents:async(i,c)=>{const r=new Set;for(const u of i)this.pendingAwaitedSubIds.add(u),r.add(u);try{return await e.awaitCompletion(i,c)}finally{for(const u of r)this.pendingAwaitedSubIds.delete(u)}},abortSubAgent:i=>e.abort(i)}}buildAwaitCompleteDeps(){const e=this.config.agentId??"unknown",t=this.config.subAgentManager;return{agentId:e,awaitSubAgents:t?(o,s)=>{for(const a of o)this.pendingAwaitedSubIds.add(a);try{return this.buildSubAgentToolDeps(t).awaitSubAgents(o,s)}finally{for(const a of o)this.pendingAwaitedSubIds.delete(a)}}:null,awaitBgCommands:(o,s)=>{for(const a of o)this.pendingAwaitedBgTaskIds.add(a);try{return K.awaitCompletion(e,o,s)}finally{for(const a of o)this.pendingAwaitedBgTaskIds.delete(a)}}}}submitQuestionAnswer(e,t){const o=this.questionWaiters.get(e);return o?(this.questionWaiters.delete(e),this.isTurnSuperseded(o.contextVersion)?(o.reject(new Error("turn superseded before user answered")),!1):(o.resolve(t),!0)):!1}rejectAllQuestionWaiters(e){if(this.questionWaiters.size!==0){for(const[,t]of this.questionWaiters)t.reject(new Error(`ask_user_question ${e}`));this.questionWaiters.clear()}}cancelPendingAwaitedSubAgents(){if(this.pendingAwaitedSubIds.size===0)return;const e=this.config.subAgentManager;if(!e)return;const t=Array.from(this.pendingAwaitedSubIds);l(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited sub-agent(s): ${t.join(", ")}`);for(const o of t)e.abort(o).catch(s=>{l(`[ACodeRuntime] \u26A0\uFE0F manager.abort(${o}) threw: ${s instanceof Error?s.message:String(s)}`)})}cancelPendingAwaitedBgTasks(){if(this.pendingAwaitedBgTaskIds.size===0)return;const e=this.config.agentId??"unknown",t=Array.from(this.pendingAwaitedBgTaskIds);l(`[ACodeRuntime] \u26D4 Cancelling ${t.length} pending awaited background command(s): ${t.join(", ")}`),K.interruptAwaits(e,t)}detachCurrentToolExecution(){const e=this.toolSettleSlot;return!e||!e.settlable||e.settled?!1:(e.settled=!0,e.resolveSettle?.(),l(`[ACodeRuntime] \u{1F680} detachCurrentToolExecution: settling tool ${e.toolName} (toolCallId=${e.toolCallId}) to background`),!0)}beginToolSettle(e,t,o){const s=e.toolName==="Bash"&&typeof t.wait_seconds=="number"&&t.wait_seconds>0,a=++this.toolSettleSeq;let i;const c=s?new Promise(r=>{i=r}):void 0;return this.toolSettleSlot={seq:a,toolName:e.toolName,toolCallId:o,settlable:s,settled:!1,resolveSettle:i??null},{seq:a,signal:c}}clearToolSettle(e){this.toolSettleSlot?.seq===e&&(this.toolSettleSlot=null)}async stop(){if(this.state.terminated)return;this.state.terminated=!0,this.notifyRuntimeStop(),this.abortRequested=this.activeTurn,this.abortController?.abort(),this.abortController=null,this.rejectAllQuestionWaiters("session terminated"),this.cancelPendingAwaitedSubAgents(),this.cancelPendingAwaitedBgTasks(),await Q("ACode active turn shutdown",this.submitQueue,t=>{this.emitAcodeEvent("error",{message:t})}),await this.disconnectMcpServers();const e=this.config.agentId||"unknown";if(await Q("ACode background commands shutdown",K.disposeAgent(e),t=>{this.emitAcodeEvent("error",{message:t})}),Q("ACode agent PTY sessions shutdown",Promise.resolve(this.config.terminalManager?.killByAgent?.(e)),t=>{this.emitAcodeEvent("error",{message:t})}),await Q("ACode Stop hooks",this.hookBus.dispatch("Stop",{reason:"session_stop"}),t=>{this.emitAcodeEvent("error",{message:t})}),this.state.terminated=!0,this.sessionCompleteEmitted||(this.sessionCompleteEmitted=!0,this.emitAcodeEvent("session_complete",{exitCode:0})),this.stopCompactionDebugLog(),this.stopSkillWatchers(),te)try{J.promises.appendFile(ke,`[${new Date().toISOString()}] runtime stopped | ---
|
|
12
12
|
`,"utf-8").catch(()=>{})}catch{}}resolveCommandDirs(e){const t=[...this.config.commandDirs??[],...e.commandDirs??[],...e.commands?.directories??[]];return[...new Set(t.map(o=>o.trim()).filter(Boolean))]}async runModelTurn(e,t=this.contextVersion){let o="",s,a,i,c="",r=0;l(`[ACodeRuntime] \u25B6\uFE0F runModelTurn started (contextVersion: ${t})`);const u=50;for(let m=0;m<u;m+=1){if(l(`[ACodeRuntime] \u{1F504} Iteration ${m+1} starting...`),this.isTurnSuperseded(t))return l(`[ACodeRuntime] \u26A0\uFE0F Turn superseded at iteration ${m+1}, aborting`),{text:"",totalOutputTextLength:c.length};await this.injectForceMessage();const g=Date.now(),y=await this.requestChatCompletion(e),w=Date.now()-g;if(l(`[ACodeRuntime] \u{1F916} AI request completed in ${w}ms, choices: ${y.choices?.length||0}`),this.isTurnSuperseded(t))return l(`[ACodeRuntime] \u26A0\uFE0F Turn superseded after AI response at iteration ${m+1}`),{text:"",totalOutputTextLength:c.length};if(this.state.terminated)throw l(`[ACodeRuntime] Runtime terminated at iteration ${m+1}`),new Error("ACode runtime was stopped during the active turn");const d=y.choices?.[0];a=JSON.stringify(y,null,2),i=typeof d?.finish_reason=="string"?d.finish_reason:void 0;const b=d?.message,h=typeof b?.content=="string"?b.content:"",f=Array.isArray(b?.tool_calls)?b.tool_calls:[];if(l(`[ACodeRuntime] AI response: content=${h?h.slice(0,100)+(h.length>100?"...":""):"(empty)"}, toolCalls=${f.length}, finishReason=${i||"(none)"}`),y.usage){const A=y.usage.prompt_tokens??y.usage.input_tokens,$=y.usage.completion_tokens??y.usage.output_tokens;typeof A=="number"&&typeof $=="number"&&(s={inputTokens:A,outputTokens:$},r+=A,this.state.accumulatedInputTokens=A,this.state.accumulatedOutputTokens=$,l(`[ACodeRuntime] \u{1F4B0} Token usage: input=${A}, output=${$} (turn accumulated cost: input=${r})`))}if(this.state.conversation.push({role:"assistant",content:h||null,tool_calls:f.length>0?f:void 0}),h&&(o=h,c+=h,l(`[ACodeRuntime] \u2728 Captured assistant text: ${h.slice(0,80)}...`)),f.length===0)return l(`[ACodeRuntime] \u2705 No tool calls at iteration ${m+1}, completing turn`),{text:o,usage:s,responseBody:a,finishReason:i,totalOutputTextLength:c.length};const C=f.map(A=>A.function.name).join(", ");l(`[ACodeRuntime] \u{1F527} Executing ${f.length} tool(s) at iteration ${m+1}: ${C}`);const _=Date.now();await this.handleToolCalls(f,e,t);const k=Date.now()-_;if(l(`[ACodeRuntime] \u2699\uFE0F Tool execution completed in ${k}ms at iteration ${m+1}`),this.isTurnSuperseded(t))return l(`[ACodeRuntime] \u26A0\uFE0F Turn superseded after tool execution at iteration ${m+1}`),{text:"",totalOutputTextLength:c.length};l(`[ACodeRuntime] \u{1F504} Preparing iteration ${m+2}...`)}return console.error(`[ACodeRuntime] \u{1F6A8} CRITICAL: Reached safety limit of ${u} iterations! This indicates a potential infinite loop. Forcing turn completion.`),{text:o||`ACode \u5DF2\u8FBE\u5230\u5B89\u5168\u8FED\u4EE3\u4E0A\u9650\uFF08${u}\u6B21\uFF09\uFF0C\u5F3A\u5236\u7ED3\u675F\u672C\u8F6E\u5BF9\u8BDD\u3002`,usage:s,responseBody:a,finishReason:i||"safety_iteration_limit_reached",totalOutputTextLength:c.length}}async summarizeConversationWithLLM(e){const t=this.state.provider,o=t.baseURL;if(!o)throw new Error("ACode provider baseURL is not configured for LLM summarization");const s={"Content-Type":"application/json"};t.apiKey&&(s.Authorization=`Bearer ${t.apiKey}`);const a={model:t.model,stream:!1,messages:[{role:"system",content:"You are a helpful AI assistant tasked with summarizing conversations. When asked to summarize, provide a detailed but concise summary of the conversation. Focus on information that would be helpful for continuing the conversation, especially what was in progress and what needs to happen next. Do not respond to any questions in the conversation, only output the summary."},{role:"user",content:`Below is an earlier conversation (${e.messageCount} messages) that needs to be summarized to free context space (current context limit: ${e.contextLimit} tokens).
|
|
13
13
|
|
|
14
14
|
--- Conversation Start ---
|
|
@@ -12,8 +12,6 @@ export declare class McpServer {
|
|
|
12
12
|
private server;
|
|
13
13
|
private agentClient;
|
|
14
14
|
private messageBuffer;
|
|
15
|
-
|
|
16
|
-
private lastTodoFingerprint;
|
|
17
15
|
private memoryStore;
|
|
18
16
|
private roleMemoryStore;
|
|
19
17
|
private sessionContextMemoryStore;
|
|
@@ -191,7 +189,7 @@ export declare class McpServer {
|
|
|
191
189
|
|
|
192
190
|
private sanitizeAgentIdForTodoFile;
|
|
193
191
|
|
|
194
|
-
private
|
|
192
|
+
private buildTodoBlockNotification;
|
|
195
193
|
|
|
196
194
|
private buildBackgroundTaskNotification;
|
|
197
195
|
start(): Promise<void>;
|
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import $ 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 a}from"./logger.js";import{MCP_TOOLS as D,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;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(a.info(`[McpServer] syncRoleMemoryStore \u88AB\u8C03\u7528: displayName=${e.displayName}, roleName="${e.roleName??"(\u7A7A)"}", id=${e.id}`),!e.roleName){a.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&&(a.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=>{a.warn("[McpServer] maybeSyncPermanentMemoryToServer \u5F02\u5E38: %s, stack=%s",e instanceof Error?e.message:String(e),e instanceof Error?e.stack:"(no stack)")})}async doMaybeSyncPermanentMemoryToServer(){a.info("[McpServer] maybeSyncPermanentMemoryToServer \u5F00\u59CB\uFF0CinitialRoleName="+this.initialRoleName);let e;try{e=await this.memoryStore.getStats()}catch(s){a.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(a.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"),a.info(`[McpServer] \u89D2\u8272\u5B50\u76EE\u5F55\u8BFB\u53D6\u5230 ${r.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`)}catch(s){a.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));a.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&&(a.info(`[McpServer] \u4ECE\u9057\u7559\u76EE\u5F55\u8BFB\u53D6\u5230 ${l.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`),r=l)}catch(o){a.warn("[McpServer] \u8BFB\u53D6\u9057\u7559\u76EE\u5F55\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25:",o instanceof Error?o.message:o)}}if(r.length===0){a.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++,a.info(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u6210\u529F: ${s.uri}`)}catch(o){const l=o instanceof Error?o.message:String(o);l.includes("already exists")||l.includes("Memory already exists")||a.warn(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25 (${s.uri}): ${l}`),i++}a.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 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
|
|
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?D.filter(t=>F.has(t.name)):D})),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?._aws_force_inject===!0,s={...t};delete s._aws_sub_agent,delete s._aws_silent_poll,delete s._aws_force_inject;const o=r?null:this.agentClient.getStatusReporter();o&&!n&&o.reportToolUse(e,this.sanitizeToolUseArgs(e,s));try{const l=await this.executeToolCall(e,s,{forceInject:i});if(o&&!n){const m=this.stripAutoDelegateForReporting(l);m!==null&&(o.reportToolResult(m),o.reportThinking())}return l}catch(l){throw o&&!n&&o.reportToolError(l),l}}async executeToolCall(e,t,r={}){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(r.forceInject===!0);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($.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 k;try{k=JSON.parse(S)}catch{continue}if(k.by_uri)for(const[E,I]of Object.entries(k.by_uri)){if(!I)continue;const O=I.id||E;l.has(O)||t&&I.memory_type!==t||I.bound_files&&I.bound_files.length>0&&I.bound_files.some(B=>this.normalizeBoundFilePath(B)===R)&&(l.add(O),o.push({...I,uri:I.uri||E}))}}}}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
|
|
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){a.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=>a.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=>a.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(a.info("[McpServer] handlePollMessage \u88AB\u8C03\u7528"),this.pendingPollMessage)return a.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(){a.info("[McpServer] doPollMessage \u88AB\u8C03\u7528");const e=await this.buildTodoBlockNotification();if(e)return this.buildPollResult({directMessages:[e],groupMessages:[]});await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0}),await this.hydrateBrowserListenerEvents();const t=await this.buildBackgroundTaskNotification(),r=this.messageBuffer.get();if(r.directMessages.length>0||r.groupMessages.length>0)return t&&r.directMessages.unshift(t),this.buildPollResult(r);if(t)return this.buildPollResult({directMessages:[t],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 T=p.getCurrentStatus();T.status==="tool_using"&&T.actionDetail!=null&&T.actionDetail.includes("poll_message")||p.reportToolUse("poll_message",{}),i=!0}}const o=await this.messageBuffer.poll(ee);if(o.directMessages.length>0||o.groupMessages.length>0)return this.buildPollResult(o);if(a.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 a.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;const m=await this.buildTodoBlockNotification();if(m)return this.buildPollResult({directMessages:[m],groupMessages:[]});await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0});const u=await this.buildBackgroundTaskNotification(),h=this.messageBuffer.get();if(h.directMessages.length>0||h.groupMessages.length>0)return u&&h.directMessages.unshift(u),this.buildPollResult(h);if(u)return this.buildPollResult({directMessages:[u],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?(a.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(([,o])=>o.listable&&o.exceeded),r=Object.entries(e.by_type).flatMap(([o,l])=>l.listable?Object.entries(l.by_domain).filter(([,m])=>m.exceeded).map(([m,u])=>({memoryType:o,domain:m,domainStats:u})):[]);if(t.length===0&&r.length===0)return null;const n=t.map(([o,l])=>`${o} \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:o,domain:l,domainStats:m})=>`${o}/${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,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_cleanup",uris:r.map(o=>`${o.memoryType}/${o.domain}`)}}}catch(e){return a.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
|
|
11
11
|
${i}${r.length>5?`
|
|
12
|
-
...\u53CA\u5176\u4ED6 ${r.length-5} \u6761`:""}`,requireReply:!1,
|
|
12
|
+
...\u53CA\u5176\u4ED6 ${r.length-5} \u6761`:""}`,requireReply:!1,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:r.map(s=>s.uri)}}}if(n.length>0){const i=n.slice(0,5).map(s=>s.uri).join(`
|
|
13
13
|
`);return{msgId:`memory-recurrence-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:t,content:`\u6709 ${n.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\u5F97\u6574\u7406\uFF0C\u8BF7\u4F7F\u7528 memory_recurrence(limit=10) \u5206\u6279\u5904\u7406\uFF08\u6700\u65E9\u8FC7\u671F\u4F18\u5148\uFF0C\u6BCF\u6B21 10 \u6761\uFF09\u3002
|
|
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,
|
|
17
|
+
...\u53CA\u5176\u4ED6 ${n.length-5} \u6761`:""}`,requireReply:!1,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:n.map(s=>s.uri)}}}return null}catch(e){return a.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(a.info(`[McpServer] buildMemoryFileChangeNotification \u88AB\u8C03\u7528: hasRoleName=${this.hasRoleName}, agentId=${this.getCurrentAgentId()??"(\u7A7A)"}`),!this.hasRoleName)return a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u8DF3\u8FC7: hasRoleName=false"),null;try{const e=this.getCurrentAgentId();if(!e)return a.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 k=r.find(E=>E.id===S.memoryId);k&&await this.memoryStore.updateMemory({uri:k.uri,memory_type:"project_memory",memory_status:"updating",updating_agent_id:e,file_md5_map:k.file_md5_map,last_check_time:k.last_check_time,bound_files:k.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,
|
|
23
|
-
`);return
|
|
22
|
+
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u6700\u65B0\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,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 k=r.find(E=>E.id===S.memoryId);return{uri:S.memoryUri,boundFiles:k?.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){a.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){a.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 a.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 C=M.slice(0,5).map(y=>`- ${y.uri} (\u6587\u4EF6: ${y.bound_files?.join(", ")||"\u672A\u77E5"})`).join(`
|
|
23
|
+
`);return a.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
|
+
${C}
|
|
26
26
|
|
|
27
|
-
\u8BF7\u7EE7\u7EED\u5B8C\u6210\u66F4\u65B0\u5DE5\u4F5C\u3002`,requireReply:!1,
|
|
27
|
+
\u8BF7\u7EE7\u7EED\u5B8C\u6210\u66F4\u65B0\u5DE5\u4F5C\u3002`,requireReply:!1,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],o=s.filter(c=>c.bound_files&&c.bound_files.length>0);a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: recently=${n.length}, agent=${i.length}, \u6709\u7ED1\u5B9A\u6587\u4EF6=${o.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;a.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){a.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){a.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,
|
|
32
|
+
\u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,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);a.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 T=new Map;for(const c of d)T.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=T.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){a.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){a.info(`[McpServer] \u56DE\u5199\u57FA\u7EBF MD5 \u5931\u8D25: ${c.uri}`,g)}}if(a.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(_){a.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 C=_.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
|
+
${C}
|
|
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,
|
|
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,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 a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u5B8C\u6210: \u65E0\u53D8\u66F4\u901A\u77E5\u4EA7\u751F"),null}catch(e){return a.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 o=r[i];o?o!==s?t.push({path:i,status:"content_changed",localMd5:s,serverMd5:o}):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={},o=r.file_md5_map||{},l=process.env.AWS_WORKSPACE_PATH||process.cwd(),m=Object.keys(o).length===0;let u=!1;for(const h of r.bound_files)try{const d=await this.calculateFileMd5(h);if(d===null){const T=f.resolve(l,h);a.info(`[McpServer] MD5 \u8BA1\u7B97\u8FD4\u56DE null: ${h} -> ${T} (workspace=${l})`);const R=o[h];R?await this.verifyFileNotExist(T)?(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))):(a.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=o[h];p?p!==d?(a.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,a.info(`[McpServer] \u5EFA\u7ACB\u5355\u6587\u4EF6\u57FA\u7EBF: ${h} new=${d} (workspace=${l}, memory=${r.uri})`))}catch(d){a.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){a.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),o=f.resolve(i);return!o.startsWith(s+f.sep)&&o!==s?(a.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 a.info(`[McpServer] \u8BA1\u7B97\u6587\u4EF6 MD5 \u5931\u8D25: ${e}`,t),null}}async verifyFileNotExist(e){try{return await $.promises.access(e,$.constants.F_OK),!1}catch{return!0}}async handleGetMessage(e=!1){if(e){const s=this.messageBuffer.takeForceDirectMessages();return this.buildPollResult(s)}const t=this.messageBuffer.size()>0;await this.hydrateUnreadMessagesFromServer();const r=this.messageBuffer.get(),n=r.directMessages.length>0||r.groupMessages.length>0,i=t||!n;return await this.prependMemoryNotification(r,()=>this.buildMemoryRecurrenceNotification())?this.buildPollResult(r,i):await this.prependMemoryNotification(r,()=>this.buildMemoryCleanupNotification())?this.buildPollResult(r,i):this.buildPollResult(r,i)}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)}),a.info(`[McpServer] \u5DF2\u8865\u62C9\u672A\u8BFB\u6D88\u606F\uFF0C\u79C1\u804A: ${r.length}\uFF0C\u7FA4\u804A: ${n.length}`),!0}catch(t){return a.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;a.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 a.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(),o=`\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:
|
|
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??""}
|
|
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:o,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)&&(a.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}a.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"?(a.warn("[McpServer] \u5FFD\u7565\u7ED3\u6784\u5F02\u5E38\u7684\u6D88\u606F\uFF0C\u907F\u514D\u7F13\u51B2\u533A\u6C61\u67D3"),!1):this.isSelfSentMessage(e)?(a.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)?(a.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 buildTodoBlockNotification(){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 $.promises.readFile(t,"utf-8")}catch(o){return o?.code==="ENOENT"||a.warn("[McpServer] \u8BFB\u53D6 Todo \u6587\u4EF6\u5931\u8D25\uFF0C\u653E\u884C poll_message: %s",o instanceof Error?o.message:String(o)),null}let n;try{n=JSON.parse(r)}catch(o){return a.warn("[McpServer] \u89E3\u6790 Todo \u6587\u4EF6\u5931\u8D25\uFF0C\u653E\u884C poll_message: %s",o instanceof Error?o.message:String(o)),null}return(Array.isArray(n.todos)?n.todos:[]).filter(o=>o&&typeof o=="object"&&typeof o.content=="string"&&(o.status==="pending"||o.status==="in_progress"||o.status==="cancelled")).length===0?null:{msgId:`todo-block-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:"\u5F53\u524D\u6709\u672A\u5904\u7406\u7684todo list\u4E0D\u80FD\u4F7F\u7528poll_message\u963B\u585E\u83B7\u53D6\u6D88\u606F",requireReply:!1,timestamp:new Date().toISOString()}}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 $.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 o=s.slice(0,5),l=o.map(u=>`[bg:${u.taskId}] ${u.status} (exitCode=${u.exitCode??"?"}) ${u.commandPreview??""}
|
|
43
42
|
lines=${u.outputLineCount??0} bytes=${u.outputBytes??0}
|
|
44
43
|
Use read_background_output(task_id="${u.taskId}", mode="tail", lines=50) for full output.`).join(`
|
|
45
44
|
|
|
46
|
-
`),m=s.length>
|
|
45
|
+
`),m=s.length>o.length?`
|
|
47
46
|
|
|
48
|
-
\uFF08\u53E6\u6709 ${s.length-
|
|
49
|
-
${l}${m}`,requireReply:!1,
|
|
47
|
+
\uFF08\u53E6\u6709 ${s.length-o.length} \u6761\u5DF2\u5B8C\u6210\uFF0C\u8BF7\u7528 ListBackgroundCommands \u67E5\u770B\uFF09`:"";return await $.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=>{a.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]
|
|
48
|
+
${l}${m}`,requireReply:!1,timestamp:new Date().toISOString()}}catch(e){return a.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(),a.info("[McpServer] MCP Server \u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u8FDE\u63A5..."),a.info(`[McpServer] \u53EF\u7528\u5DE5\u5177: ${D.map(t=>t.name).join(", ")}`),a.info(`[McpServer] \u5DE5\u4F5C\u76EE\u5F55: ${process.env.AWS_WORKSPACE_PATH||process.cwd()}`),L&&a.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(),a.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&&(a.warn("[McpServer] \u79DF\u7EA6\u5DF2\u4E22\u5931\uFF08renewed=0\uFF09\uFF0C\u505C\u6B62\u7EED\u671F\u5B9A\u65F6\u5668"),this.stopLeaseRenewal())}catch(e){a.warn("[McpServer] \u7EED\u671F\u79DF\u7EA6\u5931\u8D25: %s",e instanceof Error?e.message:String(e))}},v.LEASE_RENEW_INTERVAL_MS),a.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,a.info("[McpServer] \u79DF\u7EA6\u7EED\u671F\u5B9A\u65F6\u5668\u5DF2\u505C\u6B62"))}stop(){this.stopLeaseRenewal(),this.getCurrentAgentId()&&this.memoryStore.releaseAllMemoryClaims().catch(t=>a.warn("[McpServer] \u91CA\u653E claim \u5931\u8D25: %s",t instanceof Error?t.message:String(t))),this.agentClient.stop(),a.info("[McpServer] MCP Server \u5DF2\u505C\u6B62")}async autoRegister(){const e=this.agentClient.getAgentConfig();a.info(`[McpServer] \u89D2\u8272: "${e.roleName}"`),a.info(`[McpServer] AgentID: "${e.agentId||"(\u81EA\u52A8\u5206\u914D)"}"`),a.info("[McpServer] \u6B63\u5728\u8BF7\u6C42\u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C...");let t=0;for(;;)try{const r=await this.agentClient.register();a.info(`[McpServer] \u6CE8\u518C\u6210\u529F: ${r.displayName} (${r.agentId})`),a.info(`[McpServer] \u72B6\u6001: ${r.status}`),this.startListeningInternal(),a.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){a.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(),a.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}`)}a.warn(`[McpServer] \u6CE8\u518C\u5931\u8D25\uFF0C\u7B49\u5F85\u91CD\u8BD5... (${t}/${P}): ${n}`),await this.delay(Y)}}autoRegisterInBackground(){this.autoRegister().catch(e=>{a.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(){a.info("[McpServer] startListeningInternal \u88AB\u8C03\u7528");const e=t=>{a.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()){a.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,a.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=>{a.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};
|
|
50
49
|
|
|
@@ -1153,7 +1153,7 @@ describe("McpServer memory tools", () => {
|
|
|
1153
1153
|
assert.equal(result.directMessages[0]?.receiverId, "agent-self");
|
|
1154
1154
|
assert.match(result.directMessages[0]?.content ?? "", /本地 memory 已超过建议容量/);
|
|
1155
1155
|
assert.match(result.directMessages[0]?.content ?? "", /get_memory_stats/);
|
|
1156
|
-
assert.
|
|
1156
|
+
assert.equal(result.directMessages[0]?.prompt, undefined);
|
|
1157
1157
|
// 非紧急类在初始路径直接返回,不进入 while 循环
|
|
1158
1158
|
assert.equal(pollCallCount, 0);
|
|
1159
1159
|
});
|
|
@@ -1224,7 +1224,7 @@ describe("McpServer memory tools", () => {
|
|
|
1224
1224
|
assert.equal(result.directMessages[0]?.receiverId, "agent-self");
|
|
1225
1225
|
assert.match(result.directMessages[0]?.content ?? "", /本地 memory 已超过建议容量/);
|
|
1226
1226
|
assert.match(result.directMessages[0]?.content ?? "", /agent_memory\/recent 类型下当前 33 条 memory/);
|
|
1227
|
-
assert.
|
|
1227
|
+
assert.equal(result.directMessages[0]?.prompt, undefined);
|
|
1228
1228
|
});
|
|
1229
1229
|
});
|
|
1230
1230
|
it("rejects incomplete memory update arguments", async () => {
|
|
@@ -1241,6 +1241,48 @@ describe("McpServer memory tools", () => {
|
|
|
1241
1241
|
}), /old_string and new_string must be provided together/);
|
|
1242
1242
|
});
|
|
1243
1243
|
});
|
|
1244
|
+
it("get_message with _aws_force_inject returns only force messages and keeps normal ones", async () => {
|
|
1245
|
+
await withMemoryServer(async (server) => {
|
|
1246
|
+
const handleToolCall = getHandleToolCall(server);
|
|
1247
|
+
Reflect.set(server, "agentClient", {
|
|
1248
|
+
fetchUnreadMessages: async () => ({
|
|
1249
|
+
directMessages: [],
|
|
1250
|
+
groupMessages: [],
|
|
1251
|
+
}),
|
|
1252
|
+
getState: () => ({ agentId: "agent-self" }),
|
|
1253
|
+
getStatusReporter: () => null,
|
|
1254
|
+
});
|
|
1255
|
+
const messageBuffer = getMessageBuffer(server);
|
|
1256
|
+
messageBuffer.push({
|
|
1257
|
+
msgId: "dm-force-1",
|
|
1258
|
+
senderId: "human-1",
|
|
1259
|
+
senderName: "Human One",
|
|
1260
|
+
content: "紧急消息",
|
|
1261
|
+
requireReply: false,
|
|
1262
|
+
force: true,
|
|
1263
|
+
timestamp: new Date().toISOString(),
|
|
1264
|
+
});
|
|
1265
|
+
messageBuffer.push({
|
|
1266
|
+
msgId: "dm-normal-1",
|
|
1267
|
+
senderId: "agent-2",
|
|
1268
|
+
senderName: "Agent Two",
|
|
1269
|
+
content: "普通消息",
|
|
1270
|
+
requireReply: false,
|
|
1271
|
+
timestamp: new Date().toISOString(),
|
|
1272
|
+
});
|
|
1273
|
+
// runtime 迭代边界强制注入调用:只取 force=true 的私信
|
|
1274
|
+
const result = (await handleToolCall.call(server, "get_message", {
|
|
1275
|
+
_aws_force_inject: true,
|
|
1276
|
+
}));
|
|
1277
|
+
assert.equal(result.directMessages.length, 1);
|
|
1278
|
+
assert.equal(result.directMessages[0]?.msgId, "dm-force-1");
|
|
1279
|
+
assert.equal(result.groupMessages.length, 0);
|
|
1280
|
+
// 普通消息留在缓冲区,下一次 get_message 正常返回
|
|
1281
|
+
const rest = (await handleToolCall.call(server, "get_message", {}));
|
|
1282
|
+
assert.equal(rest.directMessages.length, 1);
|
|
1283
|
+
assert.equal(rest.directMessages[0]?.msgId, "dm-normal-1");
|
|
1284
|
+
});
|
|
1285
|
+
});
|
|
1244
1286
|
it("redacts memory content from status reporting", async () => {
|
|
1245
1287
|
await withMemoryServer(async (server) => {
|
|
1246
1288
|
const reported = [];
|
|
@@ -1976,7 +2018,7 @@ describe("AgentClient group room aggregation", () => {
|
|
|
1976
2018
|
assert.equal(result.count, 2);
|
|
1977
2019
|
});
|
|
1978
2020
|
});
|
|
1979
|
-
describe("McpServer todo
|
|
2021
|
+
describe("McpServer todo guard on poll_message", () => {
|
|
1980
2022
|
// 使用唯一 agentId 避免污染开发者真实 todo 文件;每次测试写入并清理
|
|
1981
2023
|
const testAgentId = `test-todo-reminder-${process.pid}-${Date.now()}`;
|
|
1982
2024
|
const todoDir = path.join(os.homedir(), ".acode", "todos");
|
|
@@ -2013,7 +2055,7 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2013
2055
|
timestamp: new Date().toISOString(),
|
|
2014
2056
|
};
|
|
2015
2057
|
}
|
|
2016
|
-
it("returns todo
|
|
2058
|
+
it("returns todo block message when incomplete todos exist", async () => {
|
|
2017
2059
|
await writeTodoFile([
|
|
2018
2060
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2019
2061
|
{ content: "任务B", status: "completed", priority: "low" },
|
|
@@ -2021,18 +2063,17 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2021
2063
|
try {
|
|
2022
2064
|
const server = createServerForTodo();
|
|
2023
2065
|
const handleToolCall = getHandleToolCall(server);
|
|
2066
|
+
// 有未完成 todo 时 poll_message 正常 JSON 返回提示消息(非异常)
|
|
2024
2067
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2025
|
-
// 无未读消息时,todo 单独返回(组合2)
|
|
2026
2068
|
assert.equal(result.directMessages.length, 1);
|
|
2027
2069
|
assert.equal(result.directMessages[0].senderId, "MCP-System");
|
|
2028
|
-
assert.equal(result.directMessages[0].
|
|
2029
|
-
assert.match(result.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2070
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2030
2071
|
}
|
|
2031
2072
|
finally {
|
|
2032
2073
|
await cleanupTodoFile();
|
|
2033
2074
|
}
|
|
2034
2075
|
});
|
|
2035
|
-
it("returns
|
|
2076
|
+
it("returns todo block message even with unread messages", async () => {
|
|
2036
2077
|
await writeTodoFile([
|
|
2037
2078
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2038
2079
|
]);
|
|
@@ -2043,18 +2084,16 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2043
2084
|
const msg = makeDirectMessage("msg-before-poll");
|
|
2044
2085
|
buffer.push(msg);
|
|
2045
2086
|
const handleToolCall = getHandleToolCall(server);
|
|
2087
|
+
// todo 前置检查在 hydrate 之前,即使有未读消息也先返回提示
|
|
2046
2088
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2047
|
-
|
|
2048
|
-
assert.equal(result.directMessages.
|
|
2049
|
-
assert.equal(result.directMessages[0].senderId, "MCP-System");
|
|
2050
|
-
assert.match(result.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2051
|
-
assert.equal(result.directMessages[1].msgId, "msg-before-poll");
|
|
2089
|
+
assert.equal(result.directMessages.length, 1);
|
|
2090
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2052
2091
|
}
|
|
2053
2092
|
finally {
|
|
2054
2093
|
await cleanupTodoFile();
|
|
2055
2094
|
}
|
|
2056
2095
|
});
|
|
2057
|
-
it("
|
|
2096
|
+
it("allows poll_message when all todos are completed", async () => {
|
|
2058
2097
|
await writeTodoFile([
|
|
2059
2098
|
{ content: "任务A", status: "completed", priority: "high" },
|
|
2060
2099
|
]);
|
|
@@ -2065,7 +2104,7 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2065
2104
|
setTimeout(() => buffer.push(wake), 50);
|
|
2066
2105
|
const handleToolCall = getHandleToolCall(server);
|
|
2067
2106
|
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2068
|
-
//
|
|
2107
|
+
// 全完成时无未完成 todo,进入 while 循环等待消息后返回
|
|
2069
2108
|
assert.equal(result.directMessages.length, 1);
|
|
2070
2109
|
assert.equal(result.directMessages[0].msgId, "wake-completed");
|
|
2071
2110
|
}
|
|
@@ -2073,119 +2112,58 @@ describe("McpServer todo reminder on poll_message", () => {
|
|
|
2073
2112
|
await cleanupTodoFile();
|
|
2074
2113
|
}
|
|
2075
2114
|
});
|
|
2076
|
-
it("
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
// 模拟外部消息到达后唤醒
|
|
2089
|
-
const buffer = getMessageBuffer(server);
|
|
2090
|
-
const wake = makeDirectMessage("wake-after-dedup");
|
|
2091
|
-
setTimeout(() => buffer.push(wake), 50);
|
|
2092
|
-
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2093
|
-
// 指纹去重生效:只返回真实消息,不再附带 todo 提醒
|
|
2094
|
-
assert.equal(result2.directMessages.length, 1);
|
|
2095
|
-
assert.equal(result2.directMessages[0].msgId, "wake-after-dedup");
|
|
2096
|
-
}
|
|
2097
|
-
finally {
|
|
2098
|
-
await cleanupTodoFile();
|
|
2099
|
-
}
|
|
2100
|
-
});
|
|
2101
|
-
it("re-injects todo reminder when todo fingerprint changes (new item added)", async () => {
|
|
2102
|
-
await writeTodoFile([
|
|
2103
|
-
{ content: "任务A", status: "pending", priority: "high" },
|
|
2104
|
-
]);
|
|
2105
|
-
try {
|
|
2106
|
-
const server = createServerForTodo();
|
|
2107
|
-
const handleToolCall = getHandleToolCall(server);
|
|
2108
|
-
// 第一次 poll:1 项未完成,返回 todo 提醒
|
|
2109
|
-
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2110
|
-
assert.equal(result1.directMessages.length, 1);
|
|
2111
|
-
assert.match(result1.directMessages[0].content, /Todo 列表存在 1 项未完成/);
|
|
2112
|
-
// 修改 todo:新增一项未完成,指纹变化
|
|
2113
|
-
await writeTodoFile([
|
|
2114
|
-
{ content: "任务A", status: "pending", priority: "high" },
|
|
2115
|
-
{ content: "任务B", status: "in_progress", priority: "medium" },
|
|
2116
|
-
]);
|
|
2117
|
-
// 第二次 poll:指纹变化,重新注入 todo 提醒
|
|
2118
|
-
const result2 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2119
|
-
assert.equal(result2.directMessages.length, 1);
|
|
2120
|
-
assert.match(result2.directMessages[0].content, /Todo 列表存在 2 项未完成/);
|
|
2121
|
-
}
|
|
2122
|
-
finally {
|
|
2123
|
-
await cleanupTodoFile();
|
|
2124
|
-
}
|
|
2115
|
+
it("allows poll_message when todo file is absent", async () => {
|
|
2116
|
+
// 确保文件不存在
|
|
2117
|
+
await cleanupTodoFile();
|
|
2118
|
+
const server = createServerForTodo();
|
|
2119
|
+
const buffer = getMessageBuffer(server);
|
|
2120
|
+
const wake = makeDirectMessage("wake-no-file");
|
|
2121
|
+
setTimeout(() => buffer.push(wake), 50);
|
|
2122
|
+
const handleToolCall = getHandleToolCall(server);
|
|
2123
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2124
|
+
// 无 todo 文件 -> 进入 while 循环等待消息后返回
|
|
2125
|
+
assert.equal(result.directMessages.length, 1);
|
|
2126
|
+
assert.equal(result.directMessages[0].msgId, "wake-no-file");
|
|
2125
2127
|
});
|
|
2126
|
-
it("
|
|
2128
|
+
it("returns todo block message when todo has in_progress status", async () => {
|
|
2127
2129
|
await writeTodoFile([
|
|
2128
|
-
{ content: "任务A", status: "
|
|
2130
|
+
{ content: "任务A", status: "in_progress", priority: "high" },
|
|
2129
2131
|
]);
|
|
2130
2132
|
try {
|
|
2131
2133
|
const server = createServerForTodo();
|
|
2132
2134
|
const handleToolCall = getHandleToolCall(server);
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
assert.
|
|
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 项未完成/);
|
|
2135
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2136
|
+
assert.equal(result.directMessages.length, 1);
|
|
2137
|
+
assert.equal(result.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2143
2138
|
}
|
|
2144
2139
|
finally {
|
|
2145
2140
|
await cleanupTodoFile();
|
|
2146
2141
|
}
|
|
2147
2142
|
});
|
|
2148
|
-
it("
|
|
2143
|
+
it("allows poll_message again after todos are all completed", async () => {
|
|
2149
2144
|
await writeTodoFile([
|
|
2150
2145
|
{ content: "任务A", status: "pending", priority: "high" },
|
|
2151
2146
|
]);
|
|
2152
2147
|
try {
|
|
2153
2148
|
const server = createServerForTodo();
|
|
2154
2149
|
const handleToolCall = getHandleToolCall(server);
|
|
2155
|
-
// 第一次 poll
|
|
2150
|
+
// 第一次 poll:有未完成项 -> 返回提示消息(正常 JSON)
|
|
2156
2151
|
const result1 = (await handleToolCall.call(server, "poll_message", {}));
|
|
2157
|
-
assert.
|
|
2158
|
-
|
|
2152
|
+
assert.equal(result1.directMessages.length, 1);
|
|
2153
|
+
assert.equal(result1.directMessages[0].content, "当前有未处理的todo list不能使用poll_message阻塞获取消息");
|
|
2154
|
+
// 全部完成后:poll_message 恢复正常阻塞,等消息返回
|
|
2159
2155
|
await writeTodoFile([
|
|
2160
2156
|
{ content: "任务A", status: "completed", priority: "high" },
|
|
2161
2157
|
]);
|
|
2162
2158
|
const buffer = getMessageBuffer(server);
|
|
2163
|
-
const
|
|
2164
|
-
setTimeout(() => buffer.push(
|
|
2165
|
-
const
|
|
2166
|
-
assert.equal(
|
|
2167
|
-
|
|
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 项未完成/);
|
|
2159
|
+
const wake = makeDirectMessage("wake-after-complete");
|
|
2160
|
+
setTimeout(() => buffer.push(wake), 50);
|
|
2161
|
+
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2162
|
+
assert.equal(result.directMessages.length, 1);
|
|
2163
|
+
assert.equal(result.directMessages[0].msgId, "wake-after-complete");
|
|
2173
2164
|
}
|
|
2174
2165
|
finally {
|
|
2175
2166
|
await cleanupTodoFile();
|
|
2176
2167
|
}
|
|
2177
2168
|
});
|
|
2178
|
-
it("does not inject todo reminder when todo file is absent", async () => {
|
|
2179
|
-
// 确保文件不存在
|
|
2180
|
-
await cleanupTodoFile();
|
|
2181
|
-
const server = createServerForTodo();
|
|
2182
|
-
const buffer = getMessageBuffer(server);
|
|
2183
|
-
const wake = makeDirectMessage("wake-no-file");
|
|
2184
|
-
setTimeout(() => buffer.push(wake), 50);
|
|
2185
|
-
const handleToolCall = getHandleToolCall(server);
|
|
2186
|
-
const result = (await handleToolCall.call(server, "poll_message", {}));
|
|
2187
|
-
// 无 todo 文件 -> 进入 while 循环等待消息后返回
|
|
2188
|
-
assert.equal(result.directMessages.length, 1);
|
|
2189
|
-
assert.equal(result.directMessages[0].msgId, "wake-no-file");
|
|
2190
|
-
});
|
|
2191
2169
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{logger as r}from"./logger.js";const
|
|
1
|
+
import{logger as r}from"./logger.js";const o=500;function h(n){if(!(n.msgId===void 0||n.msgId===null))return n.msgId}class u{buffer=[];resolvers=[];onMessageArrived=null;isPolling=!1;seenMsgIds=new Map;setOnMessageArrived(e){this.onMessageArrived=e}setIsPolling(e){this.isPolling=e}markSeen(e){const s=h(e);s!==void 0&&(this.seenMsgIds.set(s,Date.now()),this.cleanupSeenMsgIds())}isDuplicate(e){const s=h(e);return s===void 0?!1:this.seenMsgIds.has(s)?!0:(this.seenMsgIds.set(s,Date.now()),this.cleanupSeenMsgIds(),!1)}cleanupSeenMsgIds(){if(this.seenMsgIds.size<=o)return;let e=0;const s=this.seenMsgIds.size-o;for(const t of this.seenMsgIds.keys()){if(e>=s)break;this.seenMsgIds.delete(t),e++}}push(e){if(this.isDuplicate(e)){r.info(`[MessageBuffer] \u5FFD\u7565\u91CD\u590D\u6D88\u606F\uFF0CmsgId: ${e.msgId}`);return}this.buffer.push(e),r.info(`[MessageBuffer] \u6D88\u606F\u5165\u7F13\u51B2\u533A\uFF0C\u5F53\u524D\u6570\u91CF: ${this.buffer.length}`),this.isPolling&&this.onMessageArrived&&(r.info("[MessageBuffer] \u68C0\u6D4B\u5230 poll \u7B49\u5F85\u4E2D\uFF0C\u89E6\u53D1\u6D88\u606F\u5230\u8FBE\u56DE\u8C03"),this.onMessageArrived()),this.tryDispatch()}setProfileMessage(e){r.info(`[MessageBuffer] \u6536\u5230 Profile \u63A8\u9001\uFF0CdisplayName: ${e.displayName}\u3002\u8EAB\u4EFD\u4FE1\u606F\u8BF7\u901A\u8FC7 get_profile \u83B7\u53D6\u3002`)}removeProfileMessage(){r.info("[MessageBuffer] removeProfileMessage \u88AB\u8C03\u7528\uFF0C\u5F53\u524D\u65E0\u9700\u79FB\u9664\u672A\u8BFB\u8EAB\u4EFD\u6D88\u606F")}hasProfileMessage(){return!1}wakeUpWaiters(){if(this.tryDispatch(),this.buffer.length===0&&this.resolvers.length>0){const e=this.resolvers.shift();e&&(this.updatePollingState(),r.info("[MessageBuffer] wakeUpWaiters: \u5524\u9192 poll \u8D70 hydrate \u91CD\u67E5"),e({directMessages:[],groupMessages:[]}))}}getProfileMessage(){return null}updatePollingState(){this.isPolling=this.resolvers.length>0}poll(e){if(r.info(`[MessageBuffer] poll() \u88AB\u8C03\u7528\uFF0Cbuffer: ${this.buffer.length}${e?`\uFF0C\u8D85\u65F6: ${e}ms`:""}`),this.buffer.length>0){const s=this.consumeAll();return r.info(`[MessageBuffer] poll \u7ACB\u5373\u8FD4\u56DE\uFF0C\u79C1\u804A: ${s.directMessages.length}\uFF0C\u7FA4\u804A: ${s.groupMessages.length}`),Promise.resolve(s)}return this.resolvers.length===0&&(this.isPolling=!0),new Promise(s=>{let t=null,l=!1;const f=i=>{if(l)return;l=!0;const g=this.resolvers.indexOf(f);g>-1&&this.resolvers.splice(g,1),t&&(clearTimeout(t),t=null),this.updatePollingState(),r.info(`[MessageBuffer] poll \u6536\u5230\u6D88\u606F\uFF0C\u79C1\u804A: ${i.directMessages.length}\uFF0C\u7FA4\u804A: ${i.groupMessages.length}`),s(i)};this.resolvers.push(f),this.tryDispatch(),e&&e>0&&(t=setTimeout(()=>{if(l)return;l=!0;const i=this.resolvers.indexOf(f);i>-1&&this.resolvers.splice(i,1),this.updatePollingState(),r.info(`[MessageBuffer] poll \u8D85\u65F6\uFF08${e}ms\uFF09\uFF0C\u8FD4\u56DE\u7A7A\u6D88\u606F`),s({directMessages:[],groupMessages:[]})},e))})}get(){if(this.buffer.length>0){const e=this.consumeAll();return r.info(`[MessageBuffer] get \u8FD4\u56DE\u6D88\u606F\uFF0C\u79C1\u804A: ${e.directMessages.length}\uFF0C\u7FA4\u804A: ${e.groupMessages.length}`),e}return{directMessages:[],groupMessages:[]}}takeForceDirectMessages(){const e=[],s=[];for(const t of this.buffer)this.isDirectMessage(t)&&t.force===!0?(this.markSeen(t),e.push(t)):s.push(t);return this.buffer=s,e.length>0&&(r.info(`[MessageBuffer] takeForceDirectMessages \u53D6\u8D70\u5F3A\u5236\u79C1\u4FE1 ${e.length} \u6761\uFF0C\u5269\u4F59 ${s.length} \u6761`),this.tryDispatch()),{directMessages:e,groupMessages:[]}}size(){return this.buffer.length}clear(){this.buffer=[];for(const e of this.resolvers)e({directMessages:[],groupMessages:[]});this.resolvers=[],this.isPolling=!1,this.seenMsgIds.clear()}tryDispatch(){for(;this.resolvers.length>0&&this.buffer.length>0;){const e=this.resolvers.shift();if(e){const s=this.consumeAll();e(s)}}this.updatePollingState()}consumeAll(){const e=[],s=[];for(;this.buffer.length>0;){const t=this.buffer.shift();t&&(this.markSeen(t),this.isDirectMessage(t)?e.push(t):s.push(t))}return{directMessages:e,groupMessages:s}}isDirectMessage(e){return typeof e.msgId=="string"}}export{u as MessageBuffer};
|
|
2
2
|
|
|
@@ -40,4 +40,43 @@ describe('MessageBuffer poll', () => {
|
|
|
40
40
|
assert.equal(messages.groupMessages.length, 1);
|
|
41
41
|
assert.deepEqual(messages.groupMessages[0], groupMessage);
|
|
42
42
|
});
|
|
43
|
+
it('takeForceDirectMessages only takes force=true direct messages and keeps others', () => {
|
|
44
|
+
const buffer = new MessageBuffer();
|
|
45
|
+
const forcedMessage = {
|
|
46
|
+
msgId: 'dm-force-1',
|
|
47
|
+
senderId: 'human-1',
|
|
48
|
+
senderName: 'Human One',
|
|
49
|
+
content: '紧急:先停下处理这个',
|
|
50
|
+
requireReply: false,
|
|
51
|
+
force: true,
|
|
52
|
+
timestamp: new Date().toISOString()
|
|
53
|
+
};
|
|
54
|
+
const normalMessage = {
|
|
55
|
+
msgId: 'dm-normal-1',
|
|
56
|
+
senderId: 'agent-2',
|
|
57
|
+
senderName: 'Agent Two',
|
|
58
|
+
content: '普通消息',
|
|
59
|
+
requireReply: false,
|
|
60
|
+
timestamp: new Date().toISOString()
|
|
61
|
+
};
|
|
62
|
+
const groupMessage = {
|
|
63
|
+
msgId: 2,
|
|
64
|
+
senderId: 'human-1',
|
|
65
|
+
senderName: 'Human One',
|
|
66
|
+
content: '群消息',
|
|
67
|
+
timestamp: new Date().toISOString()
|
|
68
|
+
};
|
|
69
|
+
buffer.push(normalMessage);
|
|
70
|
+
buffer.push(groupMessage);
|
|
71
|
+
buffer.push(forcedMessage);
|
|
72
|
+
const taken = buffer.takeForceDirectMessages();
|
|
73
|
+
assert.equal(taken.directMessages.length, 1);
|
|
74
|
+
assert.deepEqual(taken.directMessages[0], forcedMessage);
|
|
75
|
+
assert.deepEqual(taken.groupMessages, []);
|
|
76
|
+
const rest = buffer.get();
|
|
77
|
+
assert.equal(rest.directMessages.length, 1);
|
|
78
|
+
assert.deepEqual(rest.directMessages[0], normalMessage);
|
|
79
|
+
assert.equal(rest.groupMessages.length, 1);
|
|
80
|
+
assert.deepEqual(rest.groupMessages[0], groupMessage);
|
|
81
|
+
});
|
|
43
82
|
});
|