aws-runtime-bridge 1.9.120 → 1.9.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/AcodeSdkAdapter.d.ts +18 -0
- package/dist/adapter/AcodeSdkAdapter.js +5 -5
- package/dist/adapter/types.d.ts +4 -0
- package/dist/adapter/types.js +1 -1
- package/dist/browser/browser-ws-bridge.js +3 -3
- package/dist/browser/built-in-browser-tools.js +1 -1
- package/dist/desktop/built-in-desktop-tools.d.ts +49 -0
- package/dist/desktop/built-in-desktop-tools.js +2 -0
- package/dist/desktop/desktop-mcp-client.d.ts +47 -0
- package/dist/desktop/desktop-mcp-client.js +3 -0
- package/dist/index.js +1 -1
- package/dist/remote-desktop/index.js +2 -2
- package/dist/routes/dashboard.js +521 -17
- package/dist/routes/git.d.ts +39 -0
- package/dist/routes/git.js +33 -25
- package/dist/routes/instance.js +1 -1
- package/dist/routes/jdks.d.ts +2 -0
- package/dist/routes/jdks.js +2 -0
- package/dist/routes/launch.d.ts +6 -0
- package/dist/routes/launch.js +30 -0
- package/dist/routes/pty.js +3 -3
- package/dist/routes/runtime-binding.js +1 -1
- package/dist/routes/terminal.js +30 -30
- package/dist/services/access-control.d.ts +18 -0
- package/dist/services/access-control.js +5 -0
- package/dist/services/agent-process-manager.js +1 -1
- package/dist/services/auto-register.js +1 -1
- package/dist/services/change-cache-retention.d.ts +5 -0
- package/dist/services/change-cache-retention.js +2 -0
- package/dist/services/desktop-mcp-manager.d.ts +47 -0
- package/dist/services/desktop-mcp-manager.js +2 -0
- package/dist/services/instance-features.d.ts +6 -0
- package/dist/services/instance-features.js +1 -1
- package/dist/services/jdk-scanner.d.ts +20 -0
- package/dist/services/jdk-scanner.js +2 -0
- package/dist/services/launch-manager.d.ts +181 -0
- package/dist/services/launch-manager.js +12 -0
- package/dist/services/launch-metrics.d.ts +89 -0
- package/dist/services/launch-metrics.js +4 -0
- package/dist/services/launch-streams.d.ts +24 -0
- package/dist/services/launch-streams.js +11 -0
- package/dist/services/mcp-launch-binding-queue.js +1 -1
- package/dist/services/panel-auth.d.ts +11 -1
- package/dist/services/panel-auth.js +1 -1
- package/dist/services/panel-env.d.ts +5 -0
- package/dist/services/panel-env.js +2 -0
- package/dist/services/port-detector.d.ts +3 -0
- package/dist/services/port-detector.js +6 -0
- package/dist/services/runtime-lifecycle-policy.d.ts +1 -0
- package/dist/services/runtime-lifecycle-policy.js +1 -1
- package/dist/services/system-metrics.js +1 -1
- package/dist/services/tunnel-client.d.ts +4 -0
- package/dist/services/tunnel-client.js +1 -1
- package/dist/services/workspace-files.d.ts +3 -0
- package/dist/services/workspace-files.js +1 -1
- package/dist/services/workspaceTaskChangeWatcher.d.ts +2 -0
- package/dist/services/workspaceTaskChangeWatcher.js +3 -3
- package/dist/utils/config-write-queue.d.ts +3 -0
- package/dist/utils/config-write-queue.js +2 -0
- package/package/acode/dist/built-in-file-tools.d.ts +2 -0
- package/package/acode/dist/runtime.d.ts +20 -0
- package/package/acode/dist/runtime.js +27 -27
- package/package/acode/dist/sub-agent-manager.d.ts +4 -0
- package/package/acode/dist/sub-agent-manager.js +1 -1
- package/package/acode/dist/sub-agent-tools.d.ts +6 -0
- package/package/acode/dist/sub-agent-tools.js +39 -10
- package/package/acode/dist/sub-agent-types.d.ts +2 -0
- package/package/acode/dist/tool-allocation.d.ts +55 -0
- package/package/acode/dist/tool-allocation.js +2 -0
- package/package/acode/dist/types.d.ts +6 -0
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +24 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +19 -5
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +6 -6
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/index.js +1 -1
- package/package/aws-client-agent-mcp/dist/launch-config-tools.test.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +106 -0
- package/package/aws-client-agent-mcp/dist/logger.d.ts +4 -0
- package/package/aws-client-agent-mcp/dist/logger.js +2 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +25 -25
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +24 -16
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +20 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +8 -8
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +55 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +5 -1
- package/package/aws-client-agent-mcp/dist/parent-lifecycle.d.ts +10 -0
- package/package/aws-client-agent-mcp/dist/parent-lifecycle.js +2 -0
- package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/parent-lifecycle.test.js +81 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.js +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +4 -0
- package/package/aws-client-agent-mcp/dist/watchdog-worker.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/watchdog-worker.js +3 -0
- package/package/aws-client-agent-mcp/dist/watchdog.d.ts +17 -0
- package/package/aws-client-agent-mcp/dist/watchdog.js +2 -0
- package/package/aws-client-agent-mcp/dist/watchdog.test.d.ts +1 -0
- package/package/aws-client-agent-mcp/dist/watchdog.test.js +124 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +4 -0
- package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.test.js +83 -6
- package/package/aws-client-agent-mcp/package.json +1 -1
- package/package.json +2 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{randomUUID as S}from"node:crypto";import{ACodeRuntime as w}from"./runtime.js";const T=30*60*1e3,m=2*60*1e3,f=12*60*1e3,b=5e3,d=[100,500,2e3,8e3],E=6e4,A=10*60*1e3;class _{constructor(t){this.subAgents=new Map,this.parentToSubs=new Map,this.subscribers=new Map,this.cleanupTimer=null,this.completionQueue=[],this.completionWaiterResolve=null,this.pendingPersists=new Set,this.store=t.store,this.maxConcurrent=t.maxConcurrent??5,this.cleanupTimer=setInterval(()=>{this.cleanupCompletedEntries(),this.recoverZombieEntries()},5*60*1e3),this.cleanupTimer.unref&&this.cleanupTimer.unref(),this.store.reconcileZombies().catch(()=>{})}async spawn(t){if(this.countByStatus(t.parentAgentId,"running","pending")>=this.maxConcurrent)throw new Error(`concurrent sub-agent limit reached (${this.maxConcurrent}) for agent ${t.parentAgentId}`);const s=`sub-${S()}`,n=Date.now(),i=new AbortController,o={...t.parentConfig,sessionId:s,workingDirectory:t.workingDirectory,agentId:`${t.parentAgentId}__sub__${s}`,initialPrompt:void 0,subAgentManager:void 0,subagentType:t.subagentType,...t.parentConfig.subAgentModel?{model:t.parentConfig.subAgentModel}:{},...t.parentConfig.subAgentEffort?{effort:t.parentConfig.subAgentEffort}:{},subAgentModel:void 0,subAgentEffort:void 0},r=new w(o),a={subId:s,parentAgentId:t.parentAgentId,parentSessionId:t.parentSessionId,subagentType:t.subagentType,description:t.description,prompt:t.prompt,status:"pending",startedAt:n,runtime:r,abortController:i,silent:t.silent,contextSnapshot:{tokenUsage:{input:0,output:0},messageCount:0,updatedAt:new Date().toISOString()},availableTools:[]};this.subAgents.set(s,a),this.addToParentMap(t.parentAgentId,s);const u=this.toRecord(a);return await this.store.upsert(t.parentAgentId,u),r.on("event",l=>{this.handleRuntimeEvent(s,l)}),this.emitEvent(s,"spawned",{}),s}async start(t){const e=this.subAgents.get(t);if(!e)throw new Error(`sub-agent not found: ${t}`);if(e.status!=="pending")throw new Error(`sub-agent ${t} cannot start from status: ${e.status}`);e.completionPromise=new Promise((s,n)=>{e.completionResolve=s,e.completionReject=n}),this.runEntryToCompletion(e).catch(()=>{})}async startWithRecheck(t,e,s=3){const n=this.subAgents.get(t);if(!n)throw new Error(`sub-agent not found: ${t}`);if(n.status!=="pending")throw new Error(`sub-agent ${t} cannot start from status: ${n.status}`);n.completionPromise=new Promise((i,o)=>{n.completionResolve=i,n.completionReject=o}),this.runEntryToCompletionWithRecheck(n,e,s).catch(()=>{})}async awaitCompletion(t,e=0){if(t.length===0)return[];const s=[],n=t.map(async o=>{const r=this.subAgents.get(o);if(!r){const u=await this.findRecordInStore(o);u?s.push({subId:o,output:u.result??"",error:u.errorMessage,status:u.status}):s.push({subId:o,output:"",error:`sub-agent not found: ${o}`,status:"failed"});return}r.awaited=!0,r.status==="pending"&&await this.start(o);const a=r.completionPromise??Promise.resolve({output:"",error:"not started"});try{const u=await a;s.push({subId:o,output:u.output,error:u.error,status:r.status})}catch(u){s.push({subId:o,output:"",error:u instanceof Error?u.message:String(u),status:r.status})}}),i=new Set(t);for(let o=this.completionQueue.length-1;o>=0;o--)i.has(this.completionQueue[o].subId)&&this.completionQueue.splice(o,1);return e>0?await Promise.race([Promise.allSettled(n),new Promise(o=>setTimeout(()=>o(),e))]):await Promise.allSettled(n),s}async runEntryToCompletion(t){const e=[];let s=!1,n;const i=o=>{if(o)switch(o.type){case"assistant_delta":typeof o.data?.text=="string"&&e.push(o.data.text);break;case"turn_complete":o.data?.failed&&(s=!0);break;case"error":typeof o.data?.message=="string"&&(n=o.data.message);break}};t.runtime.on("event",i),t.status="running",t.contextSnapshot.currentTool="[starting]",t.contextSnapshot.updatedAt=new Date().toISOString(),this.emitEvent(t.subId,"running",{snapshot:{...t.contextSnapshot}});try{await c(this.store.updateStatus(t.parentAgentId,t.subId,{status:"running"}),b,`sub-agent ${t.subId} store.updateStatus(running)`)}catch(o){console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${t.subId}: ${o instanceof Error?o.message:String(o)}`)}try{if(await t.runtime.start(),t.availableTools=t.runtime.getAvailableTools(),await t.runtime.submit(t.prompt),s)throw new Error(n??"sub-agent turn failed");const o=e.join("");t.status="completed",t.result=o,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"completed",{result:o}),t.completionResolve?.({output:o}),this.enqueueCompletion(t,o,void 0),this.persistFinalStatus(t,{status:"completed",result:o,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}catch(o){const r=o instanceof Error?o.message:String(o);t.status="failed",t.errorMessage=r,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:r}),t.completionResolve?.({output:"",error:r}),this.enqueueCompletion(t,"",r),this.persistFinalStatus(t,{status:"failed",errorMessage:r,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}finally{try{t.runtime.removeListener?.("event",i)}catch{}try{await t.runtime.stop()}catch{}}}async runEntryToCompletionWithRecheck(t,e,s){const n=[];let i=!1,o;const r=a=>{if(a)switch(a.type){case"assistant_delta":typeof a.data?.text=="string"&&n.push(a.data.text);break;case"turn_complete":a.data?.failed&&(i=!0);break;case"error":typeof a.data?.message=="string"&&(o=a.data.message);break}};t.runtime.on("event",r),t.status="running",t.contextSnapshot.currentTool="[starting]",t.contextSnapshot.updatedAt=new Date().toISOString(),this.emitEvent(t.subId,"running",{snapshot:{...t.contextSnapshot}});try{await c(this.store.updateStatus(t.parentAgentId,t.subId,{status:"running"}),b,`sub-agent ${t.subId} store.updateStatus(running)`)}catch(a){console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${t.subId}: ${a instanceof Error?a.message:String(a)}`)}try{if(await c(t.runtime.start(),E,`sub-agent ${t.subId} runtime.start()`),t.contextSnapshot.currentTool="[submitting]",t.contextSnapshot.updatedAt=new Date().toISOString(),t.availableTools=t.runtime.getAvailableTools(),this.emitEvent(t.subId,"progress",{snapshot:{...t.contextSnapshot}}),await c(t.runtime.submit(t.prompt),A,`sub-agent ${t.subId} runtime.submit() turn=1`),i)throw new Error(o??"sub-agent turn failed");let a=1;for(;a<=s;){let l;try{l=await e(n.join(""),a)}catch(g){console.error(`[SubAgentManager] recheck threw error for subId=${t.subId} turn=${a}, treating as done:`,g instanceof Error?g.message:String(g));break}if(l.done)break;const h=l.continueMessage??"\u7ECF\u68C0\u67E5\uFF0C\u672A\u5B8C\u6210\u5904\u7406\uFF0C\u8BF7\u7EE7\u7EED\u3002";if(i=!1,o=void 0,console.error(`[SubAgentManager] recheck not done for subId=${t.subId} turn=${a}/${s}, injecting continue message (${h.length} chars)`),await c(t.runtime.submit(h),A,`sub-agent ${t.subId} runtime.submit() recheck turn=${a+1}`),i)throw new Error(o??`sub-agent turn ${a+1} failed during recheck continuation`);a++}a>s&&console.error(`[SubAgentManager] recheck reached maxRetries=${s} for subId=${t.subId}, completing with potentially unfinished work`);const u=n.join("");t.status="completed",t.result=u,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"completed",{result:u}),t.completionResolve?.({output:u}),this.enqueueCompletion(t,u,void 0),this.persistFinalStatus(t,{status:"completed",result:u,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}catch(a){const u=a instanceof Error?a.message:String(a);t.status="failed",t.errorMessage=u,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:u}),t.completionResolve?.({output:"",error:u}),this.enqueueCompletion(t,"",u),this.persistFinalStatus(t,{status:"failed",errorMessage:u,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}finally{try{t.runtime.removeListener?.("event",r)}catch{}try{await t.runtime.stop()}catch{}}}async runToCompletion(t){const e=this.subAgents.get(t);if(!e)throw new Error(`sub-agent not found: ${t}`);if(e.status==="running")throw new Error(`sub-agent already running: ${t}`);return e.status!=="pending"&&(e.status="pending"),await this.start(t),await e.completionPromise??{output:"",error:"not started"}}async abort(t){const e=this.subAgents.get(t);if(e&&!(e.status==="completed"||e.status==="failed"||e.status==="cancelled")){e.abortController.abort();try{await e.runtime.abort()}catch{}e.status="cancelled",e.endedAt=Date.now(),e.durationMs=e.endedAt-e.startedAt,this.emitEvent(t,"cancelled",{}),e.completionResolve?.({output:"",error:"aborted"}),this.enqueueCompletion(e,"","aborted"),this.persistFinalStatus(e,{status:"cancelled",endedAt:e.endedAt,durationMs:e.durationMs,availableTools:e.availableTools,contextSnapshot:void 0})}}getSubAgent(t){return this.subAgents.get(t)}listByParent(t){const e=this.parentToSubs.get(t);return e?Array.from(e).map(s=>this.subAgents.get(s)).filter(s=>s!==void 0):[]}listActiveByParent(t){return this.listByParent(t).filter(e=>(e.status==="pending"||e.status==="running")&&!e.silent)}takeCompletedSubAgents(){return this.completionQueue.length===0?[]:this.completionQueue.splice(0)}waitForAnyCompletion(){if(this.completionQueue.length>0){const n=this.completionQueue.shift();return{promise:Promise.resolve(n),cancel:()=>{}}}let t=!1;return{promise:new Promise(n=>{this.completionWaiterResolve=i=>{t||n(i)}}),cancel:()=>{t=!0,this.completionWaiterResolve=null}}}subscribe(t,e){let s=this.subscribers.get(t);return s||(s=new Set,this.subscribers.set(t,s)),s.add(e),()=>{s?.delete(e),s&&s.size===0&&this.subscribers.delete(t)}}async dispose(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null);const t=Array.from(this.subAgents.values());await Promise.allSettled(t.map(e=>this.abort(e.subId))),await this.flushPendingPersists(),this.subAgents.clear(),this.parentToSubs.clear(),this.subscribers.clear()}persistFinalStatus(t,e){const s=(async()=>{let n;for(let i=0;i<=d.length;i++)try{await this.store.updateStatus(t.parentAgentId,t.subId,e);return}catch(o){n=o,i<d.length&&await new Promise(r=>setTimeout(r,d[i]))}console.error(`[SubAgentManager] persistFinalStatus(${e.status}) failed after ${d.length+1} attempts for subId=${t.subId}: ${n instanceof Error?n.message:String(n)}`)})();return this.pendingPersists.add(s),s.finally(()=>this.pendingPersists.delete(s)),s}async flushPendingPersists(){this.pendingPersists.size!==0&&await Promise.allSettled(Array.from(this.pendingPersists))}enqueueCompletion(t,e,s){if(t.silent||t.awaited)return;const n={subId:t.subId,subagentType:t.subagentType,description:t.description,status:t.status,output:e,error:s,durationMs:t.durationMs};if(this.completionQueue.push(n),this.completionWaiterResolve){const i=this.completionWaiterResolve;this.completionWaiterResolve=null;const o=this.completionQueue.pop();i(o??n)}}countByStatus(t,...e){return this.listByParent(t).filter(s=>e.includes(s.status)).length}addToParentMap(t,e){let s=this.parentToSubs.get(t);s||(s=new Set,this.parentToSubs.set(t,s)),s.add(e)}removeFromParentMap(t,e){const s=this.parentToSubs.get(t);s&&(s.delete(e),s.size===0&&this.parentToSubs.delete(t))}cleanupCompletedEntries(){const t=Date.now();for(const[e,s]of this.subAgents)(s.status==="completed"||s.status==="failed"||s.status==="cancelled")&&s.endedAt&&t-s.endedAt>T&&(this.subAgents.delete(e),this.removeFromParentMap(s.parentAgentId,e))}recoverZombieEntries(){const t=Date.now();for(const[e,s]of this.subAgents)s.status==="pending"&&t-s.startedAt>m?this.recoverZombie(s,`stuck in pending >${m}ms (never started)`):s.status==="running"&&t-s.startedAt>f&&this.recoverZombie(s,`stuck in running >${f}ms (no completion)`)}async recoverZombie(t,e){if(t.status!=="pending"&&t.status!=="running")return;const s=`zombie recovered: ${e}`;t.status="failed",t.errorMessage=s,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:s}),t.completionResolve?.({output:"",error:s}),this.enqueueCompletion(t,"",s),this.persistFinalStatus(t,{status:"failed",errorMessage:s,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0});try{await t.runtime.stop()}catch{}}async findRecordInStore(t){for(const e of this.parentToSubs.keys()){const n=(await this.store.read(e)).subAgents.find(i=>i.subId===t);if(n)return n}}handleRuntimeEvent(t,e){const s=this.subAgents.get(t);if(!s)return;const n=s.contextSnapshot;switch(n.updatedAt=new Date().toISOString(),e?.type){case"session_started":n.currentTool="[starting]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"startup_phase":n.currentTool=`[startup:${e.data?.phase??"unknown"}]`,this.emitEvent(t,"progress",{snapshot:{...n}});break;case"mcp_status":e.data?.action==="call_tool"?(n.currentTool=e.data?.tool,this.emitEvent(t,"tool_start",{tool:n.currentTool,snapshot:{...n}})):e.data?.action==="tool_result"?(this.emitEvent(t,"tool_end",{tool:n.currentTool,snapshot:{...n}}),n.currentTool=void 0):(n.currentTool="[mcp_init]",this.emitEvent(t,"progress",{snapshot:{...n}}));break;case"skill_status":n.currentTool="[skill_loaded]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"ai_request":e.data?.estimatedContextTokens!=null&&(n.tokenUsage.input=e.data.estimatedContextTokens??n.tokenUsage.input),n.currentTool="[model_request]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"context_compaction":e.data?.action==="start"?(n.currentTool="[context_compaction]",this.emitEvent(t,"progress",{snapshot:{...n}})):e.data?.action==="end"&&n.currentTool==="[context_compaction]"&&(n.currentTool=void 0,this.emitEvent(t,"progress",{snapshot:{...n}}));break;case"turn_complete":e.data?.usage?(n.tokenUsage.input=e.data.usage.inputTokens??n.tokenUsage.input,n.tokenUsage.output=e.data.usage.outputTokens??n.tokenUsage.output,this.emitEvent(t,"progress",{snapshot:{...n}})):(e.data?.failed||e.data?.aborted)&&(n.tokenUsage.output=0);break;case"assistant_delta":n.lastAssistantText=((n.lastAssistantText??"")+(e.data?.text??"")).slice(-500),n.messageCount+=1,this.emitEvent(t,"delta",{text:e.data?.text});break;case"session_complete":n.messageCount+=1;break}}emitEvent(t,e,s){const n={type:e,subId:t,timestamp:new Date().toISOString(),data:s},i=this.subscribers.get(t);if(i)for(const r of i)try{r(n)}catch{}const o=this.subscribers.get("*");if(o)for(const r of o)try{r(n)}catch{}if(e==="tool_start"||e==="tool_end"){const r=this.subAgents.get(t);r?(console.error(`[SubAgentManager] appendEvent: subId=${t} type=${e} tool=${s.tool??"?"} parentAgentId=${r.parentAgentId}`),this.store.appendEvent(r.parentAgentId,t,n).catch(a=>{console.error(`[SubAgentManager] appendEvent FAILED: subId=${t} error=${a instanceof Error?a.message:String(a)}`)})):console.error(`[SubAgentManager] appendEvent SKIPPED (entry not found): subId=${t} type=${e}`)}}toRecord(t){return{subId:t.subId,parentAgentId:t.parentAgentId,parentSessionId:t.parentSessionId,subagentType:t.subagentType,description:t.description,prompt:t.prompt,status:t.status,result:t.result,errorMessage:t.errorMessage,startedAt:t.startedAt,endedAt:t.endedAt,durationMs:t.durationMs,contextSnapshot:t.status==="running"?t.contextSnapshot:void 0,availableTools:t.availableTools}}getContextSnapshot(t){return this.subAgents.get(t)?.contextSnapshot}}async function c(p,t,e){if(t<=0)return await p;let s;try{return await new Promise((n,i)=>{s=setTimeout(()=>i(new Error(`${e} timed out after ${t}ms`)),t),p.then(n,i)})}finally{s&&clearTimeout(s)}}export{_ as SubAgentManager};
|
|
1
|
+
import{randomUUID as S}from"node:crypto";import{ACodeRuntime as w}from"./runtime.js";const T=30*60*1e3,m=2*60*1e3,f=12*60*1e3,b=5e3,c=[100,500,2e3,8e3],E=6e4,A=10*60*1e3;class _{constructor(t){this.subAgents=new Map,this.parentToSubs=new Map,this.subscribers=new Map,this.cleanupTimer=null,this.completionQueue=[],this.completionWaiterResolve=null,this.pendingPersists=new Set,this.store=t.store,this.maxConcurrent=t.maxConcurrent??5,this.cleanupTimer=setInterval(()=>{this.cleanupCompletedEntries(),this.recoverZombieEntries()},5*60*1e3),this.cleanupTimer.unref&&this.cleanupTimer.unref(),this.store.reconcileZombies().catch(()=>{})}async spawn(t){if(this.countByStatus(t.parentAgentId,"running","pending")>=this.maxConcurrent)throw new Error(`concurrent sub-agent limit reached (${this.maxConcurrent}) for agent ${t.parentAgentId}`);const s=`sub-${S()}`,n=Date.now(),i=new AbortController,o={...t.parentConfig,sessionId:s,workingDirectory:t.workingDirectory,agentId:`${t.parentAgentId}__sub__${s}`,initialPrompt:void 0,subAgentManager:void 0,subagentType:t.subagentType,subAgentAllowedTools:t.allowedTools&&t.allowedTools.length>0?t.allowedTools:void 0,...t.parentConfig.subAgentModel?{model:t.parentConfig.subAgentModel}:{},...t.parentConfig.subAgentEffort?{effort:t.parentConfig.subAgentEffort}:{},subAgentModel:void 0,subAgentEffort:void 0},r=new w(o),a={subId:s,parentAgentId:t.parentAgentId,parentSessionId:t.parentSessionId,subagentType:t.subagentType,description:t.description,prompt:t.prompt,status:"pending",startedAt:n,runtime:r,abortController:i,silent:t.silent,contextSnapshot:{tokenUsage:{input:0,output:0},messageCount:0,updatedAt:new Date().toISOString()},availableTools:[],...t.allowedTools&&t.allowedTools.length>0?{allocatedTools:t.allowedTools}:{}};this.subAgents.set(s,a),this.addToParentMap(t.parentAgentId,s);const u=this.toRecord(a);return await this.store.upsert(t.parentAgentId,u),r.on("event",l=>{this.handleRuntimeEvent(s,l)}),this.emitEvent(s,"spawned",{}),s}async start(t){const e=this.subAgents.get(t);if(!e)throw new Error(`sub-agent not found: ${t}`);if(e.status!=="pending")throw new Error(`sub-agent ${t} cannot start from status: ${e.status}`);e.completionPromise=new Promise((s,n)=>{e.completionResolve=s,e.completionReject=n}),this.runEntryToCompletion(e).catch(()=>{})}async startWithRecheck(t,e,s=3){const n=this.subAgents.get(t);if(!n)throw new Error(`sub-agent not found: ${t}`);if(n.status!=="pending")throw new Error(`sub-agent ${t} cannot start from status: ${n.status}`);n.completionPromise=new Promise((i,o)=>{n.completionResolve=i,n.completionReject=o}),this.runEntryToCompletionWithRecheck(n,e,s).catch(()=>{})}async awaitCompletion(t,e=0){if(t.length===0)return[];const s=[],n=t.map(async o=>{const r=this.subAgents.get(o);if(!r){const u=await this.findRecordInStore(o);u?s.push({subId:o,output:u.result??"",error:u.errorMessage,status:u.status}):s.push({subId:o,output:"",error:`sub-agent not found: ${o}`,status:"failed"});return}r.awaited=!0,r.status==="pending"&&await this.start(o);const a=r.completionPromise??Promise.resolve({output:"",error:"not started"});try{const u=await a;s.push({subId:o,output:u.output,error:u.error,status:r.status})}catch(u){s.push({subId:o,output:"",error:u instanceof Error?u.message:String(u),status:r.status})}}),i=new Set(t);for(let o=this.completionQueue.length-1;o>=0;o--)i.has(this.completionQueue[o].subId)&&this.completionQueue.splice(o,1);return e>0?await Promise.race([Promise.allSettled(n),new Promise(o=>setTimeout(()=>o(),e))]):await Promise.allSettled(n),s}async runEntryToCompletion(t){const e=[];let s=!1,n;const i=o=>{if(o)switch(o.type){case"assistant_delta":typeof o.data?.text=="string"&&e.push(o.data.text);break;case"turn_complete":o.data?.failed&&(s=!0);break;case"error":typeof o.data?.message=="string"&&(n=o.data.message);break}};t.runtime.on("event",i),t.status="running",t.contextSnapshot.currentTool="[starting]",t.contextSnapshot.updatedAt=new Date().toISOString(),this.emitEvent(t.subId,"running",{snapshot:{...t.contextSnapshot}});try{await d(this.store.updateStatus(t.parentAgentId,t.subId,{status:"running"}),b,`sub-agent ${t.subId} store.updateStatus(running)`)}catch(o){console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${t.subId}: ${o instanceof Error?o.message:String(o)}`)}try{if(await t.runtime.start(),t.availableTools=t.runtime.getAvailableTools(),await t.runtime.submit(t.prompt),s)throw new Error(n??"sub-agent turn failed");const o=e.join("");t.status="completed",t.result=o,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"completed",{result:o}),t.completionResolve?.({output:o}),this.enqueueCompletion(t,o,void 0),this.persistFinalStatus(t,{status:"completed",result:o,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}catch(o){const r=o instanceof Error?o.message:String(o);t.status="failed",t.errorMessage=r,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:r}),t.completionResolve?.({output:"",error:r}),this.enqueueCompletion(t,"",r),this.persistFinalStatus(t,{status:"failed",errorMessage:r,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}finally{try{t.runtime.removeListener?.("event",i)}catch{}try{await t.runtime.stop()}catch{}}}async runEntryToCompletionWithRecheck(t,e,s){const n=[];let i=!1,o;const r=a=>{if(a)switch(a.type){case"assistant_delta":typeof a.data?.text=="string"&&n.push(a.data.text);break;case"turn_complete":a.data?.failed&&(i=!0);break;case"error":typeof a.data?.message=="string"&&(o=a.data.message);break}};t.runtime.on("event",r),t.status="running",t.contextSnapshot.currentTool="[starting]",t.contextSnapshot.updatedAt=new Date().toISOString(),this.emitEvent(t.subId,"running",{snapshot:{...t.contextSnapshot}});try{await d(this.store.updateStatus(t.parentAgentId,t.subId,{status:"running"}),b,`sub-agent ${t.subId} store.updateStatus(running)`)}catch(a){console.error(`[SubAgentManager] store.updateStatus(running) failed for subId=${t.subId}: ${a instanceof Error?a.message:String(a)}`)}try{if(await d(t.runtime.start(),E,`sub-agent ${t.subId} runtime.start()`),t.contextSnapshot.currentTool="[submitting]",t.contextSnapshot.updatedAt=new Date().toISOString(),t.availableTools=t.runtime.getAvailableTools(),this.emitEvent(t.subId,"progress",{snapshot:{...t.contextSnapshot}}),await d(t.runtime.submit(t.prompt),A,`sub-agent ${t.subId} runtime.submit() turn=1`),i)throw new Error(o??"sub-agent turn failed");let a=1;for(;a<=s;){let l;try{l=await e(n.join(""),a)}catch(g){console.error(`[SubAgentManager] recheck threw error for subId=${t.subId} turn=${a}, treating as done:`,g instanceof Error?g.message:String(g));break}if(l.done)break;const h=l.continueMessage??"\u7ECF\u68C0\u67E5\uFF0C\u672A\u5B8C\u6210\u5904\u7406\uFF0C\u8BF7\u7EE7\u7EED\u3002";if(i=!1,o=void 0,console.error(`[SubAgentManager] recheck not done for subId=${t.subId} turn=${a}/${s}, injecting continue message (${h.length} chars)`),await d(t.runtime.submit(h),A,`sub-agent ${t.subId} runtime.submit() recheck turn=${a+1}`),i)throw new Error(o??`sub-agent turn ${a+1} failed during recheck continuation`);a++}a>s&&console.error(`[SubAgentManager] recheck reached maxRetries=${s} for subId=${t.subId}, completing with potentially unfinished work`);const u=n.join("");t.status="completed",t.result=u,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"completed",{result:u}),t.completionResolve?.({output:u}),this.enqueueCompletion(t,u,void 0),this.persistFinalStatus(t,{status:"completed",result:u,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}catch(a){const u=a instanceof Error?a.message:String(a);t.status="failed",t.errorMessage=u,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:u}),t.completionResolve?.({output:"",error:u}),this.enqueueCompletion(t,"",u),this.persistFinalStatus(t,{status:"failed",errorMessage:u,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0})}finally{try{t.runtime.removeListener?.("event",r)}catch{}try{await t.runtime.stop()}catch{}}}async runToCompletion(t){const e=this.subAgents.get(t);if(!e)throw new Error(`sub-agent not found: ${t}`);if(e.status==="running")throw new Error(`sub-agent already running: ${t}`);return e.status!=="pending"&&(e.status="pending"),await this.start(t),await e.completionPromise??{output:"",error:"not started"}}async abort(t){const e=this.subAgents.get(t);if(e&&!(e.status==="completed"||e.status==="failed"||e.status==="cancelled")){e.abortController.abort();try{await e.runtime.abort()}catch{}e.status="cancelled",e.endedAt=Date.now(),e.durationMs=e.endedAt-e.startedAt,this.emitEvent(t,"cancelled",{}),e.completionResolve?.({output:"",error:"aborted"}),this.enqueueCompletion(e,"","aborted"),this.persistFinalStatus(e,{status:"cancelled",endedAt:e.endedAt,durationMs:e.durationMs,availableTools:e.availableTools,contextSnapshot:void 0})}}getSubAgent(t){return this.subAgents.get(t)}listByParent(t){const e=this.parentToSubs.get(t);return e?Array.from(e).map(s=>this.subAgents.get(s)).filter(s=>s!==void 0):[]}listActiveByParent(t){return this.listByParent(t).filter(e=>(e.status==="pending"||e.status==="running")&&!e.silent)}takeCompletedSubAgents(){return this.completionQueue.length===0?[]:this.completionQueue.splice(0)}waitForAnyCompletion(){if(this.completionQueue.length>0){const n=this.completionQueue.shift();return{promise:Promise.resolve(n),cancel:()=>{}}}let t=!1;return{promise:new Promise(n=>{this.completionWaiterResolve=i=>{t||n(i)}}),cancel:()=>{t=!0,this.completionWaiterResolve=null}}}subscribe(t,e){let s=this.subscribers.get(t);return s||(s=new Set,this.subscribers.set(t,s)),s.add(e),()=>{s?.delete(e),s&&s.size===0&&this.subscribers.delete(t)}}async dispose(){this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null);const t=Array.from(this.subAgents.values());await Promise.allSettled(t.map(e=>this.abort(e.subId))),await this.flushPendingPersists(),this.subAgents.clear(),this.parentToSubs.clear(),this.subscribers.clear()}persistFinalStatus(t,e){const s=(async()=>{let n;for(let i=0;i<=c.length;i++)try{await this.store.updateStatus(t.parentAgentId,t.subId,e);return}catch(o){n=o,i<c.length&&await new Promise(r=>setTimeout(r,c[i]))}console.error(`[SubAgentManager] persistFinalStatus(${e.status}) failed after ${c.length+1} attempts for subId=${t.subId}: ${n instanceof Error?n.message:String(n)}`)})();return this.pendingPersists.add(s),s.finally(()=>this.pendingPersists.delete(s)),s}async flushPendingPersists(){this.pendingPersists.size!==0&&await Promise.allSettled(Array.from(this.pendingPersists))}enqueueCompletion(t,e,s){if(t.silent||t.awaited)return;const n={subId:t.subId,subagentType:t.subagentType,description:t.description,status:t.status,output:e,error:s,durationMs:t.durationMs};if(this.completionQueue.push(n),this.completionWaiterResolve){const i=this.completionWaiterResolve;this.completionWaiterResolve=null;const o=this.completionQueue.pop();i(o??n)}}countByStatus(t,...e){return this.listByParent(t).filter(s=>e.includes(s.status)).length}addToParentMap(t,e){let s=this.parentToSubs.get(t);s||(s=new Set,this.parentToSubs.set(t,s)),s.add(e)}removeFromParentMap(t,e){const s=this.parentToSubs.get(t);s&&(s.delete(e),s.size===0&&this.parentToSubs.delete(t))}cleanupCompletedEntries(){const t=Date.now();for(const[e,s]of this.subAgents)(s.status==="completed"||s.status==="failed"||s.status==="cancelled")&&s.endedAt&&t-s.endedAt>T&&(this.subAgents.delete(e),this.removeFromParentMap(s.parentAgentId,e))}recoverZombieEntries(){const t=Date.now();for(const[e,s]of this.subAgents)s.status==="pending"&&t-s.startedAt>m?this.recoverZombie(s,`stuck in pending >${m}ms (never started)`):s.status==="running"&&t-s.startedAt>f&&this.recoverZombie(s,`stuck in running >${f}ms (no completion)`)}async recoverZombie(t,e){if(t.status!=="pending"&&t.status!=="running")return;const s=`zombie recovered: ${e}`;t.status="failed",t.errorMessage=s,t.endedAt=Date.now(),t.durationMs=t.endedAt-t.startedAt,this.emitEvent(t.subId,"failed",{error:s}),t.completionResolve?.({output:"",error:s}),this.enqueueCompletion(t,"",s),this.persistFinalStatus(t,{status:"failed",errorMessage:s,endedAt:t.endedAt,durationMs:t.durationMs,availableTools:t.availableTools,contextSnapshot:void 0});try{await t.runtime.stop()}catch{}}async findRecordInStore(t){for(const e of this.parentToSubs.keys()){const n=(await this.store.read(e)).subAgents.find(i=>i.subId===t);if(n)return n}}handleRuntimeEvent(t,e){const s=this.subAgents.get(t);if(!s)return;const n=s.contextSnapshot;switch(n.updatedAt=new Date().toISOString(),e?.type){case"session_started":n.currentTool="[starting]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"startup_phase":n.currentTool=`[startup:${e.data?.phase??"unknown"}]`,this.emitEvent(t,"progress",{snapshot:{...n}});break;case"mcp_status":e.data?.action==="call_tool"?(n.currentTool=e.data?.tool,this.emitEvent(t,"tool_start",{tool:n.currentTool,snapshot:{...n}})):e.data?.action==="tool_result"?(this.emitEvent(t,"tool_end",{tool:n.currentTool,snapshot:{...n}}),n.currentTool=void 0):(n.currentTool="[mcp_init]",this.emitEvent(t,"progress",{snapshot:{...n}}));break;case"skill_status":n.currentTool="[skill_loaded]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"ai_request":e.data?.estimatedContextTokens!=null&&(n.tokenUsage.input=e.data.estimatedContextTokens??n.tokenUsage.input),n.currentTool="[model_request]",this.emitEvent(t,"progress",{snapshot:{...n}});break;case"context_compaction":e.data?.action==="start"?(n.currentTool="[context_compaction]",this.emitEvent(t,"progress",{snapshot:{...n}})):e.data?.action==="end"&&n.currentTool==="[context_compaction]"&&(n.currentTool=void 0,this.emitEvent(t,"progress",{snapshot:{...n}}));break;case"turn_complete":e.data?.usage?(n.tokenUsage.input=e.data.usage.inputTokens??n.tokenUsage.input,n.tokenUsage.output=e.data.usage.outputTokens??n.tokenUsage.output,this.emitEvent(t,"progress",{snapshot:{...n}})):(e.data?.failed||e.data?.aborted)&&(n.tokenUsage.output=0);break;case"assistant_delta":n.lastAssistantText=((n.lastAssistantText??"")+(e.data?.text??"")).slice(-500),n.messageCount+=1,this.emitEvent(t,"delta",{text:e.data?.text});break;case"session_complete":n.messageCount+=1;break}}emitEvent(t,e,s){const n={type:e,subId:t,timestamp:new Date().toISOString(),data:s},i=this.subscribers.get(t);if(i)for(const r of i)try{r(n)}catch{}const o=this.subscribers.get("*");if(o)for(const r of o)try{r(n)}catch{}if(e==="tool_start"||e==="tool_end"){const r=this.subAgents.get(t);r?(console.error(`[SubAgentManager] appendEvent: subId=${t} type=${e} tool=${s.tool??"?"} parentAgentId=${r.parentAgentId}`),this.store.appendEvent(r.parentAgentId,t,n).catch(a=>{console.error(`[SubAgentManager] appendEvent FAILED: subId=${t} error=${a instanceof Error?a.message:String(a)}`)})):console.error(`[SubAgentManager] appendEvent SKIPPED (entry not found): subId=${t} type=${e}`)}}toRecord(t){return{subId:t.subId,parentAgentId:t.parentAgentId,parentSessionId:t.parentSessionId,subagentType:t.subagentType,description:t.description,prompt:t.prompt,status:t.status,result:t.result,errorMessage:t.errorMessage,startedAt:t.startedAt,endedAt:t.endedAt,durationMs:t.durationMs,contextSnapshot:t.status==="running"?t.contextSnapshot:void 0,availableTools:t.availableTools,allocatedTools:t.allocatedTools}}getContextSnapshot(t){return this.subAgents.get(t)?.contextSnapshot}}async function d(p,t,e){if(t<=0)return await p;let s;try{return await new Promise((n,i)=>{s=setTimeout(()=>i(new Error(`${e} timed out after ${t}ms`)),t),p.then(n,i)})}finally{s&&clearTimeout(s)}}export{_ as SubAgentManager};
|
|
2
2
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ACodeBuiltInTool } from "./built-in-file-tools.js";
|
|
2
2
|
import type { SubAgentType, SubAgentStatus } from "./sub-agent-types.js";
|
|
3
|
+
import type { ToolCatalogEntry } from "./tool-allocation.js";
|
|
3
4
|
|
|
4
5
|
export interface ACodeSubAgentToolDeps {
|
|
5
6
|
|
|
@@ -7,6 +8,8 @@ export interface ACodeSubAgentToolDeps {
|
|
|
7
8
|
subagentType: SubAgentType;
|
|
8
9
|
description: string;
|
|
9
10
|
prompt: string;
|
|
11
|
+
|
|
12
|
+
allowedTools?: string[];
|
|
10
13
|
}) => Promise<{
|
|
11
14
|
subId: string;
|
|
12
15
|
}>;
|
|
@@ -26,6 +29,8 @@ export interface ACodeSubAgentToolDeps {
|
|
|
26
29
|
}>>;
|
|
27
30
|
|
|
28
31
|
abortSubAgent: (subId: string) => Promise<void>;
|
|
32
|
+
|
|
33
|
+
listSubAgentToolCatalog: () => Promise<ToolCatalogEntry[]>;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export interface SubAgentInfo {
|
|
@@ -57,5 +62,6 @@ export declare function createSubAgentTool(deps: ACodeSubAgentToolDeps): ACodeBu
|
|
|
57
62
|
export declare function createListSubAgentsTool(deps: ACodeSubAgentToolDeps): ACodeBuiltInTool;
|
|
58
63
|
export declare function createGetSubAgentStatusTool(deps: ACodeSubAgentToolDeps): ACodeBuiltInTool;
|
|
59
64
|
export declare function createAbortSubAgentTool(deps: ACodeSubAgentToolDeps): ACodeBuiltInTool;
|
|
65
|
+
export declare function createListSubAgentToolsTool(deps: ACodeSubAgentToolDeps): ACodeBuiltInTool;
|
|
60
66
|
|
|
61
67
|
export declare function createSubAgentTools(deps: ACodeSubAgentToolDeps): ACodeBuiltInTool[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{resolveToolAllocation as p}from"./tool-allocation.js";const g=`Launch a new sub-agent to handle a task autonomously (non-blocking).
|
|
2
2
|
|
|
3
3
|
Use this tool to:
|
|
4
4
|
1. Delegate research, exploration, or implementation work to a specialized sub-agent
|
|
@@ -9,6 +9,19 @@ Available subagent types:
|
|
|
9
9
|
- "explore" (default): Read-only agent for codebase exploration (grep, glob, read, bash, web search)
|
|
10
10
|
- "general": Full-featured agent for executing multi-step tasks (file ops, bash, etc.)
|
|
11
11
|
|
|
12
|
+
Tool allocation (the 'tools' parameter):
|
|
13
|
+
- Omitted \u2192 read-only explore mode (default behavior)
|
|
14
|
+
- Templates (recommended for common cases, pass as string or array):
|
|
15
|
+
- "explore": read-only default (same as omitting tools)
|
|
16
|
+
- "general": all core default tools (read/write/edit/grep/Bash/terminal, NO suites/MCP)
|
|
17
|
+
\u2014 ideal for standard coding sub-agents with a focused context
|
|
18
|
+
- Custom allocation: each item is a category (whole suite, e.g. "office",
|
|
19
|
+
"browser", "desktop") or an exact tool path (e.g. "default.read_file").
|
|
20
|
+
Call list_sub_agent_tools to see all allocatable categories and tool paths.
|
|
21
|
+
- Templates can mix with custom items: ["general", "browser"]
|
|
22
|
+
When 'tools' is provided without subagent_type, the type defaults to "general"
|
|
23
|
+
("explore" template defaults to "explore").
|
|
24
|
+
|
|
12
25
|
This tool is NON-BLOCKING: it returns immediately with a subId after spawn.
|
|
13
26
|
The sub-agent starts automatically and runs in the background.
|
|
14
27
|
To get the result, call await_complete with this subId in the 'sub_agent_ids' array.
|
|
@@ -20,11 +33,13 @@ Guidelines:
|
|
|
20
33
|
- Write a clear, self-contained prompt (the sub-agent starts with a fresh context)
|
|
21
34
|
- Launch multiple agents concurrently when possible (multiple tool calls in one message)
|
|
22
35
|
- Sub-agents cannot spawn further sub-agents (no nesting)
|
|
23
|
-
- Always call await_complete (with this subId in 'sub_agent_ids') before reporting completion to the user`;function
|
|
24
|
-
|
|
36
|
+
- Always call await_complete (with this subId in 'sub_agent_ids') before reporting completion to the user`;function b(n){return{kind:"builtin",toolName:"sub_agent",exposedName:"sub_agent",description:g,inputSchema:{type:"object",properties:{description:{type:"string",description:"A short (3-5 words) description of the task."},prompt:{type:"string",description:"The task for the sub-agent to perform (self-contained, fresh context)."},subagent_type:{type:"string",enum:["general","explore"],description:"Type of specialized agent: 'explore' (default) for read-only codebase exploration, 'general' for multi-step tasks (file ops, bash, etc.). When in doubt, use 'explore'.",default:"explore"},tools:{type:"array",items:{type:"string"},description:"Optional tool allocation (whitelist mode). Templates: 'explore' (read-only default), 'general' (all core default tools, no suites/MCP). Or category names ('office', 'browser', 'desktop') / exact tool paths ('default.read_file'), mixable with templates (e.g. ['general','browser']). Omit for read-only explore mode. See list_sub_agent_tools for the full catalog."}},required:["description","prompt"]},async execute(r){const e=typeof r.description=="string"?r.description.trim():"",t=typeof r.prompt=="string"?r.prompt.trim():"",o=r.tools,s=typeof o=="string"?[o]:Array.isArray(o)?o.filter(i=>typeof i=="string"):[],a=p(s),l=a.patterns!==null&&a.patterns.length>0,c=l?a.patterns:void 0,u=r.subagent_type??a.subagentType??(l?"general":"explore");if(!e)return{title:"sub_agent validation error",output:"Invalid input: 'description' is required and must be non-empty.",metadata:{error:!0}};if(!t)return{title:"sub_agent validation error",output:"Invalid input: 'prompt' is required and must be non-empty.",metadata:{error:!0}};if(u!=="general"&&u!=="explore")return{title:"sub_agent validation error",output:`Invalid input: 'subagent_type' must be 'general' or 'explore', got '${u}'.`,metadata:{error:!0}};try{const{subId:i}=await n.spawnSubAgent({subagentType:u,description:e,prompt:t,...l?{allowedTools:c}:{}}),d=l?`
|
|
37
|
+
tool allocation: ${c.join(", ")} (whitelist mode \u2014 only matching tools are exposed)`:`
|
|
38
|
+
mode: read-only explore (default)`;return{title:e,output:`sub_agent spawned: ${i}
|
|
39
|
+
status: pending${d}
|
|
25
40
|
|
|
26
41
|
Call await_complete with this subId in 'sub_agent_ids' to get the result.
|
|
27
|
-
Call get_sub_agent_status to check progress.`,metadata:{subId:
|
|
42
|
+
Call get_sub_agent_status to check progress.`,metadata:{subId:i,spawned:!0,...l?{allocatedTools:c}:{}}}}catch(i){const d=i instanceof Error?i.message:String(i);return{title:e,output:`Failed to spawn sub-agent: ${d}`,metadata:{error:!0}}}}}}const h=`List all sub-agents for the current agent.
|
|
28
43
|
|
|
29
44
|
Returns two arrays:
|
|
30
45
|
- running: sub-agents currently in pending/running state
|
|
@@ -33,8 +48,8 @@ Returns two arrays:
|
|
|
33
48
|
Use this to:
|
|
34
49
|
1. Check which sub-agents are still running
|
|
35
50
|
2. Review completed sub-agent history
|
|
36
|
-
3. Find a subId before calling await_complete (sub_agent_ids) or get_sub_agent_status`;function
|
|
37
|
-
`),metadata:{runningCount:
|
|
51
|
+
3. Find a subId before calling await_complete (sub_agent_ids) or get_sub_agent_status`;function m(n){return{kind:"builtin",toolName:"list_sub_agents",exposedName:"list_sub_agents",description:h,inputSchema:{type:"object",properties:{}},async execute(r){try{const{running:e,history:t}=await n.listSubAgents(),o=a=>{const l=a.durationMs!==void 0?`${(a.durationMs/1e3).toFixed(1)}s`:a.endedAt?`${((a.endedAt-a.startedAt)/1e3).toFixed(1)}s`:"ongoing";return` - ${a.subId} [${a.status}] (${l}) ${a.description}`},s=[];return s.push(`Running (${e.length}):`),e.length===0?s.push(" (none)"):e.forEach(a=>s.push(o(a))),s.push(""),s.push(`History (${t.length}):`),t.length===0?s.push(" (none)"):t.forEach(a=>s.push(o(a))),{title:"sub-agents list",output:s.join(`
|
|
52
|
+
`),metadata:{runningCount:e.length,historyCount:t.length}}}catch(e){return{title:"list_sub_agents error",output:`Failed to list sub-agents: ${e instanceof Error?e.message:String(e)}`,metadata:{error:!0}}}}}}const f=`Get the current status and context snapshot of a sub-agent.
|
|
38
53
|
|
|
39
54
|
Returns:
|
|
40
55
|
- status: pending/running/completed/failed/cancelled
|
|
@@ -45,9 +60,9 @@ Returns:
|
|
|
45
60
|
- result: final output (if completed)
|
|
46
61
|
- error: error message (if failed)
|
|
47
62
|
|
|
48
|
-
Use this to check progress of a running sub-agent before awaiting it.`;function
|
|
49
|
-
... (truncated)`:s)}return t.errorMessage&&(
|
|
50
|
-
`),metadata:{subId:t.subId,status:t.status}}}catch(t){return{title:"get_sub_agent_status error",output:`Failed to get status: ${t instanceof Error?t.message:String(t)}`,metadata:{subId:
|
|
63
|
+
Use this to check progress of a running sub-agent before awaiting it.`;function _(n){return{kind:"builtin",toolName:"get_sub_agent_status",exposedName:"get_sub_agent_status",description:f,inputSchema:{type:"object",properties:{sub_id:{type:"string",description:"The sub-agent ID returned by sub_agent tool."}},required:["sub_id"]},async execute(r){const e=typeof r.sub_id=="string"?r.sub_id.trim():"";if(!e)return{title:"get_sub_agent_status validation error",output:"Invalid input: 'sub_id' is required.",metadata:{error:!0}};try{const t=await n.getSubAgentStatus(e);if(!t)return{title:"sub-agent not found",output:`sub-agent not found: ${e}`,metadata:{subId:e,error:!0}};const o=[];if(o.push(`subId: ${t.subId}`),o.push(`type: ${t.subagentType}`),o.push(`description: ${t.description}`),o.push(`status: ${t.status}`),t.startedAt&&o.push(`startedAt: ${new Date(t.startedAt).toISOString()}`),t.endedAt&&o.push(`endedAt: ${new Date(t.endedAt).toISOString()}`),t.durationMs!==void 0&&o.push(`duration: ${(t.durationMs/1e3).toFixed(2)}s`),t.contextSnapshot){if(o.push(""),o.push("Context:"),t.contextSnapshot.currentTool&&o.push(` currentTool: ${t.contextSnapshot.currentTool}`),t.contextSnapshot.lastAssistantText){const s=t.contextSnapshot.lastAssistantText;o.push(` lastOutput: ${s.length>200?s.slice(-200)+"...":s}`)}o.push(` tokens: input=${t.contextSnapshot.tokenUsage.input}, output=${t.contextSnapshot.tokenUsage.output}`),o.push(` messages: ${t.contextSnapshot.messageCount}`),o.push(` updatedAt: ${t.contextSnapshot.updatedAt}`)}if(t.result){o.push(""),o.push("Result:");const s=t.result;o.push(s.length>500?s.slice(0,500)+`
|
|
64
|
+
... (truncated)`:s)}return t.errorMessage&&(o.push(""),o.push(`Error: ${t.errorMessage}`)),{title:`status: ${t.description}`,output:o.join(`
|
|
65
|
+
`),metadata:{subId:t.subId,status:t.status}}}catch(t){return{title:"get_sub_agent_status error",output:`Failed to get status: ${t instanceof Error?t.message:String(t)}`,metadata:{subId:e,error:!0}}}}}}const y=`Abort a running sub-agent.
|
|
51
66
|
|
|
52
67
|
Use this to cancel a sub-agent that is taking too long or is no longer needed.
|
|
53
68
|
Only pending or running sub-agents can be aborted; completed/failed/cancelled are no-ops.
|
|
@@ -55,5 +70,19 @@ Only pending or running sub-agents can be aborted; completed/failed/cancelled ar
|
|
|
55
70
|
After abort:
|
|
56
71
|
- The sub-agent's status becomes 'cancelled'
|
|
57
72
|
- Its completionPromise resolves with error='aborted'
|
|
58
|
-
- Any await_complete waiting on it (with this subId in 'sub_agent_ids') will return immediately`;function
|
|
73
|
+
- Any await_complete waiting on it (with this subId in 'sub_agent_ids') will return immediately`;function w(n){return{kind:"builtin",toolName:"abort_sub_agent",exposedName:"abort_sub_agent",description:y,inputSchema:{type:"object",properties:{sub_id:{type:"string",description:"The sub-agent ID to abort."}},required:["sub_id"]},async execute(r){const e=typeof r.sub_id=="string"?r.sub_id.trim():"";if(!e)return{title:"abort_sub_agent validation error",output:"Invalid input: 'sub_id' is required.",metadata:{error:!0}};try{return await n.abortSubAgent(e),{title:"sub-agent aborted",output:`sub-agent ${e} has been aborted.`,metadata:{subId:e,aborted:!0}}}catch(t){return{title:"abort_sub_agent error",output:`Failed to abort sub-agent: ${t instanceof Error?t.message:String(t)}`,metadata:{subId:e,error:!0}}}}}}const x=`List all tool categories and tools that can be allocated to sub-agents.
|
|
74
|
+
|
|
75
|
+
Tools are grouped by category (suite). Each line shows a category and its tool short names:
|
|
76
|
+
- default: core file/shell tools (read_file, write_file, grep, Bash, ...)
|
|
77
|
+
- <suite>: suite tools (e.g. browser: launch, navigate, click, ...)
|
|
78
|
+
- <mcp-server>: MCP tools under that server (e.g. memory: read_memory, ...)
|
|
79
|
+
|
|
80
|
+
Use this before spawning a sub-agent with the 'tools' parameter:
|
|
81
|
+
- Pass a category name to allocate the whole suite: tools: ["office"]
|
|
82
|
+
- Pass an exact tool path for one tool: tools: ["default.read_file"]
|
|
83
|
+
- Short names inside a category also work: tools: ["browser.click"]
|
|
84
|
+
|
|
85
|
+
If a tool you need is not listed, it cannot be allocated (the sub-agent pool
|
|
86
|
+
equals the current agent's tool set).`;function T(n){return{kind:"builtin",toolName:"list_sub_agent_tools",exposedName:"list_sub_agent_tools",description:x,inputSchema:{type:"object",properties:{}},async execute(r){try{const e=await n.listSubAgentToolCatalog();if(e.length===0)return{title:"allocatable tools",output:"No allocatable tools found (tool catalog is empty).",metadata:{categories:0}};const t=["Allocatable tool categories (for sub_agent 'tools' param):",""];for(const o of e)t.push(`[${o.category}] ${o.kind} (${o.tools.length}):`),t.push(` ${o.tools.join(", ")}`);return t.push(""),t.push("Usage:"),t.push(`- tools: ["<category>"] \u2192 all tools in that category (e.g. ["${e[0].category}"])`),t.push(`- tools: ["<category>.<tool>"] \u2192 exactly that tool (e.g. ["${e[0].category}.${e[0].tools[0]??"tool"}"])`),t.push("- omit tools \u2192 read-only explore mode (default)"),{title:"allocatable tools",output:t.join(`
|
|
87
|
+
`),metadata:{categories:e.length}}}catch(e){return{title:"list_sub_agent_tools error",output:`Failed to list allocatable tools: ${e instanceof Error?e.message:String(e)}`,metadata:{error:!0}}}}}}function A(n){return[b(n),m(n),_(n),w(n),T(n)]}export{w as createAbortSubAgentTool,_ as createGetSubAgentStatusTool,T as createListSubAgentToolsTool,m as createListSubAgentsTool,b as createSubAgentTool,A as createSubAgentTools};
|
|
59
88
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export declare const DEFAULT_TOOL_CATEGORY = "default";
|
|
4
|
+
|
|
5
|
+
export declare const SUB_AGENT_MANAGEMENT_TOOL_NAMES: Set<string>;
|
|
6
|
+
|
|
7
|
+
export declare const AGENT_IDENTITY_MCP_RESTRICTED_TOOLS: Set<string>;
|
|
8
|
+
|
|
9
|
+
export declare function isAgentIdentityRestrictedTool(tool: AllocatableToolShape): boolean;
|
|
10
|
+
|
|
11
|
+
export declare function mergeAllocatableTools<T extends {
|
|
12
|
+
exposedName: string;
|
|
13
|
+
}>(base: readonly T[], extra: readonly T[]): T[];
|
|
14
|
+
|
|
15
|
+
export interface AllocatableToolShape {
|
|
16
|
+
kind: "builtin" | "mcp";
|
|
17
|
+
toolName: string;
|
|
18
|
+
exposedName: string;
|
|
19
|
+
|
|
20
|
+
serverName?: string;
|
|
21
|
+
|
|
22
|
+
category?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export declare function toolAllocationCategory(tool: AllocatableToolShape): string;
|
|
26
|
+
|
|
27
|
+
export declare function rebindDefaultAgentId<T extends AllocatableToolShape>(tools: readonly T[], agentId: string): T[];
|
|
28
|
+
|
|
29
|
+
export declare function toolAllocationAliases(tool: AllocatableToolShape): string[];
|
|
30
|
+
|
|
31
|
+
export declare function normalizeAllocationPattern(pattern: string): string;
|
|
32
|
+
|
|
33
|
+
export declare function matchesToolAllocation(tool: AllocatableToolShape, patterns: readonly string[]): boolean;
|
|
34
|
+
|
|
35
|
+
export interface ToolCatalogEntry {
|
|
36
|
+
|
|
37
|
+
category: string;
|
|
38
|
+
|
|
39
|
+
kind: "builtin" | "mcp";
|
|
40
|
+
|
|
41
|
+
tools: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare function buildToolCatalog(tools: readonly AllocatableToolShape[]): ToolCatalogEntry[];
|
|
45
|
+
|
|
46
|
+
export declare const TOOL_ALLOCATION_TEMPLATES: Readonly<Record<string, string[] | null>>;
|
|
47
|
+
|
|
48
|
+
export interface ResolvedToolAllocation {
|
|
49
|
+
|
|
50
|
+
patterns: string[] | null;
|
|
51
|
+
|
|
52
|
+
subagentType: "general" | "explore" | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export declare function resolveToolAllocation(raw: readonly string[]): ResolvedToolAllocation;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const c="default",l=new Set(["sub_agent","list_sub_agents","get_sub_agent_status","abort_sub_agent","list_sub_agent_tools"]),p=new Set(["poll_message","get_message","send_group","send_private","create_task_instance_comment","submit_task_result","start_task_rollback","finish_task_rollback","reject_task","terminate_task","hire_colleague","hire_colleagues_batch","abandon_memory_update"]);function u(e){return e.kind==="mcp"&&p.has(e.toolName)}function g(e,n){if(n.length===0)return[...e];const s=new Set(e.map(t=>t.exposedName)),r=[...e];for(const t of n)s.has(t.exposedName)||(s.add(t.exposedName),r.push(t));return r}function f(e){return e.kind==="mcp"?e.serverName||"mcp":e.category||c}function h(e,n){return!n||e.length===0?[...e]:e.map(s=>{if(s.kind!=="builtin")return s;const t=s.execute;return typeof t!="function"?s:{...s,execute:(a,i)=>{const o=a&&typeof a=="object"&&a.agentId==null?{...a,agentId:n}:a;return t(o,i)}}})}function d(e){if(e.kind==="mcp"){const t=e.serverName||"mcp",a=[`${t}.${e.toolName}`];return e.exposedName&&e.exposedName!==e.toolName&&a.push(`${t}.${e.exposedName}`),a}const n=e.category||c,s=[`${n}.${e.exposedName}`],r=`${n}_`;return n!==c&&e.exposedName.startsWith(r)&&s.push(`${n}.${e.exposedName.slice(r.length)}`),s}function m(e){let n=e.trim();for(;n.length>0&&(n.endsWith("*")||n.endsWith("."));)n=n.slice(0,-1).trimEnd();return n}function x(e,n){if(u(e))return!1;const s=n.map(t=>typeof t=="string"?m(t):"").filter(t=>t.length>0);if(s.length===0)return!1;const r=d(e);return s.some(t=>r.some(a=>a===t||a.startsWith(`${t}.`)))}function N(e){const n=[],s=new Map;for(const r of e){if(l.has(r.exposedName)||u(r))continue;const t=f(r);let a=s.get(t);a||(a={kind:r.kind,names:[],seen:new Set},s.set(t,a),n.push(t));const i=_(r);a.seen.has(i)||(a.seen.add(i),a.names.push(i))}return n.sort((r,t)=>r===c?-1:t===c?1:r<t?-1:r>t?1:0).map(r=>{const t=s.get(r);return{category:r,kind:t.kind,tools:t.names}})}function _(e){if(e.kind==="mcp")return e.toolName;const n=e.category||c,s=`${n}_`;return n!==c&&e.exposedName.startsWith(s)?e.exposedName.slice(s.length):e.exposedName}const T={explore:null,general:["default"]};function A(e){const n=e.map(o=>typeof o=="string"?o.trim():"").filter(o=>o.length>0);if(n.length===0)return{patterns:null,subagentType:null};const s=n.includes("explore"),r=n.filter(o=>o!=="explore"&&o!=="general"),t=n.includes("general")?[...r,"default"]:r;s&&t.length>0&&t.push("default");const a=new Set,i=t.filter(o=>a.has(o)?!1:(a.add(o),!0));return i.length===0?{patterns:null,subagentType:"explore"}:{patterns:i,subagentType:"general"}}export{p as AGENT_IDENTITY_MCP_RESTRICTED_TOOLS,c as DEFAULT_TOOL_CATEGORY,l as SUB_AGENT_MANAGEMENT_TOOL_NAMES,T as TOOL_ALLOCATION_TEMPLATES,N as buildToolCatalog,u as isAgentIdentityRestrictedTool,x as matchesToolAllocation,g as mergeAllocatableTools,m as normalizeAllocationPattern,h as rebindDefaultAgentId,A as resolveToolAllocation,d as toolAllocationAliases,f as toolAllocationCategory};
|
|
2
|
+
|
|
@@ -131,9 +131,15 @@ export interface ACodeRuntimeConfig {
|
|
|
131
131
|
|
|
132
132
|
subagentType?: SubAgentType;
|
|
133
133
|
|
|
134
|
+
subAgentAllowedTools?: string[];
|
|
135
|
+
|
|
134
136
|
onFileChange?: import("./built-in-file-tools.js").OnAgentFileChange;
|
|
135
137
|
|
|
136
138
|
extraBuiltInTools?: import("./built-in-file-tools.js").ACodeBuiltInTool[];
|
|
139
|
+
|
|
140
|
+
subAgentSuiteTools?: import("./built-in-file-tools.js").ACodeBuiltInTool[];
|
|
141
|
+
|
|
142
|
+
subAgentSuiteToolProvider?: (patterns: string[]) => Promise<import("./built-in-file-tools.js").ACodeBuiltInTool[]>;
|
|
137
143
|
}
|
|
138
144
|
export interface ACodeResolvedProviderConfig {
|
|
139
145
|
model: string;
|
|
@@ -63,6 +63,10 @@ export declare class AgentClient {
|
|
|
63
63
|
|
|
64
64
|
fetchUnreadMessages(options?: FetchUnreadMessagesOptions): Promise<UnreadMessagesResult>;
|
|
65
65
|
|
|
66
|
+
markDmRead(msgIds: string[]): Promise<Set<string>>;
|
|
67
|
+
|
|
68
|
+
markGroupRead(roomId: string, msgIds: number[]): Promise<Set<number>>;
|
|
69
|
+
|
|
66
70
|
fetchBrowserListenerEvents(): Promise<BrowserListenerEvent[]>;
|
|
67
71
|
|
|
68
72
|
notifyBridgeForceMessage(): Promise<void>;
|
|
@@ -98,6 +102,26 @@ export declare class AgentClient {
|
|
|
98
102
|
|
|
99
103
|
queryFileChangesHistory(args: Record<string, unknown>): Promise<unknown>;
|
|
100
104
|
|
|
105
|
+
getRuntimeFeatures(): Promise<Record<string, boolean>>;
|
|
106
|
+
|
|
107
|
+
listLaunchConfigs(): Promise<unknown>;
|
|
108
|
+
|
|
109
|
+
createLaunchConfig(args: Record<string, unknown>): Promise<unknown>;
|
|
110
|
+
|
|
111
|
+
updateLaunchConfig(args: Record<string, unknown>): Promise<unknown>;
|
|
112
|
+
|
|
113
|
+
deleteLaunchConfig(configId: string): Promise<unknown>;
|
|
114
|
+
|
|
115
|
+
startLaunchConfig(args: Record<string, unknown>): Promise<unknown>;
|
|
116
|
+
|
|
117
|
+
restartLaunchConfig(args: Record<string, unknown>): Promise<unknown>;
|
|
118
|
+
|
|
119
|
+
stopLaunchConfig(configId: string): Promise<unknown>;
|
|
120
|
+
|
|
121
|
+
getLaunchLogs(args: Record<string, unknown>): Promise<unknown>;
|
|
122
|
+
|
|
123
|
+
listLaunchRuns(): Promise<unknown>;
|
|
124
|
+
|
|
101
125
|
start(handler: MessageHandler, options?: {
|
|
102
126
|
pollMode?: boolean;
|
|
103
127
|
profileHandler?: ProfileHandler;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{configManager as g}from"./config.js";import{MESSAGE_POLL_INTERVAL_MS as m}from"./constants.js";import{HttpClient as u}from"./http-client.js";import{logger as s,setMcpNodeLogSink as C,updateStartupLogIdentity as f}from"./logger.js";import{claimRuntimeLaunchBinding as I}from"./runtime-launch-binding.js";import{StatusReporter as y}from"./status-reporter.js";import{WebSocketClient as p}from"./websocket-client.js";class b{wsClient;httpClient;agentConfig;serverConfig;agentId=null;displayName=null;projectId=null;isRunning=!1;isDegradedMode=!1;messageHandler=null;profileHandler=null;configHandler=null;pollTimer=null;statusReporter=null;newCharacterHandler=null;runtimeTokenRefreshTimer=null;cachedProfile=null;constructor(){g.loadConfig(),this.agentConfig=g.getAgentConfig(),this.serverConfig=g.getServerConfig(),this.wsClient=new p(this.serverConfig),this.httpClient=new u(this.serverConfig),this.setupWebSocketListeners()}async initialize(){const e=await I(this.agentConfig,this.serverConfig);if(!e){this.serverConfig.runtimeBridgeBaseUrl=void 0;return}g.applyRuntimeLaunchBinding(e),this.agentConfig=g.getAgentConfig(),this.serverConfig=g.getServerConfig(),this.wsClient=new p(this.serverConfig),this.httpClient=new u(this.serverConfig),this.setupWebSocketListeners()}setupWebSocketListeners(){this.wsClient.on("directMessage",e=>{this.handleIncomingDm(e)}),this.wsClient.on("groupMessage",e=>{this.handleIncomingGroup(e)}),this.wsClient.on("profileInfo",e=>{s.info(`[AgentClient] WebSocket profileInfo \u4E8B\u4EF6\u89E6\u53D1\uFF0CdisplayName: ${e.displayName}`),this.handleProfileInfo(e)}),this.wsClient.on("agentConfig",e=>{s.info("[AgentClient] WebSocket agentConfig \u4E8B\u4EF6\u89E6\u53D1"),this.configHandler&&this.configHandler(e??{})}),this.wsClient.on("agentStatus",e=>{s.info(`[AgentClient] WebSocket agentStatus \u4E8B\u4EF6\u89E6\u53D1: ${e.status}`)}),this.wsClient.on("disconnected",(e,n)=>{const t=n?.toString()||"";s.warn("[AgentClient] WebSocket \u65AD\u5F00\u8FDE\u63A5: code=%s, reason=%s, agentId=%s, \u6B63\u5728\u81EA\u52A8\u91CD\u8FDE",e,t,this.agentId)}),this.wsClient.on("error",e=>{s.error("[AgentClient] WebSocket \u9519\u8BEF: %s, stack=%s",e.message,e.stack)}),this.wsClient.on("reconnectFailed",()=>{s.error("[AgentClient] \u91CD\u8FDE\u5931\u8D25\uFF08\u5DF2\u8FBE\u91CD\u8BD5\u4E0A\u9650\uFF09\uFF0C\u505C\u6B62\u6D88\u606F\u5FAA\u73AF, agentId=%s",this.agentId),this.stop()})}async register(){return s.info("[AgentClient] \u6B63\u5728\u6CE8\u518C Agent..."),this.hasPresetAgentId()?await this.registerWithExistingAgentId():await this.registerAsNewAgent()}hasPresetAgentId(){return!!(this.agentConfig.agentId&&this.agentConfig.agentId.trim().length>0)}async registerWithExistingAgentId(){const e=this.agentConfig.agentId;if(!e)throw new Error("Preset agentId is missing");this.agentId=e.trim();let n;try{n=await this.getProfileByAgentId(this.agentId)}catch(i){throw s.error("[AgentClient] getProfileByAgentId \u5931\u8D25 (presetAgentId=%s): %s",this.agentId,i instanceof Error?i.message:String(i)),i}this.displayName=n.displayName,this.projectId=n.projectId||null,s.info(`[AgentClient] \u4F7F\u7528\u9884\u8BBE AgentID \u7ED1\u5B9A\u5B9E\u4F8B: ${this.displayName} (${this.agentId})`),s.info(`[AgentClient] \u6240\u5C5E\u9879\u76EE: ${this.projectId||"(\u672A\u8BBE\u7F6E)"}`);try{await this.wsClient.connect(this.agentId)}catch(i){throw s.error("[AgentClient] WebSocket \u8FDE\u63A5\u5931\u8D25 (agentId=%s): %s",this.agentId,i instanceof Error?i.message:String(i)),i}s.info("[AgentClient] WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB\uFF0CAgent \u73B0\u5728\u5DF2\u4E0A\u7EBF"),this.initializeStatusReporter();const t=await this.getProfileByAgentId(this.agentId);return this.displayName=t.displayName,this.projectId=t.projectId||null,f(t.roleName||"unknown-role",t.instanceName||this.agentId),{agentId:this.agentId,displayName:this.displayName,status:t.isOnline?"online":"offline",projectId:t.projectId,workspacePath:t.workspacePath,runtimeStatus:t.runtimeStatus,runtimeSessionId:t.runtimeSessionId}}async registerAsNewAgent(){const e=await this.httpClient.callTool("register",{roleName:this.agentConfig.roleName,projectId:this.agentConfig.projectId,workspacePath:this.agentConfig.workspacePath,prompt:this.agentConfig.prompt,runtimeBridgeBaseUrl:this.serverConfig.runtimeBridgeBaseUrl}),n=e.agentId;if(!n)throw new Error("Registration failed: agentId was not returned");return this.agentId=n,this.displayName=e.displayName,this.projectId=e.projectId||null,f(this.agentConfig.roleName||"unknown-role",e.instanceName||n),s.info(`[AgentClient] \u6CE8\u518C\u6210\u529F: ${this.displayName} (${this.agentId})`),s.info(`[AgentClient] \u72B6\u6001: ${e.status}`),s.info(`[AgentClient] \u6240\u5C5E\u9879\u76EE: ${this.projectId||"(\u672A\u8BBE\u7F6E)"}`),await this.wsClient.connect(this.agentId),s.info("[AgentClient] WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB\uFF0CAgent \u73B0\u5728\u5DF2\u4E0A\u7EBF"),this.initializeStatusReporter(),e.isExisting===!1&&this.newCharacterHandler&&this.newCharacterHandler(),e}async unregister(){s.info("[AgentClient] \u6B63\u5728\u6CE8\u9500 Agent..."),this.agentId&&await this.httpClient.callTool("unregister",{agentId:this.agentId}),this.stopRuntimeTokenRefresh(),this.wsClient.disconnect(),this.agentId=null,this.displayName=null,this.projectId=null,this.isRunning=!1,s.info("[AgentClient] \u5DF2\u6CE8\u9500")}async discoverColleagues(){return await this.httpClient.callTool("discover_colleague",{agentId:this.agentId})}async discoverGroupRooms(){return this.ensureRegistered(),await this.httpClient.callTool("discover_group_rooms",{agentId:this.agentId})}async hireColleague(e){return this.ensureRegistered(),await this.httpClient.callTool("hire_colleague",{agentId:this.agentId,...e})}async hireColleaguesBatch(e){return this.ensureRegistered(),await this.httpClient.callTool("hire_colleagues_batch",{agentId:this.agentId,...e})}async callAgentScopedTool(e,n){return this.ensureRegistered(),await this.httpClient.callTool(e,{agentId:this.agentId,...n})}async reportMemoryStats(e){await this.callAgentScopedTool("report_memory_stats",{memoryStats:e})}async sendGroupMessage(e,n,t){this.ensureRegistered();const i=n||this.projectId||this.agentConfig.projectId;return await this.httpClient.callTool("send_group_message",{agentId:this.agentId,projectId:i,roomId:t,content:e})}async sendDirectMessage(e,n,t=!1,i,r){this.ensureRegistered();const a={agentId:this.agentId,targetId:e,content:n,requireReply:t,waitReply:t,replyToCallId:i};if(typeof r=="number"&&r>0&&(a.timeoutMs=r),t){const d=typeof r=="number"&&r>0?r+3e4:0;return await this.httpClient.callToolOnce("send_dm",a,d)}return await this.httpClient.callTool("send_dm",a)}async sendCallAndWaitReply(e,n,t=6e4){const i=await this.sendDirectMessage(e,n,!0,void 0,t);if(i.reply)return i.reply;throw new Error("No reply received for call-style direct message before timeout")}async replyToCall(e,n,t){return await this.sendDirectMessage(n,t,!1,e)}async getDmHistory(e,n=50,t){this.ensureRegistered();const i={agentId:this.agentId,targetUserId:e,limit:n};return t&&(i.since=t),await this.httpClient.callTool("get_dm_history",i)}async getGroupHistory(e=50,n,t){if(this.ensureRegistered(),n){const o={projectId:this.projectId||this.agentConfig.projectId,roomId:n,limit:e};return t&&(o.since=t),await this.httpClient.callTool("get_group_history",o)}const i=await this.discoverGroupRooms(),r=this.projectId||this.agentConfig.projectId,a=this.collectRoomIds(i,r);if(a.length===0)return{messages:[],count:0};const h=(await Promise.all(a.map(async o=>{const l={projectId:r,roomId:o,limit:e};return t&&(l.since=t),await this.httpClient.callTool("get_group_history",l)}))).flatMap(o=>o.messages).sort((o,l)=>new Date(o.timestamp).getTime()-new Date(l.timestamp).getTime());return{messages:h,count:h.length}}async fetchUnreadMessages(e={}){this.ensureRegistered();const n=this.projectId||this.agentConfig.projectId,t={agentId:this.agentId,blockIfEmpty:e.blockIfEmpty??!1};e.since&&(t.since=e.since),e.blockTimeoutMs&&e.blockTimeoutMs>0&&(t.blockTimeoutMs=e.blockTimeoutMs);const i=e.includePipelineTasks?"poll_message":"get_dm_messages",r=await this.fetchDirectMessagesWithRecovery(i,t),a=await this.discoverGroupRooms(),d=this.collectRoomIds(a,n),h=await Promise.all(d.map(async o=>{const l={agentId:this.agentId,projectId:n,roomId:o};e.since&&(l.since=e.since);try{return await this.httpClient.callTool("get_group_messages",l)}catch(c){return s.warn("[AgentClient] \u8DF3\u8FC7\u7FA4\u623F\u95F4\u672A\u8BFB\u8865\u62C9\u5931\u8D25: roomId=%s, error=%s",o,c instanceof Error?c.message:String(c)),{messages:[],currentReadPos:0,hasMore:!1}}}));return{directMessages:Array.isArray(r.messages)?r.messages:[],groupMessages:h.flatMap(o=>this.normalizeGroupMessages(o)).sort((o,l)=>new Date(o.timestamp).getTime()-new Date(l.timestamp).getTime())}}async fetchBrowserListenerEvents(){const e=this.serverConfig.runtimeBridgeBaseUrl,n=this.agentId;if(!e||!n)return s.info("[AgentClient] fetchBrowserListenerEvents \u8DF3\u8FC7: bridgeBaseUrl=%s, agentId=%s",e||"(\u7A7A)",n||"(\u7A7A)"),[];try{const t=`${e}/runtime/browser-listeners/${encodeURIComponent(n)}/events`,i=this.serverConfig.runtimeAccessToken||"";s.debug("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6: url=%s, tokenLen=%d",t,i.length);const r=await fetch(t,{method:"GET",headers:{"X-Runtime-Token":i},signal:AbortSignal.timeout(3e3)});if(!r.ok)return s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u5931\u8D25: HTTP %s, url=%s",r.status,t),[];const a=await r.json();return!a.ok||!Array.isArray(a.events)?(s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u54CD\u5E94\u5F02\u5E38: ok=%s, eventsType=%s",a.ok,Array.isArray(a.events)?"array":typeof a.events),[]):(s.debug("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u6210\u529F: %d \u6761\u4E8B\u4EF6",a.events.length),a.events)}catch(t){return s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u5F02\u5E38: %s",t instanceof Error?t.message:String(t)),[]}}async notifyBridgeForceMessage(){const e=this.serverConfig.runtimeBridgeBaseUrl,n=this.agentId;if(!(!e||!n))try{const t=`${e}/runtime/sessions/${encodeURIComponent(n)}/notify-force-message`,i=this.serverConfig.runtimeAccessToken||"",r=await fetch(t,{method:"POST",headers:{"X-Runtime-Token":i},signal:AbortSignal.timeout(3e3)});r.ok||s.warn("[AgentClient] \u901A\u77E5 bridge \u5F3A\u5236\u6D88\u606F\u5931\u8D25: HTTP %s, url=%s",r.status,t)}catch(t){s.warn("[AgentClient] \u901A\u77E5 bridge \u5F3A\u5236\u6D88\u606F\u5F02\u5E38: %s",t instanceof Error?t.message:String(t))}}async fetchDirectMessagesWithRecovery(e,n){if(e==="poll_message")try{return await this.httpClient.callToolOnce(e,n)}catch(t){if(this.isRecoverableLongPollError(t))return s.warn("[AgentClient] poll_message \u5355\u6B21\u957F\u8F6E\u8BE2\u8D85\u65F6\uFF0C\u6309\u7A7A\u7ED3\u679C\u9000\u907F\u540E\u7EE7\u7EED\u7B49\u5F85:",t),{messages:[]};throw t}return await this.httpClient.callTool(e,n)}isRecoverableLongPollError(e){return e instanceof Error?/MCP tool poll_message call failed: 503(?:\s|$)/.test(e.message)||/MCP tool poll_message timed out after \d+ms/.test(e.message):!1}normalizeGroupMessages(e){return Array.isArray(e.messages)?e.messages.filter(n=>!!(n&&typeof n=="object"&&typeof n.msgId=="number"&&typeof n.senderId=="string"&&typeof n.content=="string"&&typeof n.timestamp=="string")):[]}collectRoomIds(e,n){const t=e.groups.filter(i=>i.projectId===n).map(i=>i.id);return Array.from(new Set(t))}async getProfile(){this.ensureRegistered();const e=this.agentId;if(!e)throw new Error("Agent is not registered");return{...await this.getProfileByAgentId(e),describe:this.agentConfig.describe}}async getProfileByAgentId(e){return await this.httpClient.callTool("get_profile",{agentId:e})}async getMyTasks(){return this.ensureRegistered(),await this.httpClient.callTool("my_task",{agentId:this.agentId})}async createTaskInstanceComment(e){this.ensureRegistered();const n={agentId:this.agentId,...e};return await this.httpClient.callTool("create_task_instance_comment",n)}async submitTaskResult(e,n,t){this.ensureRegistered();const i={agentId:this.agentId,nodeId:e,outputPayload:n};return t&&Array.isArray(t)&&t.length>0&&(i.changeLog=t),await this.httpClient.callTool("submit_task_result",i)}async startTaskRollback(e){return this.ensureRegistered(),await this.httpClient.callTool("start_task_rollback",{agentId:this.agentId,...e})}async finishTaskRollback(e){return this.ensureRegistered(),await this.httpClient.callTool("finish_task_rollback",{agentId:this.agentId,...e})}async rejectTask(e,n){return this.ensureRegistered(),await this.httpClient.callTool("reject_task",{agentId:this.agentId,nodeId:e,reason:n})}async terminateTask(e,n){return this.ensureRegistered(),await this.httpClient.callTool("terminate_task",{agentId:this.agentId,taskInstanceId:e,reason:n})}async getDebugLogConfig(){return await this.httpClient.callTool("get_debug_log_config",{agentId:this.agentId})}async queryDebugLogs(e){return this.ensureRegistered(),await this.httpClient.callTool("query_debug_logs",{...e})}async queryFileChangesHistory(e){return this.ensureRegistered(),await this.httpClient.callTool("query_file_changes_history",{...e})}start(e,n){if(this.isRunning){s.warn("[AgentClient] \u6D88\u606F\u5FAA\u73AF\u5DF2\u5728\u8FD0\u884C\u4E2D");return}this.isDegradedMode||this.ensureRegistered(),this.messageHandler=e,this.profileHandler=n?.profileHandler||null,this.configHandler=n?.configHandler||null,this.isRunning=!0,s.info(`[AgentClient] start() \u88AB\u8C03\u7528\uFF0CcachedProfile: ${this.cachedProfile?"\u5B58\u5728":"\u4E0D\u5B58\u5728"}, profileHandler: ${this.profileHandler?"\u5DF2\u8BBE\u7F6E":"\u672A\u8BBE\u7F6E"}, \u964D\u7EA7\u6A21\u5F0F: ${this.isDegradedMode?"\u662F":"\u5426"}`),this.cachedProfile&&this.profileHandler&&(s.info(`[AgentClient] \u5904\u7406\u7F13\u5B58\u7684 Profile \u4FE1\u606F: ${this.cachedProfile.displayName}`),this.profileHandler(this.cachedProfile),this.cachedProfile=null),n?.pollMode??!1?(s.warn("[AgentClient] \u542F\u52A8\u6D88\u606F\u4E3B\u5FAA\u73AF\uFF08HTTP\u8F6E\u8BE2\u6A21\u5F0F - \u4E0D\u63A8\u8350\uFF09"),this.messageLoop()):s.info("[AgentClient] \u542F\u52A8\u6D88\u606F\u4E3B\u5FAA\u73AF\uFF08WebSocket\u63A8\u9001\u6A21\u5F0F - \u63A8\u8350\uFF09")}stop(){this.isRunning=!1,this.pollTimer&&(clearTimeout(this.pollTimer),this.pollTimer=null),this.stopRuntimeTokenRefresh(),s.info("[AgentClient] \u6D88\u606F\u4E3B\u5FAA\u73AF\u5DF2\u505C\u6B62")}async messageLoop(){for(;this.isRunning;)try{await this.delay(m)}catch(e){s.error("[AgentClient] \u6D88\u606F\u5FAA\u73AF\u9519\u8BEF:",e),await this.delay(5e3)}}handleIncomingDm(e){e.senderId!==this.agentId&&this.processMessage(e)}handleIncomingGroup(e){e.senderId!==this.agentId&&this.processMessage(e)}handleProfileInfo(e){s.info(`[AgentClient] \u6536\u5230 Profile \u4FE1\u606F: ${e.displayName} (${e.roleName})`),e.projectId&&(this.projectId=e.projectId,s.info(`[AgentClient] \u66F4\u65B0\u6240\u5C5E\u9879\u76EE: ${this.projectId}`)),e.roleName&&e.roleName!==this.agentConfig.roleName&&(s.info(`[AgentClient] \u540C\u6B65\u89D2\u8272\u540D: "${this.agentConfig.roleName||"(\u7A7A)"}" -> "${e.roleName}"`),this.agentConfig.roleName=e.roleName),e.roleName&&f(e.roleName,e.instanceName||this.agentId||""),this.profileHandler?this.profileHandler(e):(s.info("[AgentClient] profileHandler \u672A\u8BBE\u7F6E\uFF0C\u7F13\u5B58 Profile \u4FE1\u606F"),this.cachedProfile=e)}async processMessage(e){if(!this.messageHandler)return;const n=typeof e.msgId=="string";s.info(`[AgentClient] \u6536\u5230${n?"\u79C1\u4FE1":"\u7FA4\u6D88\u606F"}\u6765\u81EA ${e.senderName}: ${e.content.substring(0,50)}...`);try{const t=await this.messageHandler(e);if(n){const i=e;i.requireReply&&i.callId&&await this.replyToCall(i.callId,i.senderId,t||"\u5DF2\u6536\u5230\u6D88\u606F\uFF0C\u6B63\u5728\u5904\u7406\u4E2D...")}}catch(t){const i=typeof e.msgId=="string";if(s.error("[AgentClient] \u5904\u7406\u6D88\u606F\u5931\u8D25: type=%s, senderId=%s, senderName=%s, msgId=%s, error=%s",i?"\u79C1\u4FE1":"\u7FA4\u6D88\u606F",e.senderId,e.senderName,e.msgId,t instanceof Error?t.message:String(t)),i){const r=e;r.requireReply&&r.callId&&await this.replyToCall(r.callId,r.senderId,`\u5904\u7406\u5931\u8D25: ${t}`)}}}ensureRegistered(){if(!this.agentId)throw new Error("Agent is not registered")}delay(e){return new Promise(n=>{this.pollTimer=setTimeout(n,e)})}onNewCharacter(e){this.newCharacterHandler=e}getState(){return{agentId:this.agentId,displayName:this.displayName,projectId:this.projectId,isConnected:this.wsClient.isConnected(),isRunning:this.isRunning}}getAgentConfig(){return this.agentConfig}getServerConfig(){return this.serverConfig}reloadConfig(){g.loadConfig(),this.agentConfig=g.getAgentConfig(),s.info(`[AgentClient] \u914D\u7F6E\u5DF2\u91CD\u65B0\u52A0\u8F7D\uFF0C\u89D2\u8272: ${this.agentConfig.roleName}`)}enterDegradedMode(){this.isDegradedMode=!0,s.warn("[AgentClient] \u8FDB\u5165\u964D\u7EA7\u6A21\u5F0F\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u5C1D\u8BD5\u91CD\u8FDE")}exitDegradedMode(){this.isDegradedMode=!1,s.info("[AgentClient] \u9000\u51FA\u964D\u7EA7\u6A21\u5F0F")}isInDegradedMode(){return this.isDegradedMode}async retryRegistrationInBackground(e,n){let t=0;(async()=>{for(;this.isDegradedMode;)try{s.info(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u5C1D\u8BD5\u91CD\u65B0\u6CE8\u518C (${t+1}/${n===-1?"\u65E0\u9650":n})`);const r=await this.register();s.info(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6CE8\u518C\u6210\u529F - ${r.displayName} (${r.agentId})`),this.exitDegradedMode(),this.messageHandler&&this.start(this.messageHandler);return}catch(r){t++;const a=r instanceof Error?r.message:String(r);if(s.warn(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6CE8\u518C\u5931\u8D25 (${t}/${n===-1?"\u65E0\u9650":n}): ${a}`),n>0&&t>=n){s.error("[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u505C\u6B62\u91CD\u8FDE"),this.isDegradedMode=!1;return}await new Promise(d=>setTimeout(d,e))}})().catch(r=>{s.error("[AgentClient] \u964D\u7EA7\u6A21\u5F0F\u540E\u53F0\u91CD\u8BD5\u5FAA\u73AF\u5F02\u5E38\u9000\u51FA: error=%s, stack=%s",r instanceof Error?r.message:String(r),r instanceof Error?r.stack:"(no stack)")})}initializeStatusReporter(){if(!this.agentId){s.warn("[AgentClient] \u65E0\u6CD5\u521D\u59CB\u5316\u72B6\u6001\u62A5\u544A\u5668\uFF1AagentId \u4E3A\u7A7A");return}this.statusReporter=new y({agentId:this.agentId,sendStatusUpdate:e=>{this.wsClient.send({type:"AGENT_STATUS",data:e,timestamp:new Date().toISOString()})}}),C(e=>this.reportMcpNodeLog(e)),this.startRuntimeTokenRefresh(),s.info("[AgentClient] \u72B6\u6001\u62A5\u544A\u5668\u5DF2\u521D\u59CB\u5316")}startRuntimeTokenRefresh(){this.stopRuntimeTokenRefresh();const e=this.serverConfig.runtimeTokenRefreshIntervalMs;if(!e||e<=0){s.warn("[AgentClient] runtimeTokenRefreshIntervalMs \u672A\u914D\u7F6E\u6216\u65E0\u6548\uFF0C\u8DF3\u8FC7\u4E3B\u52A8\u7EED\u671F");return}const n=this.agentId;if(!n){s.warn("[AgentClient] agentId \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u4E3B\u52A8\u7EED\u671F");return}this.runtimeTokenRefreshTimer=setInterval(async()=>{try{await this.httpClient.refreshRuntimeAccessToken(n)&&s.info("[AgentClient] runtimeAccessToken \u5DF2\u4E3B\u52A8\u7EED\u671F")}catch(t){s.warn("[AgentClient] \u4E3B\u52A8\u7EED\u671F runtimeAccessToken \u5931\u8D25: %s",t instanceof Error?t.message:String(t)),t instanceof Error&&t.stack&&s.warn("[AgentClient] \u4E3B\u52A8\u7EED\u671F\u5F02\u5E38\u5806\u6808:",t.stack)}},e),s.info(`[AgentClient] runtime token \u4E3B\u52A8\u7EED\u671F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694 ${e}ms`)}stopRuntimeTokenRefresh(){this.runtimeTokenRefreshTimer&&(clearInterval(this.runtimeTokenRefreshTimer),this.runtimeTokenRefreshTimer=null)}reportMcpNodeLog(e){const n=this.wsClient;!this.agentId||!this.wsClient.isConnected()||typeof n.sendMcpNodeLog!="function"||n.sendMcpNodeLog(e)}getStatusReporter(){return this.statusReporter}}export{b as AgentClient};
|
|
1
|
+
import{configManager as g}from"./config.js";import{MESSAGE_POLL_INTERVAL_MS as m}from"./constants.js";import{HttpClient as f}from"./http-client.js";import{logger as s,setMcpNodeLogSink as C,updateStartupLogIdentity as u}from"./logger.js";import{claimRuntimeLaunchBinding as I}from"./runtime-launch-binding.js";import{StatusReporter as y}from"./status-reporter.js";import{WebSocketClient as p}from"./websocket-client.js";class b{wsClient;httpClient;agentConfig;serverConfig;agentId=null;displayName=null;projectId=null;isRunning=!1;isDegradedMode=!1;messageHandler=null;profileHandler=null;configHandler=null;pollTimer=null;statusReporter=null;newCharacterHandler=null;runtimeTokenRefreshTimer=null;cachedProfile=null;constructor(){g.loadConfig(),this.agentConfig=g.getAgentConfig(),this.serverConfig=g.getServerConfig(),this.wsClient=new p(this.serverConfig),this.httpClient=new f(this.serverConfig),this.setupWebSocketListeners()}async initialize(){const e=await I(this.agentConfig,this.serverConfig);if(!e){this.serverConfig.runtimeBridgeBaseUrl=void 0;return}g.applyRuntimeLaunchBinding(e),this.agentConfig=g.getAgentConfig(),this.serverConfig=g.getServerConfig(),this.wsClient=new p(this.serverConfig),this.httpClient=new f(this.serverConfig),this.setupWebSocketListeners()}setupWebSocketListeners(){this.wsClient.on("directMessage",e=>{this.handleIncomingDm(e)}),this.wsClient.on("groupMessage",e=>{this.handleIncomingGroup(e)}),this.wsClient.on("profileInfo",e=>{s.info(`[AgentClient] WebSocket profileInfo \u4E8B\u4EF6\u89E6\u53D1\uFF0CdisplayName: ${e.displayName}`),this.handleProfileInfo(e)}),this.wsClient.on("agentConfig",e=>{s.info("[AgentClient] WebSocket agentConfig \u4E8B\u4EF6\u89E6\u53D1"),this.configHandler&&this.configHandler(e??{})}),this.wsClient.on("agentStatus",e=>{s.info(`[AgentClient] WebSocket agentStatus \u4E8B\u4EF6\u89E6\u53D1: ${e.status}`)}),this.wsClient.on("disconnected",(e,t)=>{const n=t?.toString()||"";s.warn("[AgentClient] WebSocket \u65AD\u5F00\u8FDE\u63A5: code=%s, reason=%s, agentId=%s, \u6B63\u5728\u81EA\u52A8\u91CD\u8FDE",e,n,this.agentId)}),this.wsClient.on("error",e=>{s.error("[AgentClient] WebSocket \u9519\u8BEF: %s, stack=%s",e.message,e.stack)}),this.wsClient.on("reconnectFailed",()=>{s.error("[AgentClient] \u91CD\u8FDE\u5931\u8D25\uFF08\u5DF2\u8FBE\u91CD\u8BD5\u4E0A\u9650\uFF09\uFF0C\u505C\u6B62\u6D88\u606F\u5FAA\u73AF, agentId=%s",this.agentId),this.stop()}),this.wsClient.on("tokenRejected",()=>{s.error("[AgentClient] runtime token \u88AB 1008 \u62D2\u7EDD\u4E14\u5237\u65B0\u65E0\u53D8\u5316\uFF0C\u8FDB\u5165\u6162\u901F\u91CD\u8BD5\u7B49\u5F85\u65B0 token \u7B7E\u53D1: agentId=%s",this.agentId)}),this.wsClient.on("tokenRefreshFailed",e=>{s.error("[AgentClient] runtime token \u5237\u65B0\u8FDE\u7EED\u5931\u8D25 %s \u6B21\uFF0C\u8FDB\u5165\u6162\u901F\u91CD\u8BD5\u7B49\u5F85 bridge \u6062\u590D: agentId=%s",e,this.agentId)})}async register(){return s.info("[AgentClient] \u6B63\u5728\u6CE8\u518C Agent..."),this.hasPresetAgentId()?await this.registerWithExistingAgentId():await this.registerAsNewAgent()}hasPresetAgentId(){return!!(this.agentConfig.agentId&&this.agentConfig.agentId.trim().length>0)}async registerWithExistingAgentId(){const e=this.agentConfig.agentId;if(!e)throw new Error("Preset agentId is missing");this.agentId=e.trim();let t;try{t=await this.getProfileByAgentId(this.agentId)}catch(i){throw s.error("[AgentClient] getProfileByAgentId \u5931\u8D25 (presetAgentId=%s): %s",this.agentId,i instanceof Error?i.message:String(i)),i}this.displayName=t.displayName,this.projectId=t.projectId||null,s.info(`[AgentClient] \u4F7F\u7528\u9884\u8BBE AgentID \u7ED1\u5B9A\u5B9E\u4F8B: ${this.displayName} (${this.agentId})`),s.info(`[AgentClient] \u6240\u5C5E\u9879\u76EE: ${this.projectId||"(\u672A\u8BBE\u7F6E)"}`);try{await this.wsClient.connect(this.agentId)}catch(i){throw s.error("[AgentClient] WebSocket \u8FDE\u63A5\u5931\u8D25 (agentId=%s): %s",this.agentId,i instanceof Error?i.message:String(i)),i}s.info("[AgentClient] WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB\uFF0CAgent \u73B0\u5728\u5DF2\u4E0A\u7EBF"),this.initializeStatusReporter();const n=await this.getProfileByAgentId(this.agentId);return this.displayName=n.displayName,this.projectId=n.projectId||null,u(n.roleName||"unknown-role",n.instanceName||this.agentId),{agentId:this.agentId,displayName:this.displayName,status:n.isOnline?"online":"offline",projectId:n.projectId,workspacePath:n.workspacePath,runtimeStatus:n.runtimeStatus,runtimeSessionId:n.runtimeSessionId}}async registerAsNewAgent(){const e=await this.httpClient.callTool("register",{roleName:this.agentConfig.roleName,projectId:this.agentConfig.projectId,workspacePath:this.agentConfig.workspacePath,prompt:this.agentConfig.prompt,runtimeBridgeBaseUrl:this.serverConfig.runtimeBridgeBaseUrl}),t=e.agentId;if(!t)throw new Error("Registration failed: agentId was not returned");return this.agentId=t,this.displayName=e.displayName,this.projectId=e.projectId||null,u(this.agentConfig.roleName||"unknown-role",e.instanceName||t),s.info(`[AgentClient] \u6CE8\u518C\u6210\u529F: ${this.displayName} (${this.agentId})`),s.info(`[AgentClient] \u72B6\u6001: ${e.status}`),s.info(`[AgentClient] \u6240\u5C5E\u9879\u76EE: ${this.projectId||"(\u672A\u8BBE\u7F6E)"}`),await this.wsClient.connect(this.agentId),s.info("[AgentClient] WebSocket \u8FDE\u63A5\u5DF2\u5EFA\u7ACB\uFF0CAgent \u73B0\u5728\u5DF2\u4E0A\u7EBF"),this.initializeStatusReporter(),e.isExisting===!1&&this.newCharacterHandler&&this.newCharacterHandler(),e}async unregister(){s.info("[AgentClient] \u6B63\u5728\u6CE8\u9500 Agent..."),this.agentId&&await this.httpClient.callTool("unregister",{agentId:this.agentId}),this.stopRuntimeTokenRefresh(),this.wsClient.disconnect(),this.agentId=null,this.displayName=null,this.projectId=null,this.isRunning=!1,s.info("[AgentClient] \u5DF2\u6CE8\u9500")}async discoverColleagues(){return await this.httpClient.callTool("discover_colleague",{agentId:this.agentId})}async discoverGroupRooms(){return this.ensureRegistered(),await this.httpClient.callTool("discover_group_rooms",{agentId:this.agentId})}async hireColleague(e){return this.ensureRegistered(),await this.httpClient.callTool("hire_colleague",{agentId:this.agentId,...e})}async hireColleaguesBatch(e){return this.ensureRegistered(),await this.httpClient.callTool("hire_colleagues_batch",{agentId:this.agentId,...e})}async callAgentScopedTool(e,t){return this.ensureRegistered(),await this.httpClient.callTool(e,{agentId:this.agentId,...t})}async reportMemoryStats(e){await this.callAgentScopedTool("report_memory_stats",{memoryStats:e})}async sendGroupMessage(e,t,n){this.ensureRegistered();const i=t||this.projectId||this.agentConfig.projectId;return await this.httpClient.callTool("send_group_message",{agentId:this.agentId,projectId:i,roomId:n,content:e})}async sendDirectMessage(e,t,n=!1,i,r){this.ensureRegistered();const a={agentId:this.agentId,targetId:e,content:t,requireReply:n,waitReply:n,replyToCallId:i};if(typeof r=="number"&&r>0&&(a.timeoutMs=r),n){const h=typeof r=="number"&&r>0?r+3e4:0;return await this.httpClient.callToolOnce("send_dm",a,h)}return await this.httpClient.callTool("send_dm",a)}async sendCallAndWaitReply(e,t,n=6e4){const i=await this.sendDirectMessage(e,t,!0,void 0,n);if(i.reply)return i.reply;throw new Error("No reply received for call-style direct message before timeout")}async replyToCall(e,t,n){return await this.sendDirectMessage(t,n,!1,e)}async getDmHistory(e,t=50,n){this.ensureRegistered();const i={agentId:this.agentId,targetUserId:e,limit:t};return n&&(i.since=n),await this.httpClient.callTool("get_dm_history",i)}async getGroupHistory(e=50,t,n){if(this.ensureRegistered(),t){const o={projectId:this.projectId||this.agentConfig.projectId,roomId:t,limit:e};return n&&(o.since=n),await this.httpClient.callTool("get_group_history",o)}const i=await this.discoverGroupRooms(),r=this.projectId||this.agentConfig.projectId,a=this.collectRoomIds(i,r);if(a.length===0)return{messages:[],count:0};const c=(await Promise.all(a.map(async o=>{const l={projectId:r,roomId:o,limit:e};return n&&(l.since=n),await this.httpClient.callTool("get_group_history",l)}))).flatMap(o=>o.messages).sort((o,l)=>new Date(o.timestamp).getTime()-new Date(l.timestamp).getTime());return{messages:c,count:c.length}}async fetchUnreadMessages(e={}){this.ensureRegistered();const t=this.projectId||this.agentConfig.projectId,n={agentId:this.agentId,blockIfEmpty:e.blockIfEmpty??!1};e.since&&(n.since=e.since),e.blockTimeoutMs&&e.blockTimeoutMs>0&&(n.blockTimeoutMs=e.blockTimeoutMs);const i=e.includePipelineTasks?"poll_message":"get_dm_messages",r=await this.fetchDirectMessagesWithRecovery(i,n),a=await this.discoverGroupRooms(),h=this.collectRoomIds(a,t),c=await Promise.all(h.map(async o=>{const l={agentId:this.agentId,projectId:t,roomId:o};e.since&&(l.since=e.since);try{return await this.httpClient.callTool("get_group_messages",l)}catch(d){return s.warn("[AgentClient] \u8DF3\u8FC7\u7FA4\u623F\u95F4\u672A\u8BFB\u8865\u62C9\u5931\u8D25: roomId=%s, error=%s",o,d instanceof Error?d.message:String(d)),{messages:[],currentReadPos:0,hasMore:!1}}}));return{directMessages:Array.isArray(r.messages)?r.messages:[],groupMessages:c.flatMap(o=>this.normalizeGroupMessages(o)).sort((o,l)=>new Date(o.timestamp).getTime()-new Date(l.timestamp).getTime())}}async markDmRead(e){if(e.length===0)return new Set;this.ensureRegistered();const t=await this.httpClient.callTool("mark_dm_read",{agentId:this.agentId,msgIds:e});return new Set(Array.isArray(t?.deliverableMsgIds)?t.deliverableMsgIds:[])}async markGroupRead(e,t){if(t.length===0)return new Set;this.ensureRegistered();const n=await this.httpClient.callTool("mark_group_read",{agentId:this.agentId,roomId:e,msgIds:t});return new Set(Array.isArray(n?.deliverableMsgIds)?n.deliverableMsgIds:[])}async fetchBrowserListenerEvents(){const e=this.serverConfig.runtimeBridgeBaseUrl,t=this.agentId;if(!e||!t)return s.info("[AgentClient] fetchBrowserListenerEvents \u8DF3\u8FC7: bridgeBaseUrl=%s, agentId=%s",e||"(\u7A7A)",t||"(\u7A7A)"),[];try{const n=`${e}/runtime/browser-listeners/${encodeURIComponent(t)}/events`,i=this.serverConfig.runtimeAccessToken||"";s.debug("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6: url=%s, tokenLen=%d",n,i.length);const r=await fetch(n,{method:"GET",headers:{"X-Runtime-Token":i},signal:AbortSignal.timeout(3e3)});if(!r.ok)return s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u5931\u8D25: HTTP %s, url=%s",r.status,n),[];const a=await r.json();return!a.ok||!Array.isArray(a.events)?(s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u54CD\u5E94\u5F02\u5E38: ok=%s, eventsType=%s",a.ok,Array.isArray(a.events)?"array":typeof a.events),[]):(s.debug("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u6210\u529F: %d \u6761\u4E8B\u4EF6",a.events.length),a.events)}catch(n){return s.warn("[AgentClient] \u62C9\u53D6\u76D1\u542C\u5668\u4E8B\u4EF6\u5F02\u5E38: %s",n instanceof Error?n.message:String(n)),[]}}async notifyBridgeForceMessage(){const e=this.serverConfig.runtimeBridgeBaseUrl,t=this.agentId;if(!(!e||!t))try{const n=`${e}/runtime/sessions/${encodeURIComponent(t)}/notify-force-message`,i=this.serverConfig.runtimeAccessToken||"",r=await fetch(n,{method:"POST",headers:{"X-Runtime-Token":i},signal:AbortSignal.timeout(3e3)});r.ok||s.warn("[AgentClient] \u901A\u77E5 bridge \u5F3A\u5236\u6D88\u606F\u5931\u8D25: HTTP %s, url=%s",r.status,n)}catch(n){s.warn("[AgentClient] \u901A\u77E5 bridge \u5F3A\u5236\u6D88\u606F\u5F02\u5E38: %s",n instanceof Error?n.message:String(n))}}async fetchDirectMessagesWithRecovery(e,t){if(e==="poll_message")try{return await this.httpClient.callToolOnce(e,t)}catch(n){if(this.isRecoverableLongPollError(n))return s.warn("[AgentClient] poll_message \u5355\u6B21\u957F\u8F6E\u8BE2\u8D85\u65F6\uFF0C\u6309\u7A7A\u7ED3\u679C\u9000\u907F\u540E\u7EE7\u7EED\u7B49\u5F85:",n),{messages:[]};throw n}return await this.httpClient.callTool(e,t)}isRecoverableLongPollError(e){return e instanceof Error?/MCP tool poll_message call failed: 503(?:\s|$)/.test(e.message)||/MCP tool poll_message timed out after \d+ms/.test(e.message):!1}normalizeGroupMessages(e){return Array.isArray(e.messages)?e.messages.filter(t=>!!(t&&typeof t=="object"&&typeof t.msgId=="number"&&typeof t.senderId=="string"&&typeof t.content=="string"&&typeof t.timestamp=="string")):[]}collectRoomIds(e,t){const n=e.groups.filter(i=>i.projectId===t).map(i=>i.id);return Array.from(new Set(n))}async getProfile(){this.ensureRegistered();const e=this.agentId;if(!e)throw new Error("Agent is not registered");return{...await this.getProfileByAgentId(e),describe:this.agentConfig.describe}}async getProfileByAgentId(e){return await this.httpClient.callTool("get_profile",{agentId:e})}async getMyTasks(){return this.ensureRegistered(),await this.httpClient.callTool("my_task",{agentId:this.agentId})}async createTaskInstanceComment(e){this.ensureRegistered();const t={agentId:this.agentId,...e};return await this.httpClient.callTool("create_task_instance_comment",t)}async submitTaskResult(e,t,n){this.ensureRegistered();const i={agentId:this.agentId,nodeId:e,outputPayload:t};return n&&Array.isArray(n)&&n.length>0&&(i.changeLog=n),await this.httpClient.callTool("submit_task_result",i)}async startTaskRollback(e){return this.ensureRegistered(),await this.httpClient.callTool("start_task_rollback",{agentId:this.agentId,...e})}async finishTaskRollback(e){return this.ensureRegistered(),await this.httpClient.callTool("finish_task_rollback",{agentId:this.agentId,...e})}async rejectTask(e,t){return this.ensureRegistered(),await this.httpClient.callTool("reject_task",{agentId:this.agentId,nodeId:e,reason:t})}async terminateTask(e,t){return this.ensureRegistered(),await this.httpClient.callTool("terminate_task",{agentId:this.agentId,taskInstanceId:e,reason:t})}async getDebugLogConfig(){return await this.httpClient.callTool("get_debug_log_config",{agentId:this.agentId})}async queryDebugLogs(e){return this.ensureRegistered(),await this.httpClient.callTool("query_debug_logs",{...e})}async queryFileChangesHistory(e){return this.ensureRegistered(),await this.httpClient.callTool("query_file_changes_history",{...e})}async getRuntimeFeatures(){return await this.httpClient.fetchRuntimeFeatures()}async listLaunchConfigs(){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_list",{})}async createLaunchConfig(e){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_create",{...e})}async updateLaunchConfig(e){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_update",{...e})}async deleteLaunchConfig(e){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_delete",{configId:e})}async startLaunchConfig(e){this.ensureRegistered();const t=Math.max(0,Math.min(3e5,Number(e.wait_timeout_ms)||0));return t>0?await this.httpClient.callToolOnce("launch_config_start",e,t+6e4):await this.httpClient.callTool("launch_config_start",{...e})}async restartLaunchConfig(e){this.ensureRegistered();const t=Math.max(0,Math.min(3e5,Number(e.wait_timeout_ms)||0));return t>0?await this.httpClient.callToolOnce("launch_config_restart",e,t+6e4):await this.httpClient.callTool("launch_config_restart",{...e})}async stopLaunchConfig(e){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_stop",{configId:e})}async getLaunchLogs(e){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_logs",{...e})}async listLaunchRuns(){return this.ensureRegistered(),await this.httpClient.callTool("launch_config_status",{})}start(e,t){if(this.isRunning){s.warn("[AgentClient] \u6D88\u606F\u5FAA\u73AF\u5DF2\u5728\u8FD0\u884C\u4E2D");return}this.isDegradedMode||this.ensureRegistered(),this.messageHandler=e,this.profileHandler=t?.profileHandler||null,this.configHandler=t?.configHandler||null,this.isRunning=!0,s.info(`[AgentClient] start() \u88AB\u8C03\u7528\uFF0CcachedProfile: ${this.cachedProfile?"\u5B58\u5728":"\u4E0D\u5B58\u5728"}, profileHandler: ${this.profileHandler?"\u5DF2\u8BBE\u7F6E":"\u672A\u8BBE\u7F6E"}, \u964D\u7EA7\u6A21\u5F0F: ${this.isDegradedMode?"\u662F":"\u5426"}`),this.cachedProfile&&this.profileHandler&&(s.info(`[AgentClient] \u5904\u7406\u7F13\u5B58\u7684 Profile \u4FE1\u606F: ${this.cachedProfile.displayName}`),this.profileHandler(this.cachedProfile),this.cachedProfile=null),t?.pollMode??!1?(s.warn("[AgentClient] \u542F\u52A8\u6D88\u606F\u4E3B\u5FAA\u73AF\uFF08HTTP\u8F6E\u8BE2\u6A21\u5F0F - \u4E0D\u63A8\u8350\uFF09"),this.messageLoop()):s.info("[AgentClient] \u542F\u52A8\u6D88\u606F\u4E3B\u5FAA\u73AF\uFF08WebSocket\u63A8\u9001\u6A21\u5F0F - \u63A8\u8350\uFF09")}stop(){this.isRunning=!1,this.pollTimer&&(clearTimeout(this.pollTimer),this.pollTimer=null),this.stopRuntimeTokenRefresh(),s.info("[AgentClient] \u6D88\u606F\u4E3B\u5FAA\u73AF\u5DF2\u505C\u6B62")}async messageLoop(){for(;this.isRunning;)try{await this.delay(m)}catch(e){s.error("[AgentClient] \u6D88\u606F\u5FAA\u73AF\u9519\u8BEF:",e),await this.delay(5e3)}}handleIncomingDm(e){e.senderId!==this.agentId&&this.processMessage(e)}handleIncomingGroup(e){e.senderId!==this.agentId&&this.processMessage(e)}handleProfileInfo(e){s.info(`[AgentClient] \u6536\u5230 Profile \u4FE1\u606F: ${e.displayName} (${e.roleName})`),e.projectId&&(this.projectId=e.projectId,s.info(`[AgentClient] \u66F4\u65B0\u6240\u5C5E\u9879\u76EE: ${this.projectId}`)),e.roleName&&e.roleName!==this.agentConfig.roleName&&(s.info(`[AgentClient] \u540C\u6B65\u89D2\u8272\u540D: "${this.agentConfig.roleName||"(\u7A7A)"}" -> "${e.roleName}"`),this.agentConfig.roleName=e.roleName),e.roleName&&u(e.roleName,e.instanceName||this.agentId||""),this.profileHandler?this.profileHandler(e):(s.info("[AgentClient] profileHandler \u672A\u8BBE\u7F6E\uFF0C\u7F13\u5B58 Profile \u4FE1\u606F"),this.cachedProfile=e)}async processMessage(e){if(!this.messageHandler)return;const t=typeof e.msgId=="string";s.info(`[AgentClient] \u6536\u5230${t?"\u79C1\u4FE1":"\u7FA4\u6D88\u606F"}\u6765\u81EA ${e.senderName}: ${e.content.substring(0,50)}...`);try{const n=await this.messageHandler(e);if(t){const i=e;i.requireReply&&i.callId&&await this.replyToCall(i.callId,i.senderId,n||"\u5DF2\u6536\u5230\u6D88\u606F\uFF0C\u6B63\u5728\u5904\u7406\u4E2D...")}}catch(n){const i=typeof e.msgId=="string";if(s.error("[AgentClient] \u5904\u7406\u6D88\u606F\u5931\u8D25: type=%s, senderId=%s, senderName=%s, msgId=%s, error=%s",i?"\u79C1\u4FE1":"\u7FA4\u6D88\u606F",e.senderId,e.senderName,e.msgId,n instanceof Error?n.message:String(n)),i){const r=e;r.requireReply&&r.callId&&await this.replyToCall(r.callId,r.senderId,`\u5904\u7406\u5931\u8D25: ${n}`)}}}ensureRegistered(){if(!this.agentId)throw new Error("Agent is not registered")}delay(e){return new Promise(t=>{this.pollTimer=setTimeout(t,e)})}onNewCharacter(e){this.newCharacterHandler=e}getState(){return{agentId:this.agentId,displayName:this.displayName,projectId:this.projectId,isConnected:this.wsClient.isConnected(),isRunning:this.isRunning}}getAgentConfig(){return this.agentConfig}getServerConfig(){return this.serverConfig}reloadConfig(){g.loadConfig(),this.agentConfig=g.getAgentConfig(),s.info(`[AgentClient] \u914D\u7F6E\u5DF2\u91CD\u65B0\u52A0\u8F7D\uFF0C\u89D2\u8272: ${this.agentConfig.roleName}`)}enterDegradedMode(){this.isDegradedMode=!0,s.warn("[AgentClient] \u8FDB\u5165\u964D\u7EA7\u6A21\u5F0F\uFF0C\u5C06\u5728\u540E\u53F0\u6301\u7EED\u5C1D\u8BD5\u91CD\u8FDE")}exitDegradedMode(){this.isDegradedMode=!1,s.info("[AgentClient] \u9000\u51FA\u964D\u7EA7\u6A21\u5F0F")}isInDegradedMode(){return this.isDegradedMode}async retryRegistrationInBackground(e,t){let n=0;(async()=>{for(;this.isDegradedMode;)try{s.info(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u5C1D\u8BD5\u91CD\u65B0\u6CE8\u518C (${n+1}/${t===-1?"\u65E0\u9650":t})`);const r=await this.register();s.info(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6CE8\u518C\u6210\u529F - ${r.displayName} (${r.agentId})`),this.exitDegradedMode(),this.messageHandler&&this.start(this.messageHandler);return}catch(r){n++;const a=r instanceof Error?r.message:String(r);if(s.warn(`[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6CE8\u518C\u5931\u8D25 (${n}/${t===-1?"\u65E0\u9650":t}): ${a}`),t>0&&n>=t){s.error("[AgentClient] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u505C\u6B62\u91CD\u8FDE"),this.isDegradedMode=!1;return}await new Promise(h=>setTimeout(h,e))}})().catch(r=>{s.error("[AgentClient] \u964D\u7EA7\u6A21\u5F0F\u540E\u53F0\u91CD\u8BD5\u5FAA\u73AF\u5F02\u5E38\u9000\u51FA: error=%s, stack=%s",r instanceof Error?r.message:String(r),r instanceof Error?r.stack:"(no stack)")})}initializeStatusReporter(){if(!this.agentId){s.warn("[AgentClient] \u65E0\u6CD5\u521D\u59CB\u5316\u72B6\u6001\u62A5\u544A\u5668\uFF1AagentId \u4E3A\u7A7A");return}this.statusReporter=new y({agentId:this.agentId,sendStatusUpdate:e=>{this.wsClient.send({type:"AGENT_STATUS",data:e,timestamp:new Date().toISOString()})}}),C(e=>this.reportMcpNodeLog(e)),this.startRuntimeTokenRefresh(),s.info("[AgentClient] \u72B6\u6001\u62A5\u544A\u5668\u5DF2\u521D\u59CB\u5316")}startRuntimeTokenRefresh(){this.stopRuntimeTokenRefresh();const e=this.serverConfig.runtimeTokenRefreshIntervalMs;if(!e||e<=0){s.warn("[AgentClient] runtimeTokenRefreshIntervalMs \u672A\u914D\u7F6E\u6216\u65E0\u6548\uFF0C\u8DF3\u8FC7\u4E3B\u52A8\u7EED\u671F");return}const t=this.agentId;if(!t){s.warn("[AgentClient] agentId \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u4E3B\u52A8\u7EED\u671F");return}this.runtimeTokenRefreshTimer=setInterval(async()=>{try{await this.httpClient.refreshRuntimeAccessToken(t)&&s.info("[AgentClient] runtimeAccessToken \u5DF2\u4E3B\u52A8\u7EED\u671F")}catch(n){s.warn("[AgentClient] \u4E3B\u52A8\u7EED\u671F runtimeAccessToken \u5931\u8D25: %s",n instanceof Error?n.message:String(n)),n instanceof Error&&n.stack&&s.warn("[AgentClient] \u4E3B\u52A8\u7EED\u671F\u5F02\u5E38\u5806\u6808:",n.stack)}},e),this.runtimeTokenRefreshTimer.unref?.(),s.info(`[AgentClient] runtime token \u4E3B\u52A8\u7EED\u671F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694 ${e}ms`)}stopRuntimeTokenRefresh(){this.runtimeTokenRefreshTimer&&(clearInterval(this.runtimeTokenRefreshTimer),this.runtimeTokenRefreshTimer=null)}reportMcpNodeLog(e){const t=this.wsClient;!this.agentId||!this.wsClient.isConnected()||typeof t.sendMcpNodeLog!="function"||t.sendMcpNodeLog(e)}getStatusReporter(){return this.statusReporter}}export{b as AgentClient};
|
|
2
2
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
|
3
3
|
import { AgentClient } from "./agent-client.js";
|
|
4
|
+
import { setMcpNodeLogSink } from "./logger.js";
|
|
4
5
|
// Helper function to create a complete ProfileInfo
|
|
5
6
|
function createProfileInfo(overrides = {}) {
|
|
6
7
|
return {
|
|
@@ -779,11 +780,24 @@ describe("AgentClient", () => {
|
|
|
779
780
|
throw new Error("transient room failure");
|
|
780
781
|
},
|
|
781
782
|
});
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
783
|
+
// logger 在非 TTY(测试子进程为管道)下有意跳过 console 写,改经 WS sink 观察告警
|
|
784
|
+
const warnMessages = [];
|
|
785
|
+
setMcpNodeLogSink((entry) => {
|
|
786
|
+
if (entry.level === "warn") {
|
|
787
|
+
warnMessages.push(entry.message);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
warnMessages.length = 0; // 丢弃绑定 sink 时同步回放的历史缓冲日志
|
|
791
|
+
try {
|
|
792
|
+
const result = await client.fetchUnreadMessages();
|
|
793
|
+
assert.deepEqual(result.directMessages, []);
|
|
794
|
+
assert.deepEqual(result.groupMessages, [validGroupMessage]);
|
|
795
|
+
assert.equal(warnMessages.length, 1);
|
|
796
|
+
assert.match(warnMessages[0], /跳过群房间未读补拉失败/);
|
|
797
|
+
}
|
|
798
|
+
finally {
|
|
799
|
+
setMcpNodeLogSink(null);
|
|
800
|
+
}
|
|
787
801
|
});
|
|
788
802
|
});
|
|
789
803
|
describe("project name update from profile", () => {
|
|
@@ -56,20 +56,20 @@ describe("ContextManager", () => {
|
|
|
56
56
|
it("estimates ASCII text at ~4 chars/token", () => {
|
|
57
57
|
const cm = new ContextManager({});
|
|
58
58
|
const tokens = cm.estimateTokens("hello world this is a test message");
|
|
59
|
-
// "hello world this is a test message" = 34 chars, 34/4
|
|
60
|
-
assert.equal(tokens,
|
|
59
|
+
// "hello world this is a test message" = 34 chars, 34/4 → ceil 9,x1.15 安全填充 → 11
|
|
60
|
+
assert.equal(tokens, 11);
|
|
61
61
|
});
|
|
62
62
|
it("estimates Chinese text at ~1.5 chars/token", () => {
|
|
63
63
|
const cm = new ContextManager({});
|
|
64
64
|
const tokens = cm.estimateTokens("一段中文测试消息");
|
|
65
|
-
// 9 Chinese chars, 9/1.5
|
|
66
|
-
assert.equal(tokens,
|
|
65
|
+
// 9 Chinese chars, 9/1.5 → 6,x1.15 安全填充 → 7
|
|
66
|
+
assert.equal(tokens, 7);
|
|
67
67
|
});
|
|
68
68
|
it("estimates mixed text correctly", () => {
|
|
69
69
|
const cm = new ContextManager({});
|
|
70
70
|
const tokens = cm.estimateTokens("你好 world 测试 hello");
|
|
71
|
-
// Chinese: 4 chars ÷ 1.5 ≈ 3, ASCII: 11 chars ÷ 4 ≈ 3,
|
|
72
|
-
assert.equal(tokens,
|
|
71
|
+
// Chinese: 4 chars ÷ 1.5 ≈ 3, ASCII: 11 chars ÷ 4 ≈ 3, base 6,x1.15 → 7
|
|
72
|
+
assert.equal(tokens, 7);
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
describe("estimateTotalUsage", () => {
|
|
@@ -13,6 +13,8 @@ export declare class HttpClient {
|
|
|
13
13
|
|
|
14
14
|
callTool<T = unknown>(toolName: string, args: Record<string, unknown>): Promise<T>;
|
|
15
15
|
|
|
16
|
+
fetchRuntimeFeatures(): Promise<Record<string, boolean>>;
|
|
17
|
+
|
|
16
18
|
callToolOnce<T = unknown>(toolName: string, args: Record<string, unknown>, timeoutMs?: number): Promise<T>;
|
|
17
19
|
|
|
18
20
|
private singleFetchWithTimeout;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{logger as a}from"./logger.js";const d=12e4,w=5e3,g=3e4,m=1e3;function h(c){const
|
|
1
|
+
import{logger as a}from"./logger.js";const d=12e4,w=5e3,g=3e4,m=1e3;function h(c){const t=c.trim();return t?t.length<=m?t:`${t.slice(0,m)}...(truncated)`:""}function T(c){return c.replace(/^((?:https?|wss?):\/\/(?:\[[^\]]+\]|[^/:?#]+):\d+):\d+(?=\/|$)/i,"$1")}function y(c){const t=c.toLowerCase();return t==="localhost"||t==="::1"||t==="[::1]"?"127.0.0.1":t}function p(c,t={}){const r=String(c||"").trim();if(!r)return;const n=T(r);try{const e=new URL(n);e.protocol==="ws:"?e.protocol="http:":e.protocol==="wss:"?e.protocol="https:":e.protocol=e.protocol.toLowerCase(),e.hostname=y(e.hostname);const s=t.fromAgentWebSocketUrl?e.pathname.replace(/\/ws\/agent\/?$/,""):e.pathname;return e.pathname=s.includes("/mcp/call")?s:`${s.replace(/\/$/,"")}/mcp/call`,e.search="",e.hash="",e.toString()}catch{const e=(t.fromAgentWebSocketUrl?n.replace(/\/ws\/agent\/?$/i,""):n).replace(/\/+$/,"");return e.endsWith("/mcp/call")?e:`${e}/mcp/call`}}const C=6e5;class k{serverConfig;mcpHttpUrl;tokenRefreshPromise=null;constructor(t){this.serverConfig=t,this.mcpHttpUrl=this.resolveMcpHttpUrl()}async callTool(t,r){await this.ensureRuntimeAccessToken();const n=Date.now()+this.resolveRetryMaxAccumulatedMs();let e=0;for(;;){e++;let s;try{s=await this.executeToolRequest(t,r)}catch(o){if(Date.now()<n&&this.isTransientError(o)){await this.delayForRetry(n,t,e);continue}throw a.warn("[HttpClient] MCP tool %s \u8BF7\u6C42\u4E0D\u53EF\u6062\u590D\u5F02\u5E38 (attempt=%s): %s",t,e,o instanceof Error?o.message:String(o)),o}if(s.status===401){if(a.warn(`[HttpClient] MCP Tool ${t} \u8FD4\u56DE 401\uFF0C\u5C1D\u8BD5\u5411 bridge \u5237\u65B0 token`),await this.tryRefreshToken(r)){let u;try{u=await this.executeToolRequest(t,r)}catch(l){if(Date.now()<n&&this.isTransientError(l)){await this.delayForRetry(n,t,e);continue}throw a.warn("[HttpClient] MCP tool %s 401\u91CD\u8BD5\u5F02\u5E38 (attempt=%s): %s",t,e,l instanceof Error?l.message:String(l)),l}if(!u.ok){if(Date.now()<n&&this.isRetryableHttpStatus(u.status)){await this.delayForRetry(n,t,e);continue}const l=typeof u.text=="function"?h(await u.text().catch(()=>"")):"";throw new Error(`MCP tool ${t} call failed: ${u.status} ${u.statusText}${l?` - ${l}`:""}`)}const f=await u.json();if(typeof f.error=="string"&&f.error.trim().length>0)throw new Error(`MCP tool ${t} failed: ${f.error}`);return f}a.warn("[HttpClient] token \u5237\u65B0\u5931\u8D25\u6216\u65E0\u65B0 token\uFF0C\u8FD4\u56DE\u539F\u59CB 401 \u9519\u8BEF")}if(!s.ok){if(Date.now()<n&&this.isRetryableHttpStatus(s.status)){await this.delayForRetry(n,t,e);continue}const o=typeof s.text=="function"?h(await s.text().catch(()=>"")):"";throw new Error(`MCP tool ${t} call failed: ${s.status} ${s.statusText}${o?` - ${o}`:""}`)}const i=await s.json();if(typeof i.error=="string"&&i.error.trim().length>0)throw new Error(`MCP tool ${t} failed: ${i.error}`);return i}}async fetchRuntimeFeatures(){await this.ensureRuntimeAccessToken();const t=this.mcpHttpUrl.replace(/\/mcp\/call(\/)?$/,"/mcp/features$1");let r;try{r=await fetch(t,{method:"POST",headers:this.buildRequestHeaders(),body:JSON.stringify({})})}catch(e){throw a.warn("[HttpClient] \u67E5\u8BE2\u80FD\u529B\u5F00\u5173\u8BF7\u6C42\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),new Error(`fetch runtime features failed: ${e instanceof Error?e.message:String(e)}`)}if(!r.ok)throw a.warn("[HttpClient] \u67E5\u8BE2\u80FD\u529B\u5F00\u5173\u5931\u8D25: status=%s, statusText=%s",r.status,r.statusText),new Error(`fetch runtime features failed: ${r.status} ${r.statusText}`);const n=await r.json();return!n||typeof n.features!="object"||n.features===null?{}:n.features}async callToolOnce(t,r,n=C){await this.ensureRuntimeAccessToken();const e=o=>fetch(this.mcpHttpUrl,{method:"POST",headers:this.buildRequestHeaders(),body:JSON.stringify({tool:t,arguments:r}),signal:o});let s=await this.singleFetchWithTimeout(e,n,t);if(s.status===401&&(a.warn(`[HttpClient] MCP Tool ${t} \u8FD4\u56DE 401\uFF0C\u5C1D\u8BD5\u5411 bridge \u5237\u65B0 token`),await this.tryRefreshToken(r)&&(s=await this.singleFetchWithTimeout(e,n,t))),!s.ok){const o=typeof s.text=="function"?h(await s.text().catch(()=>"")):"";throw new Error(`MCP tool ${t} call failed: ${s.status} ${s.statusText}${o?` - ${o}`:""}`)}const i=await s.json();if(typeof i.error=="string"&&i.error.trim().length>0)throw new Error(`MCP tool ${t} failed: ${i.error}`);return i}async singleFetchWithTimeout(t,r,n){const e=new AbortController;let s;r>0&&(s=setTimeout(()=>e.abort(),r));try{return await t(e.signal)}catch(i){throw e.signal.aborted?new Error(`MCP tool ${n} timed out after ${r}ms`):i}finally{s!==void 0&&clearTimeout(s)}}async executeToolRequest(t,r){return await this.fetchWithTimeout(this.mcpHttpUrl,{method:"POST",headers:this.buildRequestHeaders(),body:JSON.stringify({tool:t,arguments:r})},`MCP tool ${t}`)}async fetchWithTimeout(t,r,n){const e=this.resolveRequestTimeoutMs(),s=new AbortController,i=setTimeout(()=>s.abort(),e);try{return await fetch(t,{...r,signal:s.signal})}catch(o){throw s.signal.aborted?new Error(`${n} timed out after ${e}ms`):o}finally{clearTimeout(i)}}resolveRequestTimeoutMs(){const t=this.serverConfig.mcpHttpRequestTimeoutMs;return typeof t=="number"&&Number.isFinite(t)&&t>0?t:d}resolveRetryIntervalMs(){const t=this.serverConfig.mcpRetryIntervalMs;return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:w}resolveRetryMaxAccumulatedMs(){const t=this.serverConfig.mcpRetryMaxAccumulatedMs;return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:g}isTransientError(t){if(!(t instanceof Error))return!1;const r=t.message;return!!(/timed out after \d+ms/.test(r)||/fetch failed/i.test(r)||/network/i.test(r)||/ECONNREFUSED/i.test(r)||/ECONNRESET/i.test(r))}isRetryableHttpStatus(t){return t>=500||t===429}async delayForRetry(t,r,n){const e=t-Date.now(),s=Math.min(this.resolveRetryIntervalMs(),Math.max(0,e));a.warn(`[HttpClient] MCP tool ${r} \u8BF7\u6C42\u5931\u8D25\uFF0C${s}ms \u540E\u91CD\u8BD5 (\u7B2C${n}\u6B21)`),s>0&&await new Promise(i=>setTimeout(i,s))}buildRequestHeaders(){const t={"Content-Type":"application/json"};return this.serverConfig.runtimeAccessToken&&(t["X-Runtime-Token"]=this.serverConfig.runtimeAccessToken,t.Authorization=`Bearer ${this.serverConfig.runtimeAccessToken}`),t}async ensureRuntimeAccessToken(){if(!(this.serverConfig.runtimeAccessToken||!this.serverConfig.runtimeBridgeBaseUrl))throw new Error("runtimeAccessToken is required; MCP nodes must claim a session token from launch binding")}async tryRefreshToken(t){if(this.tokenRefreshPromise)return this.tokenRefreshPromise;this.tokenRefreshPromise=this.doRefreshToken(t);try{return await this.tokenRefreshPromise}finally{this.tokenRefreshPromise=null}}async doRefreshToken(t){const r=this.serverConfig.runtimeBridgeBaseUrl;if(!r)return a.warn("[HttpClient] \u65E0 runtimeBridgeBaseUrl\uFF0C\u65E0\u6CD5\u5237\u65B0 token"),!1;const n=String(t?.agentId||"").trim();if(!n)return a.warn("[HttpClient] \u65E0 agentId\uFF0C\u65E0\u6CD5\u5237\u65B0 token"),!1;try{const e=await fetch(`${r}/runtime/binding/refresh-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agentId:n,currentToken:this.serverConfig.runtimeAccessToken||""})});if(!e.ok)return a.warn(`[HttpClient] bridge refresh-token \u8FD4\u56DE ${e.status}\uFF0C\u65E0\u6CD5\u5237\u65B0`),!1;const i=(await e.json()).runtimeAccessToken;return i?i===this.serverConfig.runtimeAccessToken?!1:(this.serverConfig.runtimeAccessToken=i,a.info("[HttpClient] runtimeAccessToken \u5DF2\u901A\u8FC7 bridge refresh \u66F4\u65B0"),!0):(a.warn("[HttpClient] bridge \u672A\u8FD4\u56DE\u65B0 token"),!1)}catch(e){return a.warn("[HttpClient] \u5237\u65B0 token \u5931\u8D25: %s",e instanceof Error?e.message:String(e)),e instanceof Error&&e.stack&&a.warn("[HttpClient] \u5237\u65B0 token \u5F02\u5E38\u5806\u6808:",e.stack),!1}}async refreshRuntimeAccessToken(t){return this.tryRefreshToken({agentId:t})}resolveMcpHttpUrl(){const t=this.serverConfig.mcpHttpUrl;if(t&&t.trim().length>0){const n=p(t);if(n)return n}const r=p(this.serverConfig.serverUrl,{fromAgentWebSocketUrl:!0});return r||this.serverConfig.serverUrl}getMcpHttpUrl(){return this.mcpHttpUrl}}export{k as HttpClient};
|
|
2
2
|
|