shennian 0.2.99 → 0.2.101
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/publish-build-manifest.json +77 -27
- package/dist/src/channels/runtime.d.ts +5 -1
- package/dist/src/channels/runtime.js +3 -3
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +59 -1
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -1
- package/dist/src/channels/wechat-channel/helper-client.js +3 -3
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +1 -1
- package/dist/src/channels/wechat-channel/observer.d.ts +32 -2
- package/dist/src/channels/wechat-channel/observer.js +6 -6
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +2 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -1
- package/dist/src/channels/wechat-channel/pacing.d.ts +3 -0
- package/dist/src/channels/wechat-channel/pacing.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.d.ts +7 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -1
- package/dist/src/channels/wechat-channel/runner.d.ts +0 -1
- package/dist/src/channels/wechat-channel/runner.js +1 -1
- package/dist/src/channels/wechat-channel/scheduler.d.ts +4 -3
- package/dist/src/channels/wechat-channel/scheduler.js +1 -1
- package/dist/src/channels/wechat-rpa.d.ts +5 -1
- package/dist/src/channels/wechat-rpa.js +4 -4
- package/dist/src/commands/runtime.d.ts +89 -0
- package/dist/src/commands/runtime.js +1 -0
- package/dist/src/commands/wechat/command.d.ts +2 -0
- package/dist/src/commands/wechat/command.js +1 -0
- package/dist/src/commands/wechat/direct.d.ts +14 -0
- package/dist/src/commands/wechat/direct.js +2 -0
- package/dist/src/commands/wechat/doctor.d.ts +2 -0
- package/dist/src/commands/wechat/doctor.js +1 -0
- package/dist/src/commands/wechat/ipc.d.ts +9 -0
- package/dist/src/commands/wechat/ipc.js +1 -0
- package/dist/src/commands/wechat/operations.d.ts +3 -0
- package/dist/src/commands/wechat/operations.js +4 -0
- package/dist/src/commands/wechat/output.d.ts +7 -0
- package/dist/src/commands/wechat/output.js +4 -0
- package/dist/src/commands/wechat/types.d.ts +141 -0
- package/dist/src/commands/wechat/types.js +1 -0
- package/dist/src/commands/wechat/utils.d.ts +14 -0
- package/dist/src/commands/wechat/utils.js +1 -0
- package/dist/src/commands/wechat.d.ts +6 -127
- package/dist/src/commands/wechat.js +1 -7
- package/dist/src/devtools/wechat-channel-action-smoke.d.ts +5 -0
- package/dist/src/devtools/wechat-channel-action-smoke.js +8 -8
- package/dist/src/index.js +2 -2
- package/dist/src/manager/runtime.d.ts +3 -1
- package/dist/src/manager/runtime.js +5 -5
- package/package.json +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import L from"node:http";import{randomBytes as O,randomUUID as g}from"node:crypto";import k from"node:fs";import T from"node:os";import y from"node:path";import{AVAILABLE_BUILTIN_AGENT_TYPES as U,extractPayloadText as K,formatExternalConversationText as H,formatExternalMessageLine as j,formatExternalAttachmentReference as G,isAgentHiddenPayload as z,isToolPayload as J}from"@shennian/wire";import{ManagerRegistry as Y}from"./registry.js";import{readMessages as V}from"../session/store.js";import{ChannelRuntime as Q}from"../channels/runtime.js";import{splitExternalReplyText as X}from"../channels/reply-split.js";import{resolveShennianPath as Z}from"../config/index.js";import{buildExternalChannelInstructions as ee}from"../agents/external-channel-instructions.js";import{readDirectWeChatLatestOnce as te,sendDirectWeChatMessageOnce as ne}from"../commands/wechat.js";const
|
|
2
|
-
`).trim()}function ie(r){try{const e=JSON.parse(r),a=e.type==="tool_result"||e.result?"tool_result":"tool_call",n=e.name||"tool",t=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",i=t.length>220?`${t.slice(0,220)}...`:t;return i?`[${a}] ${n}: ${i}`:`[${a}] ${n}`}catch{return"[tool]"}}function D(r){if(!r||typeof r!="object")return;const e=r,a=String(e.kind||""),n=String(e.name||""),t=String(e.mimeType||""),i=String(e.dataBase64||""),o=String(e.localPath||""),s=String(e.url||""),l=Number(e.size||0);if(i)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(a!=="image"&&a!=="video"&&a!=="file")&&!(!n||!t||!Number.isFinite(l)||l<0)&&!(!o&&!s))return{kind:a,name:n,mimeType:t,size:l,...o?{localPath:o}:{},...s?{url:s}:{}}}function W(r){const e=r.binding&&typeof r.binding=="object"&&!Array.isArray(r.binding)?r.binding:{},a=String(e.sessionId||r.managerSessionId||"").trim(),n=String(e.channelId||"").trim(),t=String(e.conversationId||"").trim(),i=String(e.conversationName||r.conversation||"").trim(),o=String(e.workDir||r.workDir||"").trim();if(!a)throw new Error("WeChat tool sessionId is required");if(!n)throw new Error("WeChat tool channelId is required");if(!t)throw new Error("WeChat tool conversationId is required");if(!i)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:a,channelId:n,conversationId:t,conversationName:i,workDir:o}}function oe(r,e){const a=[...r].sort((c,d)=>c.ts-d.ts),n=[];let t=null,i=null,o=null,s="";const l=()=>{if(!t)return;const c=s.trim();c&&n.push({...t,id:`${t.id}-compact`,payload:c}),t=null,i=null,o=null,s=""};for(const c of a){if(z(c.payload)){l();continue}if(c.role==="user"){l(),n.push(c);continue}if(J(c.payload)){l(),n.push({...c,payload:ie(c.payload)});continue}const d=K(c.payload);if(!d.trim())continue;const h=re(c.id),p=se(c.id);t&&t.role===c.role&&i===h&&h&&p!==null&&o!==null&&p===o+1?(s+=d,t.ts=c.ts,o=p):(l(),t=c,i=h,o=p,s=d)}return l(),n.slice(-e).sort((c,d)=>d.ts-c.ts)}async function le(r){const e=[];let a=0;for await(const t of r){const i=Buffer.from(t);if(a+=i.byteLength,Number.isFinite(A)&&A>0&&a>A)throw new Error(`Manager IPC request body is too large. Max: ${A} bytes.`);e.push(i)}const n=Buffer.concat(e).toString("utf-8");return n?JSON.parse(n):{}}function w(r,e,a,n){r.client.sendRes({type:"res",id:e,ok:a,...a?{payload:n}:{error:String(n.error||"unknown error")}})}function P(r){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(r)}function v(){return ce()?y.join(T.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):Z("runtime","manager-ipc.json")}function ce(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class ve{opts;registry=new Y;channelRuntime=new Q((e,a)=>{this.handleExternalMessage(e,a)},e=>this.registry.createReplyTarget(e).replyTarget);server=null;ipcUrl=null;ipcToken=O(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;constructor(e){this.opts=e}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=L.createServer((a,n)=>{this.handleIpc(a,n)}),await new Promise((a,n)=>{this.server.once("error",n),this.server.listen(0,"127.0.0.1",()=>a())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=v();k.mkdirSync(y.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),k.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=v(),a=JSON.parse(k.readFileSync(e,"utf-8"));(a.url===this.ipcUrl||a.token===this.ipcToken)&&k.unlinkSync(e)}catch{}}getInjectedEnv(e,a,n,t){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:a??"",SHENNIAN_MANAGER_WORKDIR:S(n),SHENNIAN_MANAGER_MODEL:t,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:S(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagerWorkerDefaults(e,a,n){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,defaultWorkerAgentType:a??null,defaultWorkerModelId:n??null})}getManagerWorkerDefaults(e){const a=this.registry.getManager(e);return{agentType:a?.defaultWorkerAgentType??null,modelId:a?.defaultWorkerModelId??null}}noteManagerAgentSession(e,a,n,t){this.registry.upsertManager({sessionId:e,agentSessionId:a,workDir:S(n),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:t})}noteAgentEvent(e,a){if(!this.findWorker(e))return;const t={lastActivityAt:new Date().toISOString(),runId:a.runId};a.agentSessionId&&(t.agentSessionId=a.agentSessionId);const i=`${e}:${a.runId}`;if(a.state==="delta"&&a.text&&!a.thinking){const s=(this.workerTextAcc.get(i)??"")+a.text;this.workerTextAcc.set(i,s);const l=N(s);l&&(t.summary=l.length>160?`${l.slice(0,160)}...`:l)}if(a.state==="final"||a.state==="error"||a.state==="aborted"){t.status=a.state;const s=N(this.workerTextAcc.get(i)??"");s&&(t.summary=s.length>240?`${s.slice(0,240)}...`:s),this.workerTextAcc.delete(i)}else a.state==="start"&&(t.status="running");const o=this.registry.updateWorker(e,t);o&&(a.state==="final"||a.state==="error"||a.state==="aborted")&&this.wakeManagerForWorker(o.managedBy,o,a.state,a.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const a=this.opts.getRuntime(),n=e.params??{};try{const t=String(n.managerSessionId||n.sessionId||"");if(!t)throw new Error("sessionId is required");const i=this.registry.getManager(t),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",s=e.method==="session.wechat-rpa.channel.upsert"||e.method==="manager.wechat-rpa.channel.upsert",l=e.method==="session.wechat-rpa.channel.sync"||e.method==="manager.wechat-rpa.channel.sync",c=e.method==="session.wechat-rpa.outbound.cancel"||e.method==="manager.wechat-rpa.outbound.cancel";if(e.method==="manager.channel.get"){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const d=await this.channelRuntime.upsertManagerChannel({id:String(n.id||`websocket:${t}`),managerSessionId:t,sessionId:t,workDir:String(n.workDir||i?.workDir||""),type:"websocket",name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,wsUrl:typeof n.wsUrl=="string"?n.wsUrl:void 0,token:typeof n.token=="string"?n.token:void 0,canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:d});return}if(o){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"wechat-rpa",{includeSecret:!0})});return}if(s){const d=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(n.id||`wechat-rpa:${t}`),managerSessionId:t,sessionId:t,workDir:S(String(n.workDir||i?.workDir||"")),name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,groups:$(n.groups),canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0,source:_(n.source),pollIntervalMs:m(n.pollIntervalMs),recentLimit:m(n.recentLimit),idleSeconds:m(n.idleSeconds),forceForeground:n.forceForeground===void 0?void 0:!!n.forceForeground,noRestore:n.noRestore===void 0?void 0:!!n.noRestore,downloadAttachments:n.downloadAttachments===void 0?void 0:!!n.downloadAttachments,downloadAttachmentsDir:typeof n.downloadAttachmentsDir=="string"?n.downloadAttachmentsDir:void 0,selfNickname:typeof n.selfNickname=="string"?n.selfNickname:void 0,privacyConsentAccepted:n.privacyConsentAccepted===void 0?void 0:!!n.privacyConsentAccepted,flowScriptPath:typeof n.flowScriptPath=="string"?n.flowScriptPath:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:d});return}if(l){const d=await this.channelRuntime.syncManagerWeChatRpaChannel(t);this.broadcastManagerChannelStatus(t),w(a,e.id,!0,d);return}if(c){const d=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:t,idempotencyKey:typeof n.idempotencyKey=="string"?n.idempotencyKey:null,replyId:typeof n.replyId=="string"?n.replyId:null,reason:typeof n.reason=="string"?n.reason:"user_cancelled"});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,d);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(t){w(a,e.id,!1,{error:t instanceof Error?t.message:String(t)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const a=this.opts.getRuntime();if(!a.client?.sendEvent)return;const n=this.registry.getManager(e),t=this.channelRuntime.getManagerChannelStatus(e),i=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");a.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:n?"manager":i?.agentType,agentSessionId:n?.agentSessionId??i?.agentSessionId??null,modelId:n?.modelId??i?.modelId??null,workDir:n?.workDir??i?.workDir,externalChannel:t}}})}async handleIpc(e,a){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(a,401,{ok:!1,error:"Unauthorized"});return}try{const n=new URL(e.url??"/","http://127.0.0.1"),t=await le(e),i=String(t.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!i)throw new Error("managerSessionId is required");const o=this.registry.getManager(i);if(n.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const s=new Set(this.opts.getRuntime().sessions.keys());u(a,200,{ok:!0,sessions:this.registry.listWorkers(i,{runningSessionIds:s})});return}if(n.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const s=String(t.agentType||t.agent||o.defaultWorkerAgentType||"codex");if(!ae(s))throw new Error(`Unsupported manager worker agent: ${s}`);const l=S(String(t.workDir||o.workDir));if(l!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const c=String(t.message||"");if(!c)throw new Error("message is required");const d=this.registry.addWorker({managerSessionId:i,agentType:s,workDir:l,summary:c.slice(0,120)}),h=String(t.modelId||(s===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(d.sessionId,s,l,c,null,h),u(a,200,{ok:!0,session:d});return}if(n.pathname==="/sessions/send"){const s=String(t.sessionId||""),l=String(t.message||""),c=t.enqueue===void 0?!0:!!t.enqueue,d=this.registry.getWorkerForManager(i,s);if(!d)throw new Error("Worker not found in this manager scope");const h=String(t.modelId||(d.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));c?await this.dispatchChatEnqueue(d.sessionId,d.agentType,d.workDir,l,d.agentSessionId??null,h):await this.dispatchChatSend(d.sessionId,d.agentType,d.workDir,l,d.agentSessionId??null,h),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/queue"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");const c=this.opts.getRuntime().chatQueue?.getSnapshot(s);u(a,200,{ok:!0,queue:c});return}if(n.pathname==="/sessions/queue/edit"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${g()}`,method:"chat.queue.edit",params:{sessionId:s,queueMessageId:String(t.queueMessageId||t.messageId||""),text:String(t.message||t.text||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(s)});return}if(n.pathname==="/sessions/queue/delete"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${g()}`,method:"chat.queue.delete",params:{sessionId:s,queueMessageId:String(t.queueMessageId||t.messageId||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(s)});return}if(n.pathname==="/sessions/stop"||n.pathname==="/sessions/terminate"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${g()}`,method:"chat.abort",params:{sessionId:s}}),this.registry.updateWorker(s,{status:"aborted"}),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/read"){const s=String(t.sessionId||""),l=Number(t.limit||200);if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");const d=V(s,{limit:Math.max(l*20,l)});u(a,200,{ok:!0,messages:oe(d,l),rawMessageCount:d.length});return}if(n.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(a,200,{ok:!0,path:y.join(o.workDir,".shennian")});return}if(n.pathname==="/external/reply"){const s=typeof t.replyTarget=="string"?this.registry.getReplyTarget(t.replyTarget):this.registry.getLatestReplyTargetForManager(i),l=String(t.text||""),c=D(t.attachment),d=String(t.idempotencyKey||g()),h=String(t.channelId||""),p=String(t.conversationId||""),R=!s&&(!h||!p)?await this.channelRuntime.getDefaultReplyTarget(i).catch(()=>null):null,M=s?.channelId||h||R?.channelId||"",C=s?.conversationId||p||R?.conversationId||"";if(M&&this.channelRuntime.getChannelById(M)){if(!C)throw new Error("No external channel target is available for this Manager");const f=await this.channelRuntime.reply({managerSessionId:i,channelId:M,conversationId:C,messageId:s?.messageId??void 0,text:l,attachment:c,idempotencyKey:d});u(a,f.ok?200:400,f);return}let I;try{I=await this.sendManagedWeComReply({managerSessionId:i,text:l,attachment:c,idempotencyKey:d})}catch(f){const E=f instanceof Error?f.message:String(f);if(!P(E))throw f;I={ok:!1,error:E}}if(I.ok){u(a,200,{ok:!0,payload:I.payload});return}if(!P(I.error||"")||!M||!C){u(a,400,{ok:!1,error:I.error||"External send failed"});return}u(a,400,{ok:!1,error:`No local external channel is configured for ${M}`});return}if(n.pathname==="/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(i,"websocket")});return}if(n.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const s=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${i}`),managerSessionId:i,workDir:o.workDir,type:"websocket",name:typeof t.name=="string"?t.name:void 0,enabled:!!t.enabled,wsUrl:typeof t.wsUrl=="string"?t.wsUrl:void 0,token:typeof t.token=="string"?t.token:void 0,canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:s});return}if(n.pathname==="/wechat-rpa/tool/read"){const s=W(t),l=m(t.limit)??10,c=await te(s,{conversation:s.conversationName,workDir:s.workDir,sessionId:s.sessionId,limit:l,recentLimit:l,download:t.download==="never"?"never":"auto",traceId:typeof t.traceId=="string"?t.traceId:void 0,timeoutMs:m(t.timeoutMs)});u(a,200,{ok:!0,messages:c.messages,outDir:c.outDir,helperTracePath:c.helperTracePath});return}if(n.pathname==="/wechat-rpa/tool/send"){const s=W(t),l=String(t.text||""),c=D(t.attachment),d=await ne(s,{conversation:s.conversationName,workDir:s.workDir,sessionId:s.sessionId,text:l,attachment:c,traceId:typeof t.traceId=="string"?t.traceId:void 0,timeoutMs:m(t.timeoutMs)});u(a,200,{ok:!0,...d});return}if(n.pathname==="/wechat-rpa/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(i,"wechat-rpa",{includeSecret:!0})});return}if(n.pathname==="/wechat-rpa/channel/upsert"){const s=S(String(t.workDir||o?.workDir||""));if(!s)throw new Error("workDir is required");const l=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${i}`),managerSessionId:i,sessionId:i,workDir:s,name:typeof t.name=="string"?t.name:void 0,agentType:typeof t.agentType=="string"?t.agentType:void 0,agentSessionId:typeof t.agentSessionId=="string"?t.agentSessionId:null,modelId:typeof t.modelId=="string"?t.modelId:null,enabled:!!t.enabled,groups:$(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:_(t.source),pollIntervalMs:m(t.pollIntervalMs),recentLimit:m(t.recentLimit),idleSeconds:m(t.idleSeconds),forceForeground:t.forceForeground===void 0?void 0:!!t.forceForeground,noRestore:t.noRestore===void 0?void 0:!!t.noRestore,downloadAttachments:t.downloadAttachments===void 0?void 0:!!t.downloadAttachments,downloadAttachmentsDir:typeof t.downloadAttachmentsDir=="string"?t.downloadAttachmentsDir:void 0,selfNickname:typeof t.selfNickname=="string"?t.selfNickname:void 0,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:l});return}if(n.pathname==="/wechat-rpa/channel/sync"){const{channel:s,messages:l}=await this.channelRuntime.syncManagerWeChatRpaChannel(i);this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:s,messages:l});return}if(n.pathname==="/wechat-rpa/outbound/cancel"){const s=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:i,idempotencyKey:typeof t.idempotencyKey=="string"?t.idempotencyKey:null,replyId:typeof t.replyId=="string"?t.replyId:null,reason:typeof t.reason=="string"?t.reason:"user_cancelled"});this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,...s});return}u(a,404,{ok:!1,error:`Unknown manager IPC path: ${n.pathname}`})}catch(n){u(a,400,{ok:!1,error:n instanceof Error?n.message:String(n)})}}async dispatchChatSend(e,a,n,t,i,o){await this.opts.dispatchReq({type:"req",id:`manager-send-${g()}`,method:"chat.send",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:i,modelId:o}})}async dispatchChatEnqueue(e,a,n,t,i,o){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${g()}`,method:"chat.enqueue",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:i,modelId:o}})}async sendManagedWeComReply(e){const a=X(e.text);if(!a.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const n=this.opts.getRuntime().client;if(!n||typeof n.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const t=[];for(const[i,o]of a.entries()){const s=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:a.length>1?`${e.idempotencyKey}:${i+1}`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};t.push(s.payload)}if(e.attachment){const i=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:a.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};t.push(i.payload)}return{ok:!0,payload:t.length===1?t[0]:t}}wakeManagerForWorker(e,a,n,t){const i=this.registry.getManager(e);if(!i)return;const o=`\u4F60\u7BA1\u7406\u7684 worker \u5DF2\u7ED3\u675F\u3002
|
|
1
|
+
import L from"node:http";import{randomBytes as O,randomUUID as g}from"node:crypto";import k from"node:fs";import T from"node:os";import y from"node:path";import{AVAILABLE_BUILTIN_AGENT_TYPES as U,extractPayloadText as K,formatExternalConversationText as H,formatExternalMessageLine as j,formatExternalAttachmentReference as G,isAgentHiddenPayload as z,isToolPayload as J}from"@shennian/wire";import{ManagerRegistry as Y}from"./registry.js";import{readMessages as V}from"../session/store.js";import{ChannelRuntime as Q}from"../channels/runtime.js";import{splitExternalReplyText as X}from"../channels/reply-split.js";import{resolveShennianPath as Z}from"../config/index.js";import{buildExternalChannelInstructions as ee}from"../agents/external-channel-instructions.js";import{readDirectWeChatLatestOnce as te,sendDirectWeChatMessageOnce as ne}from"../commands/wechat.js";const C=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024);let x=null;function We(r){x=r}function Pe(){return x}function S(r){return y.resolve(r||T.homedir())}function u(r,e,a){r.writeHead(e,{"content-type":"application/json; charset=utf-8"}),r.end(JSON.stringify(a))}function re(r){return/^agent-(.+)-\d+$/.exec(r)?.[1]??null}function ae(r){return r==="manager"?!1:r.startsWith("custom:")?!0:U.includes(r)}function se(r){const e=/^agent-.+-(\d+)$/.exec(r);if(!e)return null;const a=Number(e[1]);return Number.isInteger(a)&&a>=0?a:null}function N(r){return r.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function ie(r){try{const e=JSON.parse(r),a=e.type==="tool_result"||e.result?"tool_result":"tool_call",n=e.name||"tool",t=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",i=t.length>220?`${t.slice(0,220)}...`:t;return i?`[${a}] ${n}: ${i}`:`[${a}] ${n}`}catch{return"[tool]"}}function D(r){if(!r||typeof r!="object")return;const e=r,a=String(e.kind||""),n=String(e.name||""),t=String(e.mimeType||""),i=String(e.dataBase64||""),o=String(e.localPath||""),s=String(e.url||""),c=Number(e.size||0);if(i)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(a!=="image"&&a!=="video"&&a!=="file")&&!(!n||!t||!Number.isFinite(c)||c<0)&&!(!o&&!s))return{kind:a,name:n,mimeType:t,size:c,...o?{localPath:o}:{},...s?{url:s}:{}}}function W(r){const e=r.binding&&typeof r.binding=="object"&&!Array.isArray(r.binding)?r.binding:{},a=String(e.sessionId||r.managerSessionId||"").trim(),n=String(e.channelId||"").trim(),t=String(e.conversationId||"").trim(),i=String(e.conversationName||r.conversation||"").trim(),o=String(e.workDir||r.workDir||"").trim();if(!a)throw new Error("WeChat tool sessionId is required");if(!n)throw new Error("WeChat tool channelId is required");if(!t)throw new Error("WeChat tool conversationId is required");if(!i)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:a,channelId:n,conversationId:t,conversationName:i,workDir:o}}function oe(r,e){const a=[...r].sort((l,d)=>l.ts-d.ts),n=[];let t=null,i=null,o=null,s="";const c=()=>{if(!t)return;const l=s.trim();l&&n.push({...t,id:`${t.id}-compact`,payload:l}),t=null,i=null,o=null,s=""};for(const l of a){if(z(l.payload)){c();continue}if(l.role==="user"){c(),n.push(l);continue}if(J(l.payload)){c(),n.push({...l,payload:ie(l.payload)});continue}const d=K(l.payload);if(!d.trim())continue;const h=re(l.id),p=se(l.id);t&&t.role===l.role&&i===h&&h&&p!==null&&o!==null&&p===o+1?(s+=d,t.ts=l.ts,o=p):(c(),t=l,i=h,o=p,s=d)}return c(),n.slice(-e).sort((l,d)=>d.ts-l.ts)}async function ce(r){const e=[];let a=0;for await(const t of r){const i=Buffer.from(t);if(a+=i.byteLength,Number.isFinite(C)&&C>0&&a>C)throw new Error(`Manager IPC request body is too large. Max: ${C} bytes.`);e.push(i)}const n=Buffer.concat(e).toString("utf-8");return n?JSON.parse(n):{}}function w(r,e,a,n){r.client.sendRes({type:"res",id:e,ok:a,...a?{payload:n}:{error:String(n.error||"unknown error")}})}function P(r){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(r)}function v(){return le()?y.join(T.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):Z("runtime","manager-ipc.json")}function le(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class ve{opts;registry=new Y;channelRuntime;server=null;ipcUrl=null;ipcToken=O(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;constructor(e){this.opts=e,this.channelRuntime=e.channelRuntime??new Q((a,n)=>{this.handleExternalMessage(a,n)},a=>this.registry.createReplyTarget(a).replyTarget,{createWeChatRpaProductRunner:e.createWeChatRpaProductRunner})}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=L.createServer((a,n)=>{this.handleIpc(a,n)}),await new Promise((a,n)=>{this.server.once("error",n),this.server.listen(0,"127.0.0.1",()=>a())});const e=this.server.address();typeof e=="object"&&e&&(this.ipcUrl=`http://127.0.0.1:${e.port}`,this.writeIpcRuntimeFile()),this.server.unref(),await this.channelRuntime.start(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref()}async ready(){await this.start()}async stop(){this.healthTimer&&clearInterval(this.healthTimer),this.healthTimer=null,await this.channelRuntime.stop(),await new Promise(e=>{if(!this.server)return e();this.server.close(()=>e())}),this.server=null,this.ipcUrl=null,this.removeIpcRuntimeFile()}writeIpcRuntimeFile(){if(this.ipcUrl)try{const e=v();k.mkdirSync(y.dirname(e),{recursive:!0}),k.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),k.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=v(),a=JSON.parse(k.readFileSync(e,"utf-8"));(a.url===this.ipcUrl||a.token===this.ipcToken)&&k.unlinkSync(e)}catch{}}getInjectedEnv(e,a,n,t){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:a??"",SHENNIAN_MANAGER_WORKDIR:S(n),SHENNIAN_MANAGER_MODEL:t,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:S(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagerWorkerDefaults(e,a,n){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,defaultWorkerAgentType:a??null,defaultWorkerModelId:n??null})}getManagerWorkerDefaults(e){const a=this.registry.getManager(e);return{agentType:a?.defaultWorkerAgentType??null,modelId:a?.defaultWorkerModelId??null}}noteManagerAgentSession(e,a,n,t){this.registry.upsertManager({sessionId:e,agentSessionId:a,workDir:S(n),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:t})}noteAgentEvent(e,a){if(!this.findWorker(e))return;const t={lastActivityAt:new Date().toISOString(),runId:a.runId};a.agentSessionId&&(t.agentSessionId=a.agentSessionId);const i=`${e}:${a.runId}`;if(a.state==="delta"&&a.text&&!a.thinking){const s=(this.workerTextAcc.get(i)??"")+a.text;this.workerTextAcc.set(i,s);const c=N(s);c&&(t.summary=c.length>160?`${c.slice(0,160)}...`:c)}if(a.state==="final"||a.state==="error"||a.state==="aborted"){t.status=a.state;const s=N(this.workerTextAcc.get(i)??"");s&&(t.summary=s.length>240?`${s.slice(0,240)}...`:s),this.workerTextAcc.delete(i)}else a.state==="start"&&(t.status="running");const o=this.registry.updateWorker(e,t);o&&(a.state==="final"||a.state==="error"||a.state==="aborted")&&this.wakeManagerForWorker(o.managedBy,o,a.state,a.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const a=this.opts.getRuntime(),n=e.params??{};try{const t=String(n.managerSessionId||n.sessionId||"");if(!t)throw new Error("sessionId is required");const i=this.registry.getManager(t),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",s=e.method==="session.wechat-rpa.channel.upsert"||e.method==="manager.wechat-rpa.channel.upsert",c=e.method==="session.wechat-rpa.channel.sync"||e.method==="manager.wechat-rpa.channel.sync",l=e.method==="session.wechat-rpa.outbound.cancel"||e.method==="manager.wechat-rpa.outbound.cancel";if(e.method==="manager.channel.get"){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const d=await this.channelRuntime.upsertManagerChannel({id:String(n.id||`websocket:${t}`),managerSessionId:t,sessionId:t,workDir:String(n.workDir||i?.workDir||""),type:"websocket",name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,wsUrl:typeof n.wsUrl=="string"?n.wsUrl:void 0,token:typeof n.token=="string"?n.token:void 0,canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:d});return}if(o){w(a,e.id,!0,{channel:this.channelRuntime.getManagerChannel(t,"wechat-rpa",{includeSecret:!0})});return}if(s){const d=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(n.id||`wechat-rpa:${t}`),managerSessionId:t,sessionId:t,workDir:S(String(n.workDir||i?.workDir||"")),name:typeof n.name=="string"?n.name:void 0,agentType:typeof n.agentType=="string"?n.agentType:void 0,agentSessionId:typeof n.agentSessionId=="string"?n.agentSessionId:null,modelId:typeof n.modelId=="string"?n.modelId:null,enabled:!!n.enabled,groups:$(n.groups),canReply:n.canReply===void 0?void 0:!!n.canReply,systemPrompt:typeof n.systemPrompt=="string"?n.systemPrompt:void 0,source:_(n.source),pollIntervalMs:m(n.pollIntervalMs),recentLimit:m(n.recentLimit),idleSeconds:m(n.idleSeconds),forceForeground:n.forceForeground===void 0?void 0:!!n.forceForeground,noRestore:n.noRestore===void 0?void 0:!!n.noRestore,downloadAttachments:n.downloadAttachments===void 0?void 0:!!n.downloadAttachments,downloadAttachmentsDir:typeof n.downloadAttachmentsDir=="string"?n.downloadAttachmentsDir:void 0,selfNickname:typeof n.selfNickname=="string"?n.selfNickname:void 0,privacyConsentAccepted:n.privacyConsentAccepted===void 0?void 0:!!n.privacyConsentAccepted,flowScriptPath:typeof n.flowScriptPath=="string"?n.flowScriptPath:void 0});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,{channel:d});return}if(c){const d=await this.channelRuntime.syncManagerWeChatRpaChannel(t);this.broadcastManagerChannelStatus(t),w(a,e.id,!0,d);return}if(l){const d=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:t,idempotencyKey:typeof n.idempotencyKey=="string"?n.idempotencyKey:null,replyId:typeof n.replyId=="string"?n.replyId:null,reason:typeof n.reason=="string"?n.reason:"user_cancelled"});this.broadcastManagerChannelStatus(t),w(a,e.id,!0,d);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(t){w(a,e.id,!1,{error:t instanceof Error?t.message:String(t)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const a=this.opts.getRuntime();if(!a.client?.sendEvent)return;const n=this.registry.getManager(e),t=this.channelRuntime.getManagerChannelStatus(e),i=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");a.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:n?"manager":i?.agentType,agentSessionId:n?.agentSessionId??i?.agentSessionId??null,modelId:n?.modelId??i?.modelId??null,workDir:n?.workDir??i?.workDir,externalChannel:t}}})}async handleIpc(e,a){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(a,401,{ok:!1,error:"Unauthorized"});return}try{const n=new URL(e.url??"/","http://127.0.0.1"),t=await ce(e),i=String(t.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!i)throw new Error("managerSessionId is required");const o=this.registry.getManager(i);if(n.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const s=new Set(this.opts.getRuntime().sessions.keys());u(a,200,{ok:!0,sessions:this.registry.listWorkers(i,{runningSessionIds:s})});return}if(n.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const s=String(t.agentType||t.agent||o.defaultWorkerAgentType||"codex");if(!ae(s))throw new Error(`Unsupported manager worker agent: ${s}`);const c=S(String(t.workDir||o.workDir));if(c!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const l=String(t.message||"");if(!l)throw new Error("message is required");const d=this.registry.addWorker({managerSessionId:i,agentType:s,workDir:c,summary:l.slice(0,120)}),h=String(t.modelId||(s===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(d.sessionId,s,c,l,null,h),u(a,200,{ok:!0,session:d});return}if(n.pathname==="/sessions/send"){const s=String(t.sessionId||""),c=String(t.message||""),l=t.enqueue===void 0?!0:!!t.enqueue,d=this.registry.getWorkerForManager(i,s);if(!d)throw new Error("Worker not found in this manager scope");const h=String(t.modelId||(d.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));l?await this.dispatchChatEnqueue(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h):await this.dispatchChatSend(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/queue"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");const l=this.opts.getRuntime().chatQueue?.getSnapshot(s);u(a,200,{ok:!0,queue:l});return}if(n.pathname==="/sessions/queue/edit"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${g()}`,method:"chat.queue.edit",params:{sessionId:s,queueMessageId:String(t.queueMessageId||t.messageId||""),text:String(t.message||t.text||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(s)});return}if(n.pathname==="/sessions/queue/delete"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${g()}`,method:"chat.queue.delete",params:{sessionId:s,queueMessageId:String(t.queueMessageId||t.messageId||"")}}),u(a,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(s)});return}if(n.pathname==="/sessions/stop"||n.pathname==="/sessions/terminate"){const s=String(t.sessionId||"");if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${g()}`,method:"chat.abort",params:{sessionId:s}}),this.registry.updateWorker(s,{status:"aborted"}),u(a,200,{ok:!0});return}if(n.pathname==="/sessions/read"){const s=String(t.sessionId||""),c=Number(t.limit||200);if(!this.registry.getWorkerForManager(i,s))throw new Error("Worker not found in this manager scope");const d=V(s,{limit:Math.max(c*20,c)});u(a,200,{ok:!0,messages:oe(d,c),rawMessageCount:d.length});return}if(n.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(a,200,{ok:!0,path:y.join(o.workDir,".shennian")});return}if(n.pathname==="/external/reply"){const s=typeof t.replyTarget=="string"?this.registry.getReplyTarget(t.replyTarget):this.registry.getLatestReplyTargetForManager(i),c=String(t.text||""),l=D(t.attachment),d=String(t.idempotencyKey||g()),h=String(t.channelId||""),p=String(t.conversationId||""),R=!s&&(!h||!p)?await this.channelRuntime.getDefaultReplyTarget(i).catch(()=>null):null,M=s?.channelId||h||R?.channelId||"",A=s?.conversationId||p||R?.conversationId||"";if(M&&this.channelRuntime.getChannelById(M)){if(!A)throw new Error("No external channel target is available for this Manager");const f=await this.channelRuntime.reply({managerSessionId:i,channelId:M,conversationId:A,messageId:s?.messageId??void 0,text:c,attachment:l,idempotencyKey:d});u(a,f.ok?200:400,f);return}let I;try{I=await this.sendManagedWeComReply({managerSessionId:i,text:c,attachment:l,idempotencyKey:d})}catch(f){const E=f instanceof Error?f.message:String(f);if(!P(E))throw f;I={ok:!1,error:E}}if(I.ok){u(a,200,{ok:!0,payload:I.payload});return}if(!P(I.error||"")||!M||!A){u(a,400,{ok:!1,error:I.error||"External send failed"});return}u(a,400,{ok:!1,error:`No local external channel is configured for ${M}`});return}if(n.pathname==="/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(i,"websocket")});return}if(n.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const s=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${i}`),managerSessionId:i,workDir:o.workDir,type:"websocket",name:typeof t.name=="string"?t.name:void 0,enabled:!!t.enabled,wsUrl:typeof t.wsUrl=="string"?t.wsUrl:void 0,token:typeof t.token=="string"?t.token:void 0,canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:s});return}if(n.pathname==="/wechat-rpa/tool/read"){const s=W(t),c=m(t.limit)??10,l=await te(s,{conversation:s.conversationName,workDir:s.workDir,sessionId:s.sessionId,limit:c,recentLimit:c,download:t.download==="never"?"never":"auto",traceId:typeof t.traceId=="string"?t.traceId:void 0,timeoutMs:m(t.timeoutMs)});u(a,200,{ok:!0,messages:l.messages,outDir:l.outDir,helperTracePath:l.helperTracePath});return}if(n.pathname==="/wechat-rpa/tool/send"){const s=W(t),c=String(t.text||""),l=D(t.attachment),d=await ne(s,{conversation:s.conversationName,workDir:s.workDir,sessionId:s.sessionId,text:c,attachment:l,traceId:typeof t.traceId=="string"?t.traceId:void 0,timeoutMs:m(t.timeoutMs)});u(a,200,{ok:!0,...d});return}if(n.pathname==="/wechat-rpa/channel/get"){u(a,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(i,"wechat-rpa",{includeSecret:!0})});return}if(n.pathname==="/wechat-rpa/channel/upsert"){const s=S(String(t.workDir||o?.workDir||""));if(!s)throw new Error("workDir is required");const c=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${i}`),managerSessionId:i,sessionId:i,workDir:s,name:typeof t.name=="string"?t.name:void 0,agentType:typeof t.agentType=="string"?t.agentType:void 0,agentSessionId:typeof t.agentSessionId=="string"?t.agentSessionId:null,modelId:typeof t.modelId=="string"?t.modelId:null,enabled:!!t.enabled,groups:$(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:_(t.source),pollIntervalMs:m(t.pollIntervalMs),recentLimit:m(t.recentLimit),idleSeconds:m(t.idleSeconds),forceForeground:t.forceForeground===void 0?void 0:!!t.forceForeground,noRestore:t.noRestore===void 0?void 0:!!t.noRestore,downloadAttachments:t.downloadAttachments===void 0?void 0:!!t.downloadAttachments,downloadAttachmentsDir:typeof t.downloadAttachmentsDir=="string"?t.downloadAttachmentsDir:void 0,selfNickname:typeof t.selfNickname=="string"?t.selfNickname:void 0,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:c});return}if(n.pathname==="/wechat-rpa/channel/sync"){const{channel:s,messages:c}=await this.channelRuntime.syncManagerWeChatRpaChannel(i);this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,channel:s,messages:c});return}if(n.pathname==="/wechat-rpa/outbound/cancel"){const s=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:i,idempotencyKey:typeof t.idempotencyKey=="string"?t.idempotencyKey:null,replyId:typeof t.replyId=="string"?t.replyId:null,reason:typeof t.reason=="string"?t.reason:"user_cancelled"});this.broadcastManagerChannelStatus(i),u(a,200,{ok:!0,...s});return}u(a,404,{ok:!1,error:`Unknown manager IPC path: ${n.pathname}`})}catch(n){u(a,400,{ok:!1,error:n instanceof Error?n.message:String(n)})}}async dispatchChatSend(e,a,n,t,i,o){await this.opts.dispatchReq({type:"req",id:`manager-send-${g()}`,method:"chat.send",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:i,modelId:o}})}async dispatchChatEnqueue(e,a,n,t,i,o){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${g()}`,method:"chat.enqueue",params:{sessionId:e,text:t,agentType:a,workDir:n,agentSessionId:i,modelId:o}})}async sendManagedWeComReply(e){const a=X(e.text);if(!a.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const n=this.opts.getRuntime().client;if(!n||typeof n.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const t=[];for(const[i,o]of a.entries()){const s=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:a.length>1?`${e.idempotencyKey}:${i+1}`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};t.push(s.payload)}if(e.attachment){const i=await n.sendReq({type:"req",id:`external-send-${g()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:a.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};t.push(i.payload)}return{ok:!0,payload:t.length===1?t[0]:t}}wakeManagerForWorker(e,a,n,t){const i=this.registry.getManager(e);if(!i)return;const o=`\u4F60\u7BA1\u7406\u7684 worker \u5DF2\u7ED3\u675F\u3002
|
|
3
3
|
|
|
4
4
|
Worker: ${a.sessionId}
|
|
5
5
|
\u72B6\u6001\uFF1A${n}
|
|
6
6
|
\u6700\u7EC8\u7ED3\u679C\uFF1A
|
|
7
7
|
${t||a.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
|
|
8
8
|
|
|
9
|
-
\u8BF7\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA/\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`;this.interruptAndResumeManager(i,o,n==="final"?"worker.final":`worker.${n}`)}handleExternalMessage(e,a){const n=this.channelRuntime.getChannelById(a.channelId)??this.channelRuntime.getManagerChannel(e,a.channelType),t=this.channelRuntime.getChannelStatusById(a.channelId)??this.channelRuntime.getManagerChannelStatus(e),i=this.registry.getManager(e),o=n?.agentType||(i?"manager":"codex"),s=n?.workDir||i?.workDir||process.cwd(),
|
|
9
|
+
\u8BF7\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA/\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`;this.interruptAndResumeManager(i,o,n==="final"?"worker.final":`worker.${n}`)}handleExternalMessage(e,a){const n=this.channelRuntime.getChannelById(a.channelId)??this.channelRuntime.getManagerChannel(e,a.channelType),t=this.channelRuntime.getChannelStatusById(a.channelId)??this.channelRuntime.getManagerChannelStatus(e),i=this.registry.getManager(e),o=n?.agentType||(i?"manager":"codex"),s=n?.workDir||i?.workDir||process.cwd(),c=n?.agentSessionId??i?.agentSessionId??null,l=n?.modelId||i?.modelId||"",d=de(a.attachments,a.channelType),h=he(a);this.dispatchExternalMessage({sessionId:e,agentType:o,workDir:s,agentSessionId:c,modelId:l,text:h,attachments:d,externalChannel:me(t),replyTarget:a.replyTarget})}async dispatchExternalMessage(e){await this.opts.dispatchReq({type:"req",id:`external-enqueue-${g()}`,method:"chat.enqueue",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,origin:"external",attachments:e.attachments,externalChannel:e.externalChannel??null,replyTarget:e.replyTarget}})}scanWorkerHealth(){const e=Date.now(),a=this.registry.load();for(const n of Object.values(a.workers)){if(n.status!=="running")continue;const t=e-Date.parse(n.createdAt);if(t<10*6e4)continue;const i=n.healthNotifiedAt?Date.parse(n.healthNotifiedAt):0;if(i&&e-i<10*6e4)continue;const o=a.managers[n.managedBy];if(!o)continue;const s=Math.max(0,Math.floor((e-Date.parse(n.lastActivityAt))/6e4)),c=`Worker ${n.sessionId} \u5DF2\u8FD0\u884C ${Math.floor(t/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
10
10
|
|
|
11
11
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
12
12
|
- \u6700\u8FD1\u6587\u672C\u6458\u8981\uFF1A${n.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
|
|
13
13
|
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${n.lastActivityAt}
|
|
14
14
|
- \u6700\u8FD1 ${s} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002
|
|
15
15
|
|
|
16
|
-
\u8BF7\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u7B49\u5F85\u3001\u8BE2\u95EE\u7528\u6237\u3001\u505C\u6B62 worker\u3001\u6216\u521B\u5EFA\u5176\u4ED6 worker \u534F\u52A9\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:
|
|
16
|
+
\u8BF7\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u7B49\u5F85\u3001\u8BE2\u95EE\u7528\u6237\u3001\u505C\u6B62 worker\u3001\u6216\u521B\u5EFA\u5176\u4ED6 worker \u534F\u52A9\u3002`;this.registry.updateWorker(n.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:c}),o.status==="idle"&&this.dispatchChatSend(o.sessionId,"manager",o.workDir,c,o.agentSessionId,o.modelId)}}async interruptAndResumeManager(e,a,n){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&(this.registry.upsertManager({...e,status:"interrupting"}),await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${g()}`,method:"chat.abort",params:{sessionId:e.sessionId}}));const o=n?`\u4E8B\u4EF6\u7C7B\u578B\uFF1A${n}
|
|
17
17
|
|
|
18
18
|
${a}`:a;await this.dispatchChatSend(e.sessionId,"manager",e.workDir,o,e.agentSessionId,e.modelId)}bindManagerAdapterEvents(e,a){a.on("agentEvent",n=>{if(n.state==="start"&&n.agentSessionId){const t=this.registry.getManager(e);t&&this.noteManagerAgentSession(e,n.agentSessionId,t.workDir,t.modelId)}n.state==="start"&&this.updateManagerStatus(e,"running"),(n.state==="final"||n.state==="error"||n.state==="aborted")&&this.updateManagerStatus(e,"idle")})}updateManagerStatus(e,a){const n=this.registry.getManager(e);n&&this.registry.upsertManager({...n,status:a})}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(a=>ee(a,void 0,e,"manager")).join(`
|
|
19
19
|
|
|
20
|
-
`).trim()}}function $(r){return Array.isArray(r)?r.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function _(r){return r==="wechat-channel"||r==="macos-flow"||r==="macos-probe"||r==="windows-visual-flow"||r==="wechat-rpa-lab"||r==="fixture-jsonl"?r:void 0}function de(r,e){const a=r.map(n=>B(n,e)).filter(n=>n!=null);return a.length?a:void 0}function ue(r,e,a,n){const t=e||"",i=new Set;return r.map((s,
|
|
20
|
+
`).trim()}}function $(r){return Array.isArray(r)?r.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function _(r){return r==="wechat-channel"||r==="macos-flow"||r==="macos-probe"||r==="windows-visual-flow"||r==="wechat-rpa-lab"||r==="fixture-jsonl"?r:void 0}function de(r,e){const a=r.map(n=>B(n,e)).filter(n=>n!=null);return a.length?a:void 0}function ue(r,e,a,n){const t=e||"",i=new Set;return r.map((s,c)=>{const l=B(s,n),d=G({type:s.type,name:l?.name||s.name,path:l?.path,mimeType:l?.mimeType||s.mimeType,availability:s.availability,providerError:s.providerError||ge(s)},c),h=l?.path||"";if(h){const R=`${d}
|
|
21
21
|
${h}`;return i.has(R)||t.includes(h)?"":(i.add(R),`${a}: ${d}`)}const p=d;return i.has(p)?"":(i.add(p),`${a}: ${d}`)}).filter(s=>!t.includes(s)).join(`
|
|
22
22
|
`)}function he(r){const a=(r.channelType==="wechat-rpa"?pe(r.sender.name):r.sender.name||r.sender.id)||(r.channelType==="wechat-rpa"?"\u5BF9\u65B9":r.sender.id)||"\u5BF9\u65B9",n=ue(r.attachments,r.text,a,r.channelType),i=[r.text?j({senderName:a,senderId:r.sender.id,text:r.text}):n?"":`${a}:`,n].filter(Boolean).join(`
|
|
23
23
|
`);return[H({conversationName:r.conversationName||r.conversationId,messages:[]}),i].filter(Boolean).join(`
|