shennian 0.4.6 → 0.4.8
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 +8 -8
- package/dist/src/agents/codex.js +7 -7
- package/dist/src/manager/registry.d.ts +5 -0
- package/dist/src/manager/registry.js +1 -1
- package/dist/src/manager/runtime.d.ts +4 -0
- package/dist/src/manager/runtime.js +10 -10
- package/dist/src/session/manager.d.ts +3 -1
- package/dist/src/session/manager.js +1 -1
- package/package.json +1 -1
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"file": "src/agents/codex.js",
|
|
49
|
-
"beforeBytes":
|
|
50
|
-
"afterBytes":
|
|
49
|
+
"beforeBytes": 65656,
|
|
50
|
+
"afterBytes": 33627
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"file": "src/agents/command-spec.js",
|
|
@@ -366,8 +366,8 @@
|
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"file": "src/manager/registry.js",
|
|
369
|
-
"beforeBytes":
|
|
370
|
-
"afterBytes":
|
|
369
|
+
"beforeBytes": 13848,
|
|
370
|
+
"afterBytes": 6731
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"file": "src/manager/retired-external-channel-compat.js",
|
|
@@ -376,8 +376,8 @@
|
|
|
376
376
|
},
|
|
377
377
|
{
|
|
378
378
|
"file": "src/manager/runtime.js",
|
|
379
|
-
"beforeBytes":
|
|
380
|
-
"afterBytes":
|
|
379
|
+
"beforeBytes": 76547,
|
|
380
|
+
"afterBytes": 38248
|
|
381
381
|
},
|
|
382
382
|
{
|
|
383
383
|
"file": "src/native-fusion/codex-files.js",
|
|
@@ -706,8 +706,8 @@
|
|
|
706
706
|
},
|
|
707
707
|
{
|
|
708
708
|
"file": "src/session/manager.js",
|
|
709
|
-
"beforeBytes":
|
|
710
|
-
"afterBytes":
|
|
709
|
+
"beforeBytes": 31661,
|
|
710
|
+
"afterBytes": 16413
|
|
711
711
|
},
|
|
712
712
|
{
|
|
713
713
|
"file": "src/session/native-cleanup-outbox.js",
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{spawnSync as O}from"node:child_process";import{createInterface as S}from"node:readline";import{createHash as w,randomUUID as b}from"node:crypto";import
|
|
1
|
+
import{spawnSync as O}from"node:child_process";import{createInterface as S}from"node:readline";import{createHash as w,randomUUID as b}from"node:crypto";import g from"node:path";import{AgentAdapter as q,registerAgent as N}from"./adapter.js";import{resolveBuiltinCommand as y,spawnAgentCommand as x}from"./command-spec.js";import{buildAgentProcessEnv as D,buildManagedAgentProcessEnv as L}from"../agent-env.js";import{ensurePlatformInstructionsFile as F}from"./platform-instructions.js";import{CODEX_DESKTOP_SEND_UNAVAILABLE as C}from"@shennian/wire";import{isCodexThreadTailInterrupted as $}from"../native-fusion/codex-files.js";import{resolveManagedAgentAdapterEnforcement as U}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as W}from"../security/managed-agent-runtime.js";import{CODEX_MANAGED_ROOM_DYNAMIC_TOOLS as z,CodexManagedRoomToolBinding as B}from"./codex-managed-room-tools.js";import{assertCodexManagedPermissionProfile as R,CODEX_MANAGED_RUNTIME_SECURITY as G,compileCodexManagedPermissions as J,extractCodexMcpServerNames as H}from"./codex-managed-permissions.js";import{CODEX_APP_SERVER_CLIENT_INFO as V,extractAppServerErrorMessage as E,formatCodexErrorMessage as X,formatTransientCodexStatus as K,isCodexCollabAgentItem as Q,isCodexCollabAgentToolName as T,isCodexLegacyCollabAgentItem as Y,isCodexUnsupportedEffortError as j,isMissingCodexRolloutError as v,isTransientCodexErrorMessage as Z,looksLikeCodexInteractiveAuthPrompt as ee,looksLikeFatalCodexStderr as te,normalizeCodexModelId as I,normalizeCodexReasoningEffort as re,normalizeCodexStderr as _,normalizeTerminalText as se,safeStringify as k,stripGitDirectiveArtifacts as ne}from"./codex-utils.js";import{isCodexUnsupportedEffortError as be,isMissingCodexRolloutError as qe,normalizeCodexModelId as Ne,normalizeCodexReasoningEffort as De}from"./codex-utils.js";W("codex",G);function P(o){const e=o instanceof Error?o.message:String(o);return/already has an active writer|thread-store conflict/i.test(e)}function ie(o){const e=o instanceof Error?o.message:String(o);return/paginated(?:_threads|\s+(?:thread\s+)?history)?.*(?:not supported|unsupported|unavailable)/i.test(e)||/(?:not supported|unsupported|unavailable).*paginated(?:_threads|\s+(?:thread\s+)?history)?/i.test(e)}function oe(o){const e=o instanceof Error?o.message:String(o);return/unknown field [`'"]?excludeTurns/i.test(e)||/excludeTurns.*(?:not supported|unsupported|unavailable)/i.test(e)}function ae(o){const e=o instanceof Error?o.message:String(o);return/(?:method not found|unknown method).*(?:project\/list|project\/create)/i.test(e)||/(?:project\/list|project\/create).*(?:not supported|unsupported|unavailable)/i.test(e)}function de(o,e){const t=g.resolve(e);let r;for(const s of o)if(s.id)for(const i of s.roots??[]){if(!i.path)continue;const n=g.resolve(i.path),a=g.relative(n,t);!(a===""||!a.startsWith("..")&&!g.isAbsolute(a))||n.length<=(r?.rootLength??-1)||(r={id:s.id,rootLength:n.length})}return r?.id}function le(o,e){const t=e?.flatMap(s=>ce(s)).filter(s=>!/^https?:\/\//i.test(s.path)).filter(s=>s.path);return{type:"text",text:he(o,e),text_elements:[],...t?.length?{local_images:t}:{}}}function ce(o){if(o.kind==="image")return o.previewData?[{path:o.path,data:o.previewData,mimeType:o.mimeType}]:[{path:o.path}];if(o.mimeType.startsWith("video/")){const e=ue(o.path);return e?[{path:e}]:[]}return[]}function ue(o){if(!o||/^https?:\/\//i.test(o))return null;const e=g.extname(o),r=`${e?o.slice(0,-e.length):o}.preview.png`;return O("ffmpeg",["-y","-ss","0","-i",o,"-frames:v","1","-vf","scale=320:-1",r],{stdio:"ignore",timeout:15e3}).status===0?r:null}function he(o,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>o.includes(i.path)))return o;const s=t.map((i,n)=>{const a=i.name||`attachment-${n+1}`,d=i.mimeType?` (${i.mimeType})`:"";return`[${A(a)}${d}](<${i.path}>)`});return[o,"",...s].join(`
|
|
2
2
|
`)}function pe(o,e){const t=e?.filter(s=>s.path&&!/^https?:\/\//i.test(s.path)&&!o.includes(s.path));if(!t?.length)return o;const r=t.map((s,i)=>{const n=A(s.name||`attachment-${i+1}`);return s.kind==="image"?``:`[${n}](<${s.path}>)`});return[o,"",...r].join(`
|
|
3
|
-
`)}function me(o,e,t){return["queue","--thread",o,"--message",pe(e,t)]}function A(o){return o.replace(/[[\]\\]/g,"\\$&")}class ge extends q{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";managedAgentPolicy=null;managedPermissionConfig=null;configurationRestartRequired=!1;managedRoomTools=new B;configuredManagedRoomContextToken=null;configuredManagedRoomExecutor=null;managedRoomToolThreadId=null;preparedExternalOwnerThreadId=null;managedTurnReplay=null;managedAsyncRecovery=null;persistNativeSession=!0;constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){const t=this.configurationFingerprint();this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),("managedRoomContextToken"in e||"managedRoomExecutor"in e)&&(this.managedRoomTools.deactivateTurn(),this.configuredManagedRoomContextToken=e.managedRoomContextToken?.trim()||null,this.configuredManagedRoomExecutor=e.managedRoomExecutor??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir);const r=this.configurationFingerprint();t!==r&&this.process&&(this.configurationRestartRequired=!0)}configurationFingerprint(){const e=Object.keys(this.extraEnv).sort().map(t=>[t,this.extraEnv[t]??null]);return w("sha256").update(JSON.stringify({sessionId:this.shennianSessionId,externalChannel:this.externalChannel,env:e,managedAgentPolicy:this.managedAgentPolicy})).digest("hex")}buildProcessEnv(e={}){if(!this.managedAgentPolicy)return D({...e,...this.extraEnv});const t=Object.fromEntries(Object.entries(this.extraEnv).filter(([r])=>r!=="SHENNIAN_MANAGED_ROOM_CONTEXT"&&r!=="SHENNIAN_MANAGED_ROOM_IPC_ROOT"));return L({agentType:"codex",providerEnv:t,sessionEnv:t,runtimeEnv:e})}async start(e,t,r,s){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=s?.persistNativeSession!==!1,r&&(this.agentSessionId=r)}async prepareSend(e,t,r,s,i,n){const a=i?.trim()??"";if(a!==this.agentSystemPrompt&&(this.agentSystemPrompt=a,this.process&&await this.killProcess(!0)),!(!this.agentSessionId||this.activeTurnId)){this.preparedExternalOwnerThreadId=null;try{await this.ensureAppServer(I(t))}catch(d){if(!P(d))throw d;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:d});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:d});this.preparedExternalOwnerThreadId=this.agentSessionId}}}async send(e,t,r,s,i,n){if(n?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");if(n?.source==="room_activation"&&(!this.configuredManagedRoomContextToken||!this.configuredManagedRoomExecutor))throw new Error("codex_managed_room_tool_context_unavailable");this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess(!0)),this.configurationRestartRequired&&this.process&&await this.killProcess(!0),this.configurationRestartRequired=!1;const a=I(t),d=re(r),c=i?.trim()??"";if(c!==this.agentSystemPrompt&&(this.agentSystemPrompt=c,this.process&&await this.killProcess(!0)),n?.source==="room_activation"&&this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),this.runId=b(),this.seq=0,this.resetRunState(),this.agentSessionId&&this.preparedExternalOwnerThreadId===this.agentSessionId)return this.preparedExternalOwnerThreadId=null,{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)};try{await this.ensureAppServer(a)}catch(h){if(!this.agentSessionId||!P(h))throw h;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:h});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:h});return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)}}const l=this.agentSessionId;if(!l)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:l}),this.managedTurnReplay=this.managedPermissionConfig?{text:e,modelId:a,reasoningEffort:d,attachments:s,recoveryCount:0}:null;const p=await this.startTurnWithRecovery(l,e,a,d,s),
|
|
4
|
-
${l}`.trim();if(u!==0){g(new Error(f||`codex queue exited with code ${u??"unknown"}`));return}const M=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];g(void 0,M)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.resetTextState()}async setTitle(e,t,r){const s=e.trim(),i=t.replace(/\s+/g," ").trim();!s||!i||(this.agentSessionId=s,r?.trim()&&(this.workDir=r),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:s,name:i}))}async stop(){this.managedRoomTools.deactivateTurn(),await this.interruptActiveTurn().catch(()=>{}),await this.killProcess()}async getStatus(){const e=this.agentSessionId,t=this.activeTurnId;if(!e||!this.process)return{active:!1,runId:t,runPhase:null,canStop:!1};let r=null;try{r=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const s=r?.type==="active"||!!t;return{active:s,runId:t,runPhase:s?this.mapThreadStatusToRunPhase(r):null,canStop:!!(s&&t&&this.process)}}spawnCodex(e){const t=T("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const r=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()});this.process=r,S({input:r.stdout}).on("line",n=>{if(n.trim())try{const a=JSON.parse(n);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:n})}});let i="";r.stderr?.on("data",n=>{i+=n.toString()}),r.on("close",n=>{this.process===r&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),r.on("error",n=>{this.process===r&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=T("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],r=this.options.modelInstructionsFile??F(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);r&&t.push("-c",`model_instructions_file=${JSON.stringify(r)}`);const s=x(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:this.buildProcessEnv({NO_COLOR:"1"})});this.process=s,this.stderrBuf="",this.appServerStartupError=null,S({input:s.stdout}).on("line",n=>{if(!n.trim())return;let a;try{a=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(a)}),s.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),s.on("close",n=>{if(this.process!==s)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(new Error(_(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&a&&this.handleProcessClose(n,this.stderrBuf)}),s.on("error",n=>{this.process===s&&(this.process=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=I(e);await this.sendRpc("initialize",{clientInfo:V,capabilities:{experimentalApi:!0}}),this.managedPermissionConfig=this.managedAgentPolicy?await this.buildManagedPermissionConfig():null;const r=!!(this.configuredManagedRoomContextToken||this.configuredManagedRoomExecutor);if(this.managedPermissionConfig&&r&&!this.managedRoomTools.isConfigured()&&(this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),!this.managedRoomTools.isConfigured()))throw new Error("codex_managed_room_tool_context_unavailable");if(this.agentSessionId&&this.managedPermissionConfig&&r&&this.agentSessionId!==this.managedRoomToolThreadId){if(!this.managedRoomTools.isConfigured())throw new Error("codex_managed_room_tool_context_unavailable");this.agentSessionId=null}if(this.agentSessionId){const c={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},persistExtendedHistory:!0,...this.options.hidden||!this.persistNativeSession?{threadSource:"subagent"}:{},...t?{model:t}:{}};let l;try{l=await this.sendRpc("thread/resume",{...c,excludeTurns:!0})}catch(p){if(!oe(p))throw p;l=await this.sendRpc("thread/resume",c)}this.agentSessionId=l.thread?.id??this.agentSessionId,this.managedPermissionConfig&&(E(l,this.managedPermissionConfig.profileId),r&&(this.managedRoomTools.bindThread(this.agentSessionId),this.managedRoomToolThreadId=this.agentSessionId));return}const s=this.options.hidden||!this.persistNativeSession,i=s?void 0:await this.resolveCodexProjectId(),n={cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},serviceName:"Shennian",threadSource:s?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...this.managedPermissionConfig&&r?{dynamicTools:z}:{},...i?{projectId:i}:{},...t?{model:t}:{}};let a;if(s&&!this.managedPermissionConfig)a=await this.sendRpc("thread/start",n);else try{a=await this.sendRpc("thread/start",{...n,historyMode:"paginated"})}catch(c){throw ie(c)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:c}):c}const d=a.thread?.id;if(!d)throw new Error("codex app-server thread/start returned no thread id");this.managedPermissionConfig&&(E(a,this.managedPermissionConfig.profileId),r&&(this.managedRoomTools.bindThread(d),this.managedRoomToolThreadId=d)),this.agentSessionId=d}async buildManagedPermissionConfig(){const e=this.managedAgentPolicy;if(!e)throw new Error("managed_agent_policy_missing");const t=e.adapterPolicy.externalCapabilities.mcp?[]:H(await this.sendRpc("config/read",{cwd:e.adapterPolicy.workDir}));return J(U(e),e.adapterPolicy.workDir,{configuredMcpServerNames:t,shellSubprocessPolicy:e.environmentPolicy.shellSubprocess})}async resolveCodexProjectId(){const e=this.workDir?.trim();if(e)try{const t=[];let r;do{const c=await this.sendRpc("project/list",{limit:100,...r?{cursor:r}:{}});t.push(...c.data??[]),r=c.nextCursor}while(r);const s=de(t,e);if(s)return s;const i=m.resolve(e),n=`shennian-project-${w("sha256").update(i).digest("hex")}`,d=(await this.sendRpc("project/create",{idempotencyKey:n,name:m.basename(i)||i,roots:[{path:i}],metadata:{createdBy:"Shennian"}})).project?.id;if(!d)throw new Error("codex app-server project/create returned no project id");return d}catch(t){throw ae(t)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5F52\u5C5E\u5230 Codex Desktop \u9879\u76EE\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u76F4\u63A5\u4FEE\u6539 Codex Desktop \u7684\u79C1\u6709\u4FA7\u680F\u7D22\u5F15\u3002",{cause:t}):t}}async startTurnWithRecovery(e,t,r,s,i){try{return await this.startTurn(e,t,r,s,i)}catch(n){if(!y(n))throw n;if(this.managedPermissionConfig){this.logManagedThreadLifecycle("rollout.missing",{threadId:e,recovery:"recreate"}),await this.killProcess(!0),this.agentSessionId=null,this.managedRoomToolThreadId=null,await this.ensureAppServer(r);const a=this.agentSessionId;if(!a)throw new Error("codex app-server did not recreate the managed thread",{cause:n});return this.logManagedThreadLifecycle("thread.recreated",{previousThreadId:e,threadId:a}),this.emitEvent({state:"start",agentSessionId:a}),await this.startFreshManagedTurnWithReadinessRetry(a,t,r,s,i)}return await this.killProcess(),await this.ensureAppServer(r),await this.startTurn(e,t,r,s,i)}}async startFreshManagedTurnWithReadinessRetry(e,t,r,s,i){const n=[0,100,300,1e3];let a;for(let d=0;d<n.length;d+=1){const c=n[d];c>0&&await new Promise(l=>setTimeout(l,c));try{const l=await this.startTurn(e,t,r,s,i);return d>0&&this.logManagedThreadLifecycle("rollout.ready",{threadId:e,attempt:d+1}),l}catch(l){if(a=l,!y(l)||d===n.length-1)throw l;this.logManagedThreadLifecycle("rollout.retry",{threadId:e,attempt:d+2,delayMs:n[d+1]})}}throw a}logManagedThreadLifecycle(e,t){console.error(`[codex.managed] ${JSON.stringify({event:e,...t})}`)}async startTurn(e,t,r,s,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[le(t,i)],approvalPolicy:"never",...this.managedPermissionConfig?{}:{sandboxPolicy:{type:"dangerFullAccess"}},...r?{model:r}:{},...s?{effort:s}:{}})}catch(n){throw s&&j(n)?new Error(`Codex app-server does not accept reasoning effort "${s}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),!(!e||!t||!this.process)&&await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3)}sendRpc(e,t,r=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const s=this.process;if(!s?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((a,d)=>{const c=setTimeout(()=>{this.pendingRequests.delete(i),d(new Error(`codex app-server request timed out: ${e}`))},r);this.pendingRequests.set(i,{resolve:a,reject:d,timer:c}),s.stdin.write(`${n}
|
|
5
|
-
`,l=>{l&&(clearTimeout(c),this.pendingRequests.delete(i),d(l))})})}handleAppServerMessage(e){if(e.method==="item/tool/call"&&e.id!=null){this.handleManagedRoomToolCall(e.id,e.params??{});return}if(e.id!=null){const r=typeof e.id=="number"?this.pendingRequests.get(e.id):void 0;r&&(clearTimeout(r.timer),this.pendingRequests.delete(e.id),e.error?r.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):r.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const r=typeof t.turn=="object"&&t.turn!==null?t.turn:null,s=typeof r?.id=="string"?r.id:null,i=typeof t.threadId=="string"?t.threadId:"";s&&(this.activeTurnId=s,this.
|
|
3
|
+
`)}function me(o,e,t){return["queue","--thread",o,"--message",pe(e,t)]}function A(o){return o.replace(/[[\]\\]/g,"\\$&")}class ge extends q{options;type="codex";process=null;workDir=null;seq=0;runId="";pendingUsage;agentSessionId=null;hasEmittedText=!1;textTail="";terminalState="open";forceCloseTimer=null;stderrBuf="";rpcSeq=1;pendingRequests=new Map;appServerReady=null;appServerStartupError=null;activeTurnId=null;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";managedAgentPolicy=null;managedPermissionConfig=null;configurationRestartRequired=!1;managedRoomTools=new B;configuredManagedRoomContextToken=null;configuredManagedRoomExecutor=null;managedRoomToolThreadId=null;preparedExternalOwnerThreadId=null;managedTurnReplay=null;managedAsyncRecovery=null;persistNativeSession=!0;constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){const t=this.configurationFingerprint();this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),("managedRoomContextToken"in e||"managedRoomExecutor"in e)&&(this.managedRoomTools.deactivateTurn(),this.configuredManagedRoomContextToken=e.managedRoomContextToken?.trim()||null,this.configuredManagedRoomExecutor=e.managedRoomExecutor??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir);const r=this.configurationFingerprint();t!==r&&this.process&&(this.configurationRestartRequired=!0)}configurationFingerprint(){const e=Object.keys(this.extraEnv).sort().map(t=>[t,this.extraEnv[t]??null]);return w("sha256").update(JSON.stringify({sessionId:this.shennianSessionId,externalChannel:this.externalChannel,env:e,managedAgentPolicy:this.managedAgentPolicy})).digest("hex")}buildProcessEnv(e={}){if(!this.managedAgentPolicy)return D({...e,...this.extraEnv});const t=Object.fromEntries(Object.entries(this.extraEnv).filter(([r])=>r!=="SHENNIAN_MANAGED_ROOM_CONTEXT"&&r!=="SHENNIAN_MANAGED_ROOM_IPC_ROOT"));return L({agentType:"codex",providerEnv:t,sessionEnv:t,runtimeEnv:e})}async start(e,t,r,s){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=s?.persistNativeSession!==!1,r&&(this.agentSessionId=r)}async prepareSend(e,t,r,s,i,n){const a=i?.trim()??"";if(a!==this.agentSystemPrompt&&(this.agentSystemPrompt=a,this.process&&await this.killProcess(!0)),!(!this.agentSessionId||this.activeTurnId)){this.preparedExternalOwnerThreadId=null;try{await this.ensureAppServer(I(t))}catch(d){if(!P(d))throw d;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:d});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:d});this.preparedExternalOwnerThreadId=this.agentSessionId}}}async send(e,t,r,s,i,n){if(n?.source==="room_activation"&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");if(n?.source==="room_activation"&&(!this.configuredManagedRoomContextToken||!this.configuredManagedRoomExecutor))throw new Error("codex_managed_room_tool_context_unavailable");this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess(!0)),this.configurationRestartRequired&&this.process&&await this.killProcess(!0),this.configurationRestartRequired=!1;const a=I(t),d=re(r),c=i?.trim()??"";if(c!==this.agentSystemPrompt&&(this.agentSystemPrompt=c,this.process&&await this.killProcess(!0)),n?.source==="room_activation"&&this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),this.runId=b(),this.seq=0,this.resetRunState(),this.agentSessionId&&this.preparedExternalOwnerThreadId===this.agentSessionId)return this.preparedExternalOwnerThreadId=null,{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)};try{await this.ensureAppServer(a)}catch(h){if(!this.agentSessionId||!P(h))throw h;if(n?.source==="room_activation")throw new Error("codex_managed_thread_owned_externally",{cause:h});if(this.isThreadTailInterrupted(this.agentSessionId))throw new Error(C,{cause:h});return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,s)}}const l=this.agentSessionId;if(!l)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:l}),this.managedTurnReplay=this.managedPermissionConfig?{text:e,modelId:a,reasoningEffort:d,attachments:s,recoveryCount:0}:null;const p=await this.startTurnWithRecovery(l,e,a,d,s),m=this.agentSessionId??l;if(this.activeTurnId=p.turn?.id??null,this.managedRoomToolThreadId===m&&this.activeTurnId&&!this.managedRoomTools.bindTurn(m,this.activeTurnId))throw await this.killProcess(),new Error("codex_managed_room_turn_binding_failed");return{deliveryMode:"direct"}}isThreadTailInterrupted(e){return this.options.isThreadTailInterrupted?.(e)??$(e)}async enqueueForExternalOwner(e,t,r){const s=y("codex");if(!s)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=me(e,t,r),n=x(s,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv({NO_COLOR:"1"})});return await new Promise((a,d)=>{let c="",l="",p=!1;const m=(u,f)=>{p||(p=!0,clearTimeout(h),u?d(u):a(f))},h=setTimeout(()=>{n.kill("SIGTERM"),m(new Error("Codex Desktop message queue timed out"))},6e4);h.unref?.(),n.stdout?.on("data",u=>{c+=u.toString()}),n.stderr?.on("data",u=>{l+=u.toString()}),n.once("error",u=>m(u)),n.once("close",u=>{const f=`${c}
|
|
4
|
+
${l}`.trim();if(u!==0){m(new Error(f||`codex queue exited with code ${u??"unknown"}`));return}const M=f.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];m(void 0,M)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.resetTextState()}async setTitle(e,t,r){const s=e.trim(),i=t.replace(/\s+/g," ").trim();!s||!i||(this.agentSessionId=s,r?.trim()&&(this.workDir=r),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:s,name:i}))}async stop(){this.managedRoomTools.deactivateTurn(),await this.interruptActiveTurn().catch(()=>{}),await this.killProcess()}async getStatus(){const e=this.agentSessionId,t=this.activeTurnId;if(!e||!this.process)return{active:!1,runId:t,runPhase:null,canStop:!1};let r=null;try{r=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const s=r?.type==="active"||!!t;return{active:s,runId:t,runPhase:s?this.mapThreadStatusToRunPhase(r):null,canStop:!!(s&&t&&this.process)}}spawnCodex(e){const t=y("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const r=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()});this.process=r,S({input:r.stdout}).on("line",n=>{if(n.trim())try{const a=JSON.parse(n);this.handleStreamEvent(a)}catch{this.emitEvent({state:"delta",text:n})}});let i="";r.stderr?.on("data",n=>{i+=n.toString()}),r.on("close",n=>{this.process===r&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),r.on("error",n=>{this.process===r&&(this.process=null,n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}spawnAppServer(){const e=y("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],r=this.options.modelInstructionsFile??F(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);r&&t.push("-c",`model_instructions_file=${JSON.stringify(r)}`);const s=x(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:this.buildProcessEnv({NO_COLOR:"1"})});this.process=s,this.stderrBuf="",this.appServerStartupError=null,S({input:s.stdout}).on("line",n=>{if(!n.trim())return;let a;try{a=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(a)}),s.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),s.on("close",n=>{if(this.process!==s)return;const a=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(new Error(_(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&a&&this.handleProcessClose(n,this.stderrBuf)}),s.on("error",n=>{this.process===s&&(this.process=null,this.managedRoomTools.reset(),this.clearConfiguredManagedRoomContext(),this.rejectAllPending(n),n.code==="ENOENT"?this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?')):this.emit("error",n))})}async ensureAppServer(e){return this.appServerReady?this.appServerReady:(this.spawnAppServer(),this.appServerReady=this.initializeAppServer(e).catch(async t=>{throw this.appServerReady=null,await this.killProcess(),t}),this.appServerReady)}async initializeAppServer(e){const t=I(e);await this.sendRpc("initialize",{clientInfo:V,capabilities:{experimentalApi:!0}}),this.managedPermissionConfig=this.managedAgentPolicy?await this.buildManagedPermissionConfig():null;const r=!!(this.configuredManagedRoomContextToken||this.configuredManagedRoomExecutor);if(this.managedPermissionConfig&&r&&!this.managedRoomTools.isConfigured()&&(this.managedRoomTools.configure(this.configuredManagedRoomContextToken,this.configuredManagedRoomExecutor),!this.managedRoomTools.isConfigured()))throw new Error("codex_managed_room_tool_context_unavailable");if(this.agentSessionId&&this.managedPermissionConfig&&r&&this.agentSessionId!==this.managedRoomToolThreadId){if(!this.managedRoomTools.isConfigured())throw new Error("codex_managed_room_tool_context_unavailable");this.agentSessionId=null}if(this.agentSessionId){const c={threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},persistExtendedHistory:!0,...this.options.hidden||!this.persistNativeSession?{threadSource:"subagent"}:{},...t?{model:t}:{}};let l;try{l=await this.sendRpc("thread/resume",{...c,excludeTurns:!0})}catch(p){if(!oe(p))throw p;l=await this.sendRpc("thread/resume",c)}this.agentSessionId=l.thread?.id??this.agentSessionId,this.managedPermissionConfig&&(R(l,this.managedPermissionConfig.profileId),r&&(this.managedRoomTools.bindThread(this.agentSessionId),this.managedRoomToolThreadId=this.agentSessionId));return}const s=this.options.hidden||!this.persistNativeSession,i=s?void 0:await this.resolveCodexProjectId(),n={cwd:this.workDir,approvalPolicy:"never",...this.managedPermissionConfig?{config:this.managedPermissionConfig.threadConfig}:{sandbox:"danger-full-access"},serviceName:"Shennian",threadSource:s?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...this.managedPermissionConfig&&r?{dynamicTools:z}:{},...i?{projectId:i}:{},...t?{model:t}:{}};let a;if(s&&!this.managedPermissionConfig)a=await this.sendRpc("thread/start",n);else try{a=await this.sendRpc("thread/start",{...n,historyMode:"paginated"})}catch(c){throw ie(c)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5728 Codex Desktop \u4E2D\u7EE7\u7EED\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u964D\u7EA7\u521B\u5EFA\u4E0D\u53EF\u89C1\u7684 legacy \u4F1A\u8BDD\u3002",{cause:c}):c}const d=a.thread?.id;if(!d)throw new Error("codex app-server thread/start returned no thread id");this.managedPermissionConfig&&(R(a,this.managedPermissionConfig.profileId),r&&(this.managedRoomTools.bindThread(d),this.managedRoomToolThreadId=d)),this.agentSessionId=d}async buildManagedPermissionConfig(){const e=this.managedAgentPolicy;if(!e)throw new Error("managed_agent_policy_missing");const t=e.adapterPolicy.externalCapabilities.mcp?[]:H(await this.sendRpc("config/read",{cwd:e.adapterPolicy.workDir}));return J(U(e),e.adapterPolicy.workDir,{configuredMcpServerNames:t,shellSubprocessPolicy:e.environmentPolicy.shellSubprocess})}async resolveCodexProjectId(){const e=this.workDir?.trim();if(e)try{const t=[];let r;do{const c=await this.sendRpc("project/list",{limit:100,...r?{cursor:r}:{}});t.push(...c.data??[]),r=c.nextCursor}while(r);const s=de(t,e);if(s)return s;const i=g.resolve(e),n=`shennian-project-${w("sha256").update(i).digest("hex")}`,d=(await this.sendRpc("project/create",{idempotencyKey:n,name:g.basename(i)||i,roots:[{path:i}],metadata:{createdBy:"Shennian"}})).project?.id;if(!d)throw new Error("codex app-server project/create returned no project id");return d}catch(t){throw ae(t)?new Error("\u5F53\u524D Codex CLI \u4E0D\u652F\u6301\u521B\u5EFA\u53EF\u5F52\u5C5E\u5230 Codex Desktop \u9879\u76EE\u7684\u65B0\u4F1A\u8BDD\uFF0C\u8BF7\u624B\u52A8\u5347\u7EA7 Codex CLI \u540E\u91CD\u8BD5\u3002\u795E\u5FF5\u4E0D\u4F1A\u81EA\u52A8\u5347\u7EA7\uFF0C\u4E5F\u4E0D\u4F1A\u76F4\u63A5\u4FEE\u6539 Codex Desktop \u7684\u79C1\u6709\u4FA7\u680F\u7D22\u5F15\u3002",{cause:t}):t}}async startTurnWithRecovery(e,t,r,s,i){try{return await this.startTurn(e,t,r,s,i)}catch(n){if(!v(n))throw n;if(this.managedPermissionConfig){this.logManagedThreadLifecycle("rollout.missing",{threadId:e,recovery:"recreate"}),await this.killProcess(!0),this.agentSessionId=null,this.managedRoomToolThreadId=null,await this.ensureAppServer(r);const a=this.agentSessionId;if(!a)throw new Error("codex app-server did not recreate the managed thread",{cause:n});return this.logManagedThreadLifecycle("thread.recreated",{previousThreadId:e,threadId:a}),this.emitEvent({state:"start",agentSessionId:a}),await this.startFreshManagedTurnWithReadinessRetry(a,t,r,s,i)}return await this.killProcess(),await this.ensureAppServer(r),await this.startTurn(e,t,r,s,i)}}async startFreshManagedTurnWithReadinessRetry(e,t,r,s,i){const n=[0,100,300,1e3];let a;for(let d=0;d<n.length;d+=1){const c=n[d];c>0&&await new Promise(l=>setTimeout(l,c));try{const l=await this.startTurn(e,t,r,s,i);return d>0&&this.logManagedThreadLifecycle("rollout.ready",{threadId:e,attempt:d+1}),l}catch(l){if(a=l,!v(l)||d===n.length-1)throw l;this.logManagedThreadLifecycle("rollout.retry",{threadId:e,attempt:d+2,delayMs:n[d+1]})}}throw a}logManagedThreadLifecycle(e,t){console.error(`[codex.managed] ${JSON.stringify({event:e,...t})}`)}async startTurn(e,t,r,s,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[le(t,i)],approvalPolicy:"never",...this.managedPermissionConfig?{}:{sandboxPolicy:{type:"dangerFullAccess"}},...r?{model:r}:{},...s?{effort:s}:{}})}catch(n){throw s&&j(n)?new Error(`Codex app-server does not accept reasoning effort "${s}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),!(!e||!t||!this.process)&&await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3)}sendRpc(e,t,r=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const s=this.process;if(!s?.stdin)return Promise.reject(new Error("codex app-server is not running"));const i=this.rpcSeq++,n=JSON.stringify({id:i,method:e,params:t});return new Promise((a,d)=>{const c=setTimeout(()=>{this.pendingRequests.delete(i),d(new Error(`codex app-server request timed out: ${e}`))},r);this.pendingRequests.set(i,{resolve:a,reject:d,timer:c}),s.stdin.write(`${n}
|
|
5
|
+
`,l=>{l&&(clearTimeout(c),this.pendingRequests.delete(i),d(l))})})}handleAppServerMessage(e){if(e.method==="item/tool/call"&&e.id!=null){this.handleManagedRoomToolCall(e.id,e.params??{});return}if(e.id!=null){const r=typeof e.id=="number"?this.pendingRequests.get(e.id):void 0;r&&(clearTimeout(r.timer),this.pendingRequests.delete(e.id),e.error?r.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):r.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const r=typeof t.turn=="object"&&t.turn!==null?t.turn:null,s=typeof r?.id=="string"?r.id:null,i=typeof t.threadId=="string"?t.threadId:"";s&&(this.activeTurnId=s,this.managedRoomToolThreadId===i&&!this.managedRoomTools.bindTurn(i,s)&&this.emitErrorIfOpen("codex_managed_room_turn_binding_failed"));break}case"thread/status/changed":{const r=typeof t.threadId=="string"?t.threadId:"";if(r&&this.agentSessionId&&r!==this.agentSessionId)break;const s=typeof t.status=="object"&&t.status!==null?t.status:null;s?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(s)});break}case"item/agentMessage/delta":{const r=typeof t.itemId=="string"?t.itemId:"";r&&this.deltaItemIds.add(r),typeof t.delta=="string"&&this.emitText(t.delta);break}case"item/completed":this.handleAppServerCompletedItem(t);break;case"thread/tokenUsage/updated":this.handleAppServerUsage(t);break;case"turn/completed":{const r=typeof t.turn=="object"&&t.turn!==null?t.turn:null,s=typeof r?.id=="string"?r.id:null;(!this.activeTurnId||s===this.activeTurnId)&&(this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),this.emitFinalIfOpen());break}case"turn/failed":{const r=E(t);if(this.scheduleManagedAsyncRolloutRecovery(r))return;this.emitErrorIfOpen(r||"turn failed");break}case"error":{const r=E(t);if(Z(r)){this.emitTransientStatus(r);return}if(this.scheduleManagedAsyncRolloutRecovery(r))return;this.emitErrorIfOpen(r);break}}}scheduleManagedAsyncRolloutRecovery(e){if(!this.managedPermissionConfig||!this.managedTurnReplay||!v(new Error(e)))return!1;if(this.managedAsyncRecovery)return!0;if(this.managedTurnReplay.recoveryCount>=2)return!1;this.managedTurnReplay.recoveryCount+=1;const t=this.managedTurnReplay.recoveryCount;return this.logManagedThreadLifecycle("rollout.failed-async",{threadId:this.agentSessionId,recoveryCount:t}),this.managedAsyncRecovery=this.recoverManagedTurnAfterAsyncRolloutFailure().catch(r=>{const s=v(r)?"no rollout found after managed thread recovery":r instanceof Error?r.message:"managed thread recovery failed";this.emitErrorIfOpen(s)}).finally(()=>{this.managedAsyncRecovery=null}),!0}async recoverManagedTurnAfterAsyncRolloutFailure(){const e=this.managedTurnReplay,t=this.agentSessionId;if(!e||!t||this.terminalState!=="open")return;this.activeTurnId=null,this.managedRoomTools.deactivateTurn(),await this.killProcess(!0),this.agentSessionId=null,this.managedRoomToolThreadId=null,await this.ensureAppServer(e.modelId);const r=this.agentSessionId;if(!r)throw new Error("codex app-server did not recreate the managed thread");this.logManagedThreadLifecycle("thread.recreated-async",{previousThreadId:t,threadId:r,recoveryCount:e.recoveryCount}),this.emitEvent({state:"start",agentSessionId:r});const s=await this.startFreshManagedTurnWithReadinessRetry(r,e.text,e.modelId,e.reasoningEffort,e.attachments);if(this.activeTurnId=s.turn?.id??null,this.managedRoomToolThreadId===r&&this.activeTurnId&&!this.managedRoomTools.bindTurn(r,this.activeTurnId))throw new Error("codex_managed_room_turn_binding_failed")}async handleManagedRoomToolCall(e,t){const r=await this.managedRoomTools.execute(t);if(this.writeAppServerResponse(e,r),r.success&&t.tool==="shennian_room_send"){const s=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:null,i=typeof s?.text=="string"?s.text.trim():"";i&&this.emitText(i,"block")}}writeAppServerResponse(e,t){const r=this.process;r?.stdin&&r.stdin.write(`${JSON.stringify({id:e,result:t})}
|
|
6
6
|
`)}mapThreadStatusToRunPhase(e){const t=e?.type==="active"&&Array.isArray(e.activeFlags)?e.activeFlags:[];return t.includes("waitingOnApproval")?"waiting_approval":t.includes("waitingOnUserInput")?"waiting_user_input":"thinking"}handleAppServerNonJsonStdout(e){const t=se(e);ee(t)&&this.failAppServerStartup(new Error("Codex \u672A\u767B\u5F55\u3002\u8BF7\u5148\u5728\u8FD9\u53F0\u7535\u8111\u7684\u7EC8\u7AEF\u8FD0\u884C `codex`\uFF0C\u6309\u63D0\u793A\u5B8C\u6210 ChatGPT \u767B\u5F55\u6216\u914D\u7F6E API key\uFF0C\u7136\u540E\u518D\u56DE\u5230\u795E\u5FF5\u53D1\u9001\u6D88\u606F\u3002"))}failAppServerStartup(e){this.appServerStartupError||(this.appServerStartupError=e,this.rejectAllPending(e),this.process?.kill("SIGTERM"))}handleAppServerCompletedItem(e){const t=typeof e.item=="object"&&e.item!==null?e.item:null;if(!(!t||typeof t.type!="string")&&!Q(t))switch(t.type){case"agentMessage":{const r=typeof t.id=="string"?t.id:"";if(r&&this.deltaItemIds.has(r))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const r=typeof t.command=="string"?t.command:"",s=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";r&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:r}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[s,t.exitCode!=null?`
|
|
7
|
-
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const r=typeof t.tool=="string"?t.tool:t.type;if(
|
|
8
|
-
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:r.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&
|
|
9
|
-
`);this.emitText(r,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const s=e.usage;if(s){const i=s.input_tokens??s.prompt_tokens,n=s.output_tokens??s.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const s=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(s);return}case"error":{const s=typeof e.message=="string"?e.message:"";if(s.includes("Reconnecting"))return;this.emitErrorIfOpen(s||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const s=e.thread_id??e.session_id??e.id;typeof s=="string"&&s&&(this.agentSessionId=s,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const s=e.content??e.text;s&&this.emitText(s);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&
|
|
7
|
+
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const r=typeof t.tool=="string"?t.tool:t.type;if(T(r))return;const s=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:r,args:s}),this.emitEvent({state:"tool-result",name:r,result:k(t.error??t.result??t.contentItems??"")});break}case"fileChange":break;case"approvalRequest":case"approval_request":case"permissionRequest":case"permission_request":case"userInputRequired":case"user_input_required":{this.emitApprovalPending(t);break}}}handleAppServerUsage(e){const t=typeof e.tokenUsage=="object"&&e.tokenUsage!==null?e.tokenUsage:null,r=typeof t?.last=="object"&&t.last!==null?t.last:null,s=typeof t?.total=="object"&&t.total!==null?t.total:null,i=r??s;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,a=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||a!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:a??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!Y(t))switch(t.type){case"agent_message":case"reasoning":{e.type==="item.completed"&&t.text&&this.emitText(t.text,"block");break}case"command_execution":{if(e.type==="item.started"&&t.command&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:t.command}}),e.type==="item.completed"){const r=[t.aggregated_output??"",t.exit_code!=null?`
|
|
8
|
+
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:r.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&T(t.tool))return;if(e.type==="item.started"&&this.emitEvent({state:"tool-call",name:t.tool??"mcp_tool",args:{server:t.server,...t.arguments&&typeof t.arguments=="object"?t.arguments:{}}}),e.type==="item.completed"){const r=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:r??k(t.result)})}break}case"file_change":break;case"web_search":{e.type==="item.completed"&&t.query&&this.emitText(`[web_search] ${t.query}`,"block");break}case"todo_list":{if(e.type==="item.completed"&&t.items?.length){const r=t.items.map(s=>`- [${s.completed?"x":" "}] ${s.text}`).join(`
|
|
9
|
+
`);this.emitText(r,"block")}break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{(e.type==="item.started"||e.type==="item.completed")&&this.emitApprovalPending(t);break}case"error":{e.type==="item.completed"&&t.message&&this.emitEvent({state:"error",message:t.message});break}default:break}}handleStreamEvent(e){const t=e.type??e.event;switch(t?.startsWith("item.")&&this.handleCodexItemLine(e),t){case"turn.completed":{const s=e.usage;if(s){const i=s.input_tokens??s.prompt_tokens,n=s.output_tokens??s.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const s=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(s);return}case"error":{const s=typeof e.message=="string"?e.message:"";if(s.includes("Reconnecting"))return;this.emitErrorIfOpen(s||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const s=e.thread_id??e.session_id??e.id;typeof s=="string"&&s&&(this.agentSessionId=s,this.emitEvent({state:"start",agentSessionId:this.agentSessionId}));return}case"turn.started":return}if(t?.startsWith("item."))return;switch(e.type??e.event){case"message":case"text":case"content":{const s=e.content??e.text;s&&this.emitText(s);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&T(e.name))return;this.emitEvent({state:"tool-call",name:e.name,args:e.arguments??e.input});break}case"function_call_output":case"tool_result":{if(e.name&&T(e.name))return;this.emitEvent({state:"tool-result",name:e.name,result:e.output??e.result});break}case"approval_request":case"approvalRequest":case"permission_request":case"permissionRequest":case"user_input_required":case"userInputRequired":{this.emitApprovalPending(e);break}case"error":break;case"done":case"completed":{e.usage&&(this.pendingUsage={inputTokens:e.usage.prompt_tokens??e.usage.input_tokens??0,outputTokens:e.usage.completion_tokens??e.usage.output_tokens??0}),this.emitFinalIfOpen();break}}}emitEvent(e){if(this.terminalState!=="open"&&e.state!=="final"&&e.state!=="error"&&e.state!=="aborted")return;const t={...e,runId:this.runId,seq:this.seq++};this.emit("agentEvent",t)}isApprovalLikeName(e){return typeof e!="string"?!1:/approval|permission|confirm|user[_-]?input|request[_-]?user/i.test(e)}emitApprovalPending(e){const t=typeof e.command=="string"?e.command:"",r=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",s=typeof e.prompt=="string"?e.prompt:typeof e.reason=="string"?e.reason:typeof e.message=="string"?e.message:t?`Codex \u8BF7\u6C42\u6267\u884C\u547D\u4EE4\uFF1A${t}`:"Codex \u6B63\u5728\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u540E\u7EE7\u7EED\u3002";this.emitEvent({state:"approval-pending",approval:{title:r,content:s,source:"codex",actionHint:"\u5F53\u524D\u795E\u5FF5\u6682\u4E0D\u652F\u6301\u8FDC\u7A0B\u786E\u8BA4\uFF0C\u8BF7\u56DE\u5230\u8FD0\u884C Codex \u7684\u7535\u8111/\u7EC8\u7AEF\u5B8C\u6210\u786E\u8BA4\u3002"}})}async killProcess(e=!1){const t=this.process;if(this.appServerReady=null,this.activeTurnId=null,this.managedRoomTools.reset(),e||this.clearConfiguredManagedRoomContext(),!t){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),t.kill("SIGTERM"),await new Promise(r=>{t.on("close",r),setTimeout(()=>{t.kill("SIGKILL"),r()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,r]of this.pendingRequests.entries())clearTimeout(r.timer),r.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const r=_(t);if(e!==0&&e!==null){const s=r||`codex exited with code ${e}`;this.emitErrorIfOpen(s)}else te(r)?this.emitErrorIfOpen(r):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.managedTurnReplay=null,this.managedRoomTools.deactivateTurn(),this.clearConfiguredManagedRoomContext(),this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.managedTurnReplay=null,this.managedRoomTools.deactivateTurn(),this.clearConfiguredManagedRoomContext(),this.emitEvent({state:"error",message:X(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}clearConfiguredManagedRoomContext(){this.configuredManagedRoomContextToken=null,this.configuredManagedRoomExecutor=null}emitTransientStatus(e){const t=K(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
|
|
10
10
|
`,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let r=ne(e);r&&(t==="block"&&this.hasEmittedText&&!r.startsWith(`
|
|
11
11
|
`)&&(this.textTail.endsWith(`
|
|
12
12
|
`)?r=`
|
|
@@ -35,6 +35,10 @@ export type ManagerRecord = {
|
|
|
35
35
|
defaultWorkerAgentType?: AgentType | null;
|
|
36
36
|
defaultWorkerModelId?: string | null;
|
|
37
37
|
status: ManagerStatus;
|
|
38
|
+
/** Inbox events remain durable until the provider emits `start`. This makes
|
|
39
|
+
* continuation delivery at-least-once across daemon crashes and dispatch
|
|
40
|
+
* failures instead of deleting work when a synthetic request is swallowed. */
|
|
41
|
+
deliveringInboxEventIds?: string[];
|
|
38
42
|
managedWorkerSessionIds: string[];
|
|
39
43
|
attachedExternalChannels: string[];
|
|
40
44
|
createdAt: string;
|
|
@@ -82,6 +86,7 @@ export declare class ManagerRegistry {
|
|
|
82
86
|
defaultWorkerAgentType?: AgentType | null;
|
|
83
87
|
defaultWorkerModelId?: string | null;
|
|
84
88
|
status?: ManagerStatus;
|
|
89
|
+
deliveringInboxEventIds?: string[];
|
|
85
90
|
}): ManagerRecord;
|
|
86
91
|
getManager(sessionId: string): ManagerRecord | undefined;
|
|
87
92
|
enqueueInboxEvent(input: Omit<ManagerInboxEvent, 'id' | 'createdAt' | 'updatedAt'>): ManagerInboxEvent | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import u from"node:fs";import S from"node:path";import{randomUUID as m}from"node:crypto";import{isAgentHiddenPayload as v,sessionPreviewFromPayload as w}from"@shennian/wire";import{resolveShennianPath as A}from"../config/index.js";import{listSessionRecords as f,readMessages as T}from"../session/store.js";import{listProjectedSessions as M}from"../session/projection.js";const I=A("manager-registry.json");function g(){return new Date().toISOString()}function W(){return{managers:{},workers:{},replyTargets:{},inbox:{}}}class E{load(){try{const e=JSON.parse(u.readFileSync(I,"utf-8"));return{managers:e.managers??{},workers:e.workers??{},replyTargets:e.replyTargets??{},inbox:e.inbox??{}}}catch{return W()}}save(e){u.mkdirSync(S.dirname(I),{recursive:!0}),u.writeFileSync(I,JSON.stringify(e,null,2))}upsertManager(e){const t=this.load(),r=t.managers[e.sessionId],s=g(),a={sessionId:e.sessionId,agentSessionId:e.agentSessionId??r?.agentSessionId??null,workDir:e.workDir,machineId:e.machineId??r?.machineId??null,modelId:e.modelId,agentType:e.agentType??r?.agentType??"manager",providerModelId:e.providerModelId??r?.providerModelId??null,defaultWorkerAgentType:e.defaultWorkerAgentType??r?.defaultWorkerAgentType??null,defaultWorkerModelId:e.defaultWorkerModelId??r?.defaultWorkerModelId??null,status:e.status??r?.status??"idle",deliveringInboxEventIds:e.deliveringInboxEventIds??r?.deliveringInboxEventIds??[],managedWorkerSessionIds:r?.managedWorkerSessionIds??[],attachedExternalChannels:r?.attachedExternalChannels??[],createdAt:r?.createdAt??s,updatedAt:s};return t.managers[e.sessionId]=a,this.save(t),a}getManager(e){return this.load().managers[e]}enqueueInboxEvent(e){const t=this.load(),r=t.managers[e.managerSessionId],s=t.workers[e.workerSessionId];if(!r||!s||s.managedBy!==r.sessionId||s.workDir!==r.workDir)return null;const a=g(),o=t.inbox[e.managerSessionId]??[],d=o.findIndex(c=>c.workerSessionId===e.workerSessionId&&(e.kind==="worker.health"?c.kind==="worker.health":c.kind===e.kind&&c.runId===e.runId)),l=d>=0?{...o[d],...e,updatedAt:a}:{...e,id:`mie_${m()}`,createdAt:a,updatedAt:a};return d>=0?o[d]=l:o.push(l),t.inbox[e.managerSessionId]=o,this.save(t),l}listInboxEvents(e){return[...this.load().inbox[e]??[]].sort((t,r)=>t.createdAt.localeCompare(r.createdAt))}removeInboxEvents(e,t){if(!t.length)return;const r=this.load(),s=new Set(t);r.inbox[e]=(r.inbox[e]??[]).filter(a=>!s.has(a.id)),this.save(r)}addWorker(e){const t=this.load(),r=t.managers[e.managerSessionId];if(!r)throw new Error("Manager runtime is not registered");const s=e.sessionId??`sess_worker_${m()}`,a=g(),o={sessionId:s,agentType:e.agentType,workDir:e.workDir,managedBy:e.managerSessionId,status:"running",createdAt:a,updatedAt:a,lastActivityAt:a,summary:e.summary??null,agentSessionId:null,runId:null};return t.workers[s]=o,r.managedWorkerSessionIds.includes(s)||r.managedWorkerSessionIds.push(s),r.updatedAt=a,this.save(t),o}updateWorker(e,t){const r=this.load(),s=r.workers[e];if(!s)return;const a={...s,...t,updatedAt:g()};return r.workers[e]=a,this.save(r),a}listWorkers(e,t={}){const r=this.load(),s=r.managers[e];if(!s)return[];const a=Object.values(r.workers).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").map(n=>this.decorateManagedWorker(n,t.runningSessionIds)),o=new Set(a.map(n=>n.sessionId)),d=f().filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!o.has(n.sessionId)).map(n=>this.sessionRecordToWorker(n,t.runningSessionIds)),l=new Set([...a,...d].map(n=>n.sessionId)),h=(t.projectedSessions??M()).filter(n=>n.workDir===s.workDir).filter(n=>t.includeManagers||n.agentType!=="manager").filter(n=>!l.has(n.id)).map(n=>this.projectedSessionToWorker(n,t.runningSessionIds));return[...a,...d,...h].sort((n,k)=>k.lastActivityAt.localeCompare(n.lastActivityAt))}getWorkerForManager(e,t){const r=this.load(),s=r.managers[e],a=r.workers[t];if(!s)return;if(a)return a.workDir!==s.workDir||a.agentType==="manager"?void 0:this.decorateManagedWorker(a);const o=f().find(d=>d.sessionId===t);if(!(!o||o.workDir!==s.workDir||o.agentType==="manager"))return this.sessionRecordToWorker(o)}decorateManagedWorker(e,t){const r=t?.has(e.sessionId)?"running":e.status,s=this.readTranscriptSummary(e.sessionId,e.summary??null),a=s.lastMessagePreview??e.lastMessagePreview??e.summary??null,o=e.userGoal??s.userGoal??null;return{...e,managedBy:e.managedBy??null,status:r,title:e.title??o??a??e.summary??e.sessionId,userGoal:o,lastMessagePreview:a,summary:e.summary??a,canResume:e.canResume??e.status!=="aborted",source:e.source??"manager_registry",role:"managed_worker"}}sessionRecordToWorker(e,t){const r=this.readTranscriptSummary(e.sessionId,e.lastMessagePreview??null),s=r.lastMessagePreview??e.lastMessagePreview??null,a=r.userGoal,o=t?.has(e.sessionId)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:o,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt,title:a??s??e.sessionId,userGoal:a,lastMessagePreview:s,summary:s??a??null,agentSessionId:e.agentSessionId??null,runId:null,canResume:!!e.agentSessionId,source:"local_index",role:"project_session"}}projectedSessionToWorker(e,t){const r=e.lastMessagePreview??null,s=e.title?.trim()||r,a=t?.has(e.id)?"running":e.status==="failed"?"error":e.status==="completed"?"final":"idle";return{sessionId:e.id,agentType:e.agentType,workDir:e.workDir,managedBy:null,status:a,createdAt:e.createdAt,updatedAt:e.updatedAt,lastActivityAt:e.lastActivityAt||e.updatedAt,title:s??e.id,userGoal:s??null,lastMessagePreview:r,summary:r??s??null,agentSessionId:e.agentSessionId??null,runId:e.activity?.runId??null,canResume:!0,source:"app_projection",role:"project_session"}}readTranscriptSummary(e,t){const r=T(e,{limit:200});if(!r.length)return{userGoal:null,lastMessagePreview:t};const s=[...r].sort((d,l)=>d.ts-l.ts),a=s.find(d=>d.role==="user"),o=[...s].reverse().find(d=>y(d));return{userGoal:a?p(y(a),160):null,lastMessagePreview:p(y(o),180)??t}}createReplyTarget(e){const t=this.load(),r={replyTarget:`rt_${m()}`,managerSessionId:e.managerSessionId,channelId:e.channelId,conversationId:e.conversationId,messageId:e.messageId??null,createdAt:g()};return t.replyTargets[r.replyTarget]=r,this.save(t),r}getReplyTarget(e){return this.load().replyTargets[e]}getLatestReplyTargetForManager(e){return Object.values(this.load().replyTargets).filter(t=>t.managerSessionId===e).sort((t,r)=>r.createdAt.localeCompare(t.createdAt))[0]}}function y(i){return!i||v(i.payload)?null:w(i.payload,180)}function p(i,e){return i?i.length>e?`${i.slice(0,e)}...`:i:null}export{E as ManagerRegistry};
|
|
@@ -77,6 +77,10 @@ export declare class ManagerRuntimeService {
|
|
|
77
77
|
bindManagerAdapterEvents(sessionId: string, adapter: AgentAdapter): void;
|
|
78
78
|
updateManagerStatus(sessionId: string, status: 'idle' | 'running' | 'interrupting'): void;
|
|
79
79
|
private drainManagerInbox;
|
|
80
|
+
private acknowledgeManagerInboxDelivery;
|
|
81
|
+
/** A process-local `running` flag is not evidence after daemon restart. Keep
|
|
82
|
+
* unacknowledged inbox events and make them eligible for redelivery. */
|
|
83
|
+
private recoverPersistedInboxDeliveries;
|
|
80
84
|
private dispatchManagerContinuation;
|
|
81
85
|
getExternalChannelStatus(managerSessionId: string): ExternalChannelSessionStatus | null;
|
|
82
86
|
getManagerExternalChannelSystemPrompt(managerSessionId: string): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Y from"node:http";import{
|
|
2
|
-
`).trim()}function
|
|
1
|
+
import Y from"node:http";import{createHash as V,randomBytes as Q,randomUUID as p}from"node:crypto";import M from"node:fs";import T from"node:os";import w from"node:path";import{AVAILABLE_BUILTIN_AGENT_TYPES as X,extractPayloadText as Z,formatExternalConversationText as ee,formatExternalMessageLine as te,formatExternalAttachmentReference as ne,isAgentHiddenPayload as re,isToolPayload as ae}from"@shennian/wire";import{ManagerRegistry as se}from"./registry.js";import{readMessages as ie}from"../session/store.js";import{listProjectedSessions as oe}from"../session/projection.js";import{RetiredChannelRuntime as le,RetiredWeChatRpaSessionBindingSync as ce,createRetiredAutomationLane as de,createRetiredChannelApiClient as ue,retiredDirectWeChat as he,retiredDirectWeChat as $,splitExternalReplyText as ge,weChatChannelConversationId as pe}from"./retired-external-channel-compat.js";import{loadConfig as D,resolveShennianPath as me}from"../config/index.js";import{buildExternalChannelInstructions as fe}from"../agents/external-channel-instructions.js";import{getPersonalSyncIdentity as C}from"../personal-sync/epoch.js";const A=Number(process.env.SHENNIAN_MANAGER_IPC_BODY_MAX_BYTES||2*1024*1024),x=12*6e4;let _=null;function Ve(a){_=a}function Qe(){return _}function I(a){return w.resolve(a||T.homedir())}function u(a,e,n){a.writeHead(e,{"content-type":"application/json; charset=utf-8"}),a.end(JSON.stringify(n))}function ye(a){return/^agent-(.+)-\d+$/.exec(a)?.[1]??null}function we(a){return a==="manager"||a==="pi"?!1:a.startsWith("custom:")?!0:X.includes(a)}function Ie(a){const e=/^agent-.+-(\d+)$/.exec(a);if(!e)return null;const n=Number(e[1]);return Number.isInteger(n)&&n>=0?n:null}function B(a){return a.replace(/\r\n/g,`
|
|
2
|
+
`).trim()}function ke(a){const e=a.map((n,t)=>[`${t+1}. \u4E8B\u4EF6\u7C7B\u578B\uFF1A${n.kind}`,`Worker\uFF1A${n.workerSessionId}`,n.runId?`Run\uFF1A${n.runId}`:"",`\u6458\u8981\uFF1A
|
|
3
3
|
${n.summary}`].filter(Boolean).join(`
|
|
4
4
|
`));return`Manager \u4E8B\u4EF6\u6536\u4EF6\u7BB1\u4E2D\u6709 ${a.length} \u6761\u5F85\u5904\u7406\u4E8B\u4EF6\uFF1A
|
|
5
5
|
|
|
@@ -7,19 +7,19 @@ ${e.join(`
|
|
|
7
7
|
|
|
8
8
|
`)}
|
|
9
9
|
|
|
10
|
-
\u8BF7\u7ED3\u5408\u7528\u6237\u76EE\u6807\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA\u6216\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`}function ke(a){try{const e=JSON.parse(a),n=e.type==="tool_result"||e.result?"tool_result":"tool_call",t=e.name||"tool",r=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",s=r.length>220?`${r.slice(0,220)}...`:r;return s?`[${n}] ${t}: ${s}`:`[${n}] ${t}`}catch{return"[tool]"}}function q(a){if(!a||typeof a!="object")return;const e=a,n=String(e.kind||""),t=String(e.name||""),r=String(e.mimeType||""),s=String(e.dataBase64||""),o=String(e.localPath||""),i=String(e.url||""),c=Number(e.size||0);if(s)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(n!=="image"&&n!=="video"&&n!=="file")&&!(!t||!r||!Number.isFinite(c)||c<0)&&!(!o&&!i))return{kind:n,name:t,mimeType:r,size:c,...o?{localPath:o}:{},...i?{url:i}:{}}}function L(a){const e=a.binding&&typeof a.binding=="object"&&!Array.isArray(a.binding)?a.binding:{},n=String(e.sessionId||a.managerSessionId||"").trim(),t=String(e.channelId||"").trim(),r=String(e.conversationId||"").trim(),s=String(e.conversationName||a.conversation||"").trim(),o=String(e.workDir||a.workDir||"").trim();if(!n)throw new Error("WeChat tool sessionId is required");if(!t)throw new Error("WeChat tool channelId is required");if(!r)throw new Error("WeChat tool conversationId is required");if(!s)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:n,channelId:t,conversationId:r,conversationName:s,workDir:o}}function Se(a,e){const n=[...a].sort((l,d)=>l.ts-d.ts),t=[];let r=null,s=null,o=null,i="";const c=()=>{if(!r)return;const l=i.trim();l&&t.push({...r,id:`${r.id}-compact`,payload:l}),r=null,s=null,o=null,i=""};for(const l of n){if(ne(l.payload)){c();continue}if(l.role==="user"){c(),t.push(l);continue}if(re(l.payload)){c(),t.push({...l,payload:ke(l.payload)});continue}const d=X(l.payload);if(!d.trim())continue;const h=fe(l.id),g=we(l.id);r&&r.role===l.role&&s===h&&h&&g!==null&&o!==null&&g===o+1?(i+=d,r.ts=l.ts,o=g):(c(),r=l,s=h,o=g,i=d)}return c(),t.slice(-e).sort((l,d)=>d.ts-l.ts)}async function Re(a){const e=[];let n=0;for await(const r of a){const s=Buffer.from(r);if(n+=s.byteLength,Number.isFinite(C)&&C>0&&n>C)throw new Error(`Manager IPC request body is too large. Max: ${C} bytes.`);e.push(s)}const t=Buffer.concat(e).toString("utf-8");return t?JSON.parse(t):{}}function f(a,e,n,t){a.client.sendRes({type:"res",id:e,ok:n,...n?{payload:t}:{error:String(t.error||"unknown error")}})}function F(a){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(a)}function O(){return Me()?w.join(b.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):pe("runtime","manager-ipc.json")}function Me(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class Ve{opts;managedPolicies=new Map;registry=new ae;channelRuntime;weChatRpaSessionSync;server=null;ipcUrl=null;ipcToken=V(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;weChatAutomationLane;constructor(e){this.opts=e,this.weChatAutomationLane=e.weChatAutomationLane??le(),this.channelRuntime=e.channelRuntime??new oe((n,t)=>{this.handleExternalMessage(n,t)},n=>this.registry.createReplyTarget(n).replyTarget,{createWeChatRpaProductRunner:e.createWeChatRpaProductRunner,weChatAutomationLane:this.weChatAutomationLane}),this.weChatRpaSessionSync=new ce({channelRuntime:this.channelRuntime,getLocalMachineId:()=>process.env.SHENNIAN_MACHINE_ID||N().machineId||"",listSessions:()=>this.listWeChatRpaSessionContexts(),listAuthoritativeSessions:()=>this.listAuthoritativeWeChatRpaSessionContexts()})}*listWeChatRpaSessionContexts(){const e=this.opts.getRuntime().sessions;for(const[n,t]of e)yield{sessionId:n,workDir:t.workDir,agentType:t.agentType,agentSessionId:t.agentSessionId,externalChannel:t.externalChannel??null}}async listAuthoritativeWeChatRpaSessionContexts(){const e=N(),n=process.env.SHENNIAN_MACHINE_ID||e.machineId||"";if(!e.machineToken||!n)return null;const t=this.listLocalWeChatRpaSessionContexts(n);return(await de({serverUrl:e.serverUrl,machineToken:e.machineToken}).listBindings({machineId:n})).bindings.filter(s=>s.enabled!==!1).map(s=>{const o=t.get(s.sessionId);return{sessionId:s.sessionId,workDir:s.sessionWorkDir?.trim()||o?.workDir?.trim()||b.homedir(),agentType:s.sessionAgentType?.trim()||o?.agentType||void 0,agentSessionId:s.sessionAgentSessionId??o?.agentSessionId??null,modelId:s.sessionModelId??o?.modelId??null,externalChannel:{connected:!0,configured:!0,type:"wechat-rpa",channelId:s.id,machineId:s.machineId,name:s.conversationName,canReply:s.allowReply!==!1,systemPrompt:null,wechatRpaSource:"wechat-channel",wechatRpaGroups:[{name:s.conversationName}],downloadAttachments:s.downloadMedia!==!1}}})}listLocalWeChatRpaSessionContexts(e){const n=new Map;for(const t of this.listWeChatRpaSessionContexts())n.set(t.sessionId,t);for(const t of ie())!t?.id||t.deletedAt||t.machineId&&e&&t.machineId!==e||n.has(t.id)||n.set(t.id,{sessionId:t.id,workDir:t.workDir,agentType:t.agentType,agentSessionId:t.agentSessionId,modelId:t.modelId,externalChannel:t.externalChannel??null});return n}async notifyWeChatRpaSessionBinding(e){await this.weChatRpaSessionSync.reconcileSession(e).catch(n=>{console.error(`[wechat-rpa-sync] reconcileSession failed sessionId=${e.sessionId}: ${n instanceof Error?n.message:String(n)}`)})}async reconcileWeChatRpaSessionBindings(){await this.weChatRpaSessionSync.reconcileAll().catch(e=>{console.error(`[wechat-rpa-sync] reconcileAll failed: ${e instanceof Error?e.message:String(e)}`)})}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=Y.createServer((n,t)=>{this.handleIpc(n,t)}),this.server.requestTimeout=A,this.server.timeout=A,this.server.keepAliveTimeout=A,this.server.headersTimeout=A+5e3,await new Promise((n,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>n())});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.reconcileWeChatRpaSessionBindings(),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=O();R.mkdirSync(w.dirname(e),{recursive:!0}),R.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),R.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=O(),n=JSON.parse(R.readFileSync(e,"utf-8"));(n.url===this.ipcUrl||n.token===this.ipcToken)&&R.unlinkSync(e)}catch{}}getInjectedEnv(e,n,t,r){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:n??"",SHENNIAN_MANAGER_WORKDIR:I(t),SHENNIAN_MANAGER_MODEL:r,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:I(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagedAgentPolicy(e,n){n?this.managedPolicies.set(e,n):this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,n){const t=this.registry.getManager(e),r=this.managedPolicies.get(e);return!t||!r||I(n)!==t.workDir?null:r}setManagerWorkerDefaults(e,n,t){const r=this.registry.getManager(e);r&&this.registry.upsertManager({...r,defaultWorkerAgentType:n??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const n=this.registry.getManager(e);return{agentType:n?.defaultWorkerAgentType??null,modelId:n?.defaultWorkerModelId??null}}noteManagerAgentSession(e,n,t,r){const s=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:n,workDir:I(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:r,agentType:s?.agentType,providerModelId:s?.providerModelId})}noteAgentEvent(e,n){if(!this.findWorker(e))return;const r={lastActivityAt:new Date().toISOString(),runId:n.runId};n.agentSessionId&&(r.agentSessionId=n.agentSessionId);const s=`${e}:${n.runId}`;if(n.state==="delta"&&n.text&&!n.thinking){const i=(this.workerTextAcc.get(s)??"")+n.text;this.workerTextAcc.set(s,i);const c=B(i);c&&(r.summary=c.length>160?`${c.slice(0,160)}...`:c)}if(n.state==="final"||n.state==="error"||n.state==="aborted"){r.status=n.state;const i=B(this.workerTextAcc.get(s)??"");i&&(r.summary=i.length>240?`${i.slice(0,240)}...`:i),this.workerTextAcc.delete(s)}else n.state==="start"&&(r.status="running");const o=this.registry.updateWorker(e,r);o&&(n.state==="final"||n.state==="error"||n.state==="aborted")&&this.enqueueWorkerEvent(o.managedBy,o,n.state,n.runId,n.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const n=this.opts.getRuntime();if(e.method.includes("channel")||e.method.includes("wechat-rpa")){f(n,e.id,!1,{error:"feature_retired"});return}const t=e.params??{};try{const r=String(t.managerSessionId||t.sessionId||"");if(!r)throw new Error("sessionId is required");const s=this.registry.getManager(r),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",i=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"){f(n,e.id,!0,{channel:this.channelRuntime.getManagerChannel(r,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const d=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${r}`),managerSessionId:r,sessionId:r,workDir:String(t.workDir||s?.workDir||""),type:"websocket",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,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.broadcastManagerChannelStatus(r),f(n,e.id,!0,{channel:d});return}if(o){f(n,e.id,!0,{channel:this.channelRuntime.getManagerChannel(r,"wechat-rpa",{includeSecret:!0})});return}if(i){const d=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${r}`),managerSessionId:r,sessionId:r,workDir:I(String(t.workDir||s?.workDir||"")),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:U(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:H(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,selfTriggerMarker:typeof t.selfTriggerMarker=="string"?t.selfTriggerMarker:void 0,deferInitialPoll:t.deferInitialPoll===void 0?void 0:!!t.deferInitialPoll,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});this.broadcastManagerChannelStatus(r),f(n,e.id,!0,{channel:d});return}if(c){const d=await this.channelRuntime.syncManagerWeChatRpaChannel(r);this.broadcastManagerChannelStatus(r),f(n,e.id,!0,d);return}if(l){const d=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:r,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(r),f(n,e.id,!0,d);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(r){f(n,e.id,!1,{error:r instanceof Error?r.message:String(r)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const n=this.opts.getRuntime();if(!n.client?.sendEvent)return;const t=this.registry.getManager(e),r=this.channelRuntime.getManagerChannelStatus(e),s=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");n.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:t?"manager":s?.agentType,agentSessionId:t?.agentSessionId??s?.agentSessionId??null,modelId:t?.modelId??s?.modelId??null,workDir:t?.workDir??s?.workDir,externalChannel:r}}})}async handleIpc(e,n){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(n,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),r=await Re(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){u(n,410,{ok:!1,error:"feature_retired"});return}const s=String(r.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!s)throw new Error("managerSessionId is required");const o=this.registry.getManager(s);if(t.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const i=new Set(this.opts.getRuntime().sessions.keys());u(n,200,{ok:!0,sessions:this.registry.listWorkers(s,{runningSessionIds:i})});return}if(t.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const i=String(r.agentType||r.agent||o.defaultWorkerAgentType||"codex");if(!ye(i))throw new Error(`Unsupported manager worker agent: ${i}`);const c=I(String(r.workDir||o.workDir));if(c!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const l=String(r.message||"");if(!l)throw new Error("message is required");const d=this.registry.addWorker({managerSessionId:s,agentType:i,workDir:c,summary:l.slice(0,120)}),h=String(r.modelId||(i===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(d.sessionId,i,c,l,null,h,s),u(n,200,{ok:!0,session:d});return}if(t.pathname==="/sessions/send"){const i=String(r.sessionId||""),c=String(r.message||""),l=r.enqueue===void 0?!0:!!r.enqueue,d=this.registry.getWorkerForManager(s,i);if(!d)throw new Error("Worker not found in this manager scope");const h=String(r.modelId||(d.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));l?await this.dispatchChatEnqueue(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h,s):await this.dispatchChatSend(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h,s),u(n,200,{ok:!0});return}if(t.pathname==="/sessions/queue"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const l=this.opts.getRuntime().chatQueue?.getSnapshot(i);u(n,200,{ok:!0,queue:l});return}if(t.pathname==="/sessions/queue/edit"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${p()}`,method:"chat.queue.edit",params:{sessionId:i,queueMessageId:String(r.queueMessageId||r.messageId||""),text:String(r.message||r.text||"")}}),u(n,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/queue/delete"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${p()}`,method:"chat.queue.delete",params:{sessionId:i,queueMessageId:String(r.queueMessageId||r.messageId||"")}}),u(n,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/stop"||t.pathname==="/sessions/terminate"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${p()}`,method:"chat.abort",params:{sessionId:i}}),this.registry.updateWorker(i,{status:"aborted"}),u(n,200,{ok:!0});return}if(t.pathname==="/sessions/read"){const i=String(r.sessionId||""),c=Number(r.limit||200);if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const d=se(i,{limit:Math.max(c*20,c)});u(n,200,{ok:!0,messages:Se(d,c),rawMessageCount:d.length});return}if(t.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(n,200,{ok:!0,path:w.join(o.workDir,".shennian")});return}if(t.pathname==="/external/reply"){const i=typeof r.replyTarget=="string"?this.registry.getReplyTarget(r.replyTarget):this.registry.getLatestReplyTargetForManager(s),c=String(r.text||""),l=q(r.attachment),d=String(r.idempotencyKey||p()),h=String(r.channelId||""),g=String(r.conversationId||""),M=!i&&(!h||!g)?await this.channelRuntime.getDefaultReplyTarget(s).catch(()=>null):null,k=i?.channelId||h||M?.channelId||"",E=i?.conversationId||g||M?.conversationId||"",W=k?this.channelRuntime.getChannelById(k):void 0,J=W&&(!!(i||h)||W.managedBy!=="session-sync");if(k&&J){if(!E)throw new Error("No external channel target is available for this Manager");const y=await this.channelRuntime.reply({managerSessionId:s,channelId:k,conversationId:E,messageId:i?.messageId??void 0,text:c,attachment:l,idempotencyKey:d});u(n,y.ok?200:400,y);return}const x=await this.tryDirectWeChatRpaReply(s,c,l);if(x){u(n,x.ok?200:400,x);return}let S;try{S=await this.sendManagedWeComReply({managerSessionId:s,text:c,attachment:l,idempotencyKey:d})}catch(y){const P=y instanceof Error?y.message:String(y);if(!F(P))throw y;S={ok:!1,error:P}}if(S.ok){u(n,200,{ok:!0,payload:S.payload});return}if(!F(S.error||"")||!k||!E){u(n,400,{ok:!1,error:S.error||"External send failed"});return}u(n,400,{ok:!1,error:`No local external channel is configured for ${k}`});return}if(t.pathname==="/channel/get"){u(n,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"websocket")});return}if(t.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerChannel({id:String(r.id||`websocket:${s}`),managerSessionId:s,workDir:o.workDir,type:"websocket",name:typeof r.name=="string"?r.name:void 0,enabled:!!r.enabled,wsUrl:typeof r.wsUrl=="string"?r.wsUrl:void 0,token:typeof r.token=="string"?r.token:void 0,canReply:r.canReply===void 0?void 0:!!r.canReply,systemPrompt:typeof r.systemPrompt=="string"?r.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:i});return}if(t.pathname==="/wechat-rpa/tool/read"){const i=L(r),c=m(r.limit)??10,l=typeof r.traceId=="string"?r.traceId:void 0;let d;try{d=await this.weChatAutomationLane.run(`wechat-tool:read:${i.channelId}`,()=>ue(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,limit:c,recentLimit:c,download:r.download==="never"?"never":"auto",traceId:l,timeoutMs:m(r.timeoutMs)}))}catch(h){u(n,400,{ok:!1,...z(h,l)});return}u(n,200,{ok:!0,messages:d.messages,outDir:d.outDir,helperTracePath:d.helperTracePath,activityGuardPath:d.activityGuardPath});return}if(t.pathname==="/wechat-rpa/tool/send"){const i=L(r),c=String(r.text||""),l=q(r.attachment),d=typeof r.traceId=="string"?r.traceId:void 0;let h;try{h=await this.weChatAutomationLane.run(`wechat-tool:send:${i.channelId}`,()=>$(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,text:c,attachment:l,traceId:d,timeoutMs:m(r.timeoutMs)}))}catch(g){u(n,400,{ok:!1,...z(g,d)});return}u(n,200,{ok:!0,...h});return}if(t.pathname==="/wechat-rpa/channel/get"){u(n,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"wechat-rpa",{includeSecret:!0})});return}if(t.pathname==="/wechat-rpa/channel/upsert"){const i=I(String(r.workDir||o?.workDir||""));if(!i)throw new Error("workDir is required");const c=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(r.id||`wechat-rpa:${s}`),managerSessionId:s,sessionId:s,workDir:i,name:typeof r.name=="string"?r.name:void 0,agentType:typeof r.agentType=="string"?r.agentType:void 0,agentSessionId:typeof r.agentSessionId=="string"?r.agentSessionId:null,modelId:typeof r.modelId=="string"?r.modelId:null,enabled:!!r.enabled,groups:U(r.groups),canReply:r.canReply===void 0?void 0:!!r.canReply,systemPrompt:typeof r.systemPrompt=="string"?r.systemPrompt:void 0,source:H(r.source),pollIntervalMs:m(r.pollIntervalMs),recentLimit:m(r.recentLimit),idleSeconds:m(r.idleSeconds),forceForeground:r.forceForeground===void 0?void 0:!!r.forceForeground,noRestore:r.noRestore===void 0?void 0:!!r.noRestore,downloadAttachments:r.downloadAttachments===void 0?void 0:!!r.downloadAttachments,downloadAttachmentsDir:typeof r.downloadAttachmentsDir=="string"?r.downloadAttachmentsDir:void 0,selfNickname:typeof r.selfNickname=="string"?r.selfNickname:void 0,selfTriggerMarker:typeof r.selfTriggerMarker=="string"?r.selfTriggerMarker:void 0,deferInitialPoll:r.deferInitialPoll===void 0?void 0:!!r.deferInitialPoll,privacyConsentAccepted:r.privacyConsentAccepted===void 0?void 0:!!r.privacyConsentAccepted,flowScriptPath:typeof r.flowScriptPath=="string"?r.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:c});return}if(t.pathname==="/wechat-rpa/channel/sync"){const{channel:i,messages:c}=await this.channelRuntime.syncManagerWeChatRpaChannel(s);this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:i,messages:c});return}if(t.pathname==="/wechat-rpa/outbound/cancel"){const i=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:s,idempotencyKey:typeof r.idempotencyKey=="string"?r.idempotencyKey:null,replyId:typeof r.replyId=="string"?r.replyId:null,reason:typeof r.reason=="string"?r.reason:"user_cancelled"});this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,...i});return}u(n,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){u(n,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async dispatchChatSend(e,n,t,r,s,o,i){await this.opts.dispatchReq({type:"req",id:`manager-send-${p()}`,method:"chat.send",params:{...D(),sessionId:e,text:r,agentType:n,workDir:t,agentSessionId:s,modelId:o,managedWorkerParentSessionId:i}})}async dispatchChatEnqueue(e,n,t,r,s,o,i){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${p()}`,method:"chat.enqueue",params:{...D(),sessionId:e,text:r,agentType:n,workDir:t,agentSessionId:s,modelId:o,managedWorkerParentSessionId:i}})}async tryDirectWeChatRpaReply(e,n,t){const r=this.opts.getRuntime().sessions.get(e),s=r?.externalChannel;if(!s||s.type!=="wechat-rpa"||!s.channelId||!s.name)return null;const o=process.env.SHENNIAN_MACHINE_ID||N().machineId||"";if(s.machineId&&o&&s.machineId!==o)return{ok:!1,error:`WeChat \u7ED1\u5B9A\u5C5E\u4E8E\u5176\u4ED6\u673A\u5668 (${s.machineId})\uFF0C\u8BF7\u5728\u7ED1\u5B9A\u673A\u5668\u4E0A\u53D1\u9001\u3002`};if(!n.trim()&&!t)return{ok:!1,error:"text or attachment is required"};const i=r?.workDir||process.cwd(),c={sessionId:e,channelId:s.channelId,conversationId:ge(s.name),conversationName:s.name,workDir:i};try{return{ok:!0,payload:await this.weChatAutomationLane.run(`wechat-tool:send:${c.channelId}`,()=>$(c,{conversation:c.conversationName,workDir:c.workDir,sessionId:c.sessionId,text:n,attachment:t}))}}catch(l){return{ok:!1,error:l instanceof Error?l.message:String(l)}}}async sendManagedWeComReply(e){const n=he(e.text);if(!n.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const t=this.opts.getRuntime().client;if(!t||typeof t.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const r=[];for(const[s,o]of n.entries()){const i=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:n.length>1?`${e.idempotencyKey}:${s+1}`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};r.push(i.payload)}if(e.attachment){const s=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:n.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};r.push(s.payload)}return{ok:!0,payload:r.length===1?r[0]:r}}enqueueWorkerEvent(e,n,t,r,s){this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:n.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:r??null,summary:s||n.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"})&&this.drainManagerInbox(e)}handleExternalMessage(e,n){const t=this.channelRuntime.getChannelById(n.channelId)??this.channelRuntime.getManagerChannel(e,n.channelType),r=this.channelRuntime.getChannelStatusById(n.channelId)??this.channelRuntime.getManagerChannelStatus(e),s=this.registry.getManager(e),o=t?.agentType||(s?"manager":"codex"),i=t?.workDir||s?.workDir||process.cwd(),c=t?.agentSessionId??s?.agentSessionId??null,l=t?.modelId||s?.modelId||"",d=Ce(n.attachments,n.channelType),h=Te(n);this.dispatchExternalMessage({sessionId:e,agentType:o,workDir:i,agentSessionId:c,modelId:l,text:h,messageId:n.messageId,attachments:d,externalChannel:be(r),replyTarget:n.replyTarget})}async dispatchExternalMessage(e){const n=typeof e.messageId=="string"&&e.messageId.trim()?e.messageId.trim():void 0;await this.opts.dispatchReq({type:"req",id:`external-enqueue-${p()}`,method:"chat.enqueue",params:{...D(),sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,origin:"external",queueMessageId:n,clientMessageId:n,attachments:e.attachments,externalChannel:e.externalChannel??null,replyTarget:e.replyTarget}})}scanWorkerHealth(){const e=Date.now(),n=this.registry.load();for(const t of Object.values(n.workers)){if(t.status!=="running")continue;const r=e-Date.parse(t.createdAt);if(r<10*6e4)continue;const s=t.healthNotifiedAt?Date.parse(t.healthNotifiedAt):0;if(s&&e-s<10*6e4)continue;const o=n.managers[t.managedBy];if(!o)continue;const i=Math.max(0,Math.floor((e-Date.parse(t.lastActivityAt))/6e4)),c=`\u5DF2\u8FD0\u884C ${Math.floor(r/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
10
|
+
\u8BF7\u7ED3\u5408\u7528\u6237\u76EE\u6807\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF1A\u7EE7\u7EED\u7B49\u5F85\u3001\u521B\u5EFA\u6216\u6307\u6D3E worker\u3001\u505C\u6B62 worker\u3001\u8BE2\u95EE\u7528\u6237\uFF0C\u6216\u5411\u7528\u6237\u6C47\u62A5\u3002`}function Se(a,e){return`manager-inbox:${V("sha256").update(a).update("\0").update(e.map(t=>t.id).join("\0")).digest("hex").slice(0,32)}`}function Me(a){try{const e=JSON.parse(a),n=e.type==="tool_result"||e.result?"tool_result":"tool_call",t=e.name||"tool",r=typeof e.result=="string"?e.result.replace(/\s+/g," ").trim():"",s=r.length>220?`${r.slice(0,220)}...`:r;return s?`[${n}] ${t}: ${s}`:`[${n}] ${t}`}catch{return"[tool]"}}function q(a){if(!a||typeof a!="object")return;const e=a,n=String(e.kind||""),t=String(e.name||""),r=String(e.mimeType||""),s=String(e.dataBase64||""),o=String(e.localPath||""),i=String(e.url||""),c=Number(e.size||0);if(s)throw new Error("Manager IPC external attachments must use localPath or url; dataBase64 is not accepted");if(!(n!=="image"&&n!=="video"&&n!=="file")&&!(!t||!r||!Number.isFinite(c)||c<0)&&!(!o&&!i))return{kind:n,name:t,mimeType:r,size:c,...o?{localPath:o}:{},...i?{url:i}:{}}}function L(a){const e=a.binding&&typeof a.binding=="object"&&!Array.isArray(a.binding)?a.binding:{},n=String(e.sessionId||a.managerSessionId||"").trim(),t=String(e.channelId||"").trim(),r=String(e.conversationId||"").trim(),s=String(e.conversationName||a.conversation||"").trim(),o=String(e.workDir||a.workDir||"").trim();if(!n)throw new Error("WeChat tool sessionId is required");if(!t)throw new Error("WeChat tool channelId is required");if(!r)throw new Error("WeChat tool conversationId is required");if(!s)throw new Error("WeChat tool conversationName is required");if(!o)throw new Error("WeChat tool workDir is required");return{sessionId:n,channelId:t,conversationId:r,conversationName:s,workDir:o}}function Re(a,e){const n=[...a].sort((l,d)=>l.ts-d.ts),t=[];let r=null,s=null,o=null,i="";const c=()=>{if(!r)return;const l=i.trim();l&&t.push({...r,id:`${r.id}-compact`,payload:l}),r=null,s=null,o=null,i=""};for(const l of n){if(re(l.payload)){c();continue}if(l.role==="user"){c(),t.push(l);continue}if(ae(l.payload)){c(),t.push({...l,payload:Me(l.payload)});continue}const d=Z(l.payload);if(!d.trim())continue;const h=ye(l.id),g=Ie(l.id);r&&r.role===l.role&&s===h&&h&&g!==null&&o!==null&&g===o+1?(i+=d,r.ts=l.ts,o=g):(c(),r=l,s=h,o=g,i=d)}return c(),t.slice(-e).sort((l,d)=>d.ts-l.ts)}async function Ce(a){const e=[];let n=0;for await(const r of a){const s=Buffer.from(r);if(n+=s.byteLength,Number.isFinite(A)&&A>0&&n>A)throw new Error(`Manager IPC request body is too large. Max: ${A} bytes.`);e.push(s)}const t=Buffer.concat(e).toString("utf-8");return t?JSON.parse(t):{}}function f(a,e,n,t){a.client.sendRes({type:"res",id:e,ok:n,...n?{payload:t}:{error:String(t.error||"unknown error")}})}function F(a){return/binding not found|unknown method|not supported|relay is not connected|no external channel/i.test(a)}function O(){return Ae()?w.join(T.tmpdir(),"shennian-vitest-runtime",String(process.pid),"manager-ipc.json"):me("runtime","manager-ipc.json")}function Ae(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}class Xe{opts;managedPolicies=new Map;registry=new se;channelRuntime;weChatRpaSessionSync;server=null;ipcUrl=null;ipcToken=Q(24).toString("hex");healthTimer=null;startPromise=null;workerTextAcc=new Map;weChatAutomationLane;constructor(e){this.opts=e,this.weChatAutomationLane=e.weChatAutomationLane??de(),this.channelRuntime=e.channelRuntime??new le((n,t)=>{this.handleExternalMessage(n,t)},n=>this.registry.createReplyTarget(n).replyTarget,{createWeChatRpaProductRunner:e.createWeChatRpaProductRunner,weChatAutomationLane:this.weChatAutomationLane}),this.weChatRpaSessionSync=new ce({channelRuntime:this.channelRuntime,getLocalMachineId:()=>process.env.SHENNIAN_MACHINE_ID||D().machineId||"",listSessions:()=>this.listWeChatRpaSessionContexts(),listAuthoritativeSessions:()=>this.listAuthoritativeWeChatRpaSessionContexts()})}*listWeChatRpaSessionContexts(){const e=this.opts.getRuntime().sessions;for(const[n,t]of e)yield{sessionId:n,workDir:t.workDir,agentType:t.agentType,agentSessionId:t.agentSessionId,externalChannel:t.externalChannel??null}}async listAuthoritativeWeChatRpaSessionContexts(){const e=D(),n=process.env.SHENNIAN_MACHINE_ID||e.machineId||"";if(!e.machineToken||!n)return null;const t=this.listLocalWeChatRpaSessionContexts(n);return(await ue({serverUrl:e.serverUrl,machineToken:e.machineToken}).listBindings({machineId:n})).bindings.filter(s=>s.enabled!==!1).map(s=>{const o=t.get(s.sessionId);return{sessionId:s.sessionId,workDir:s.sessionWorkDir?.trim()||o?.workDir?.trim()||T.homedir(),agentType:s.sessionAgentType?.trim()||o?.agentType||void 0,agentSessionId:s.sessionAgentSessionId??o?.agentSessionId??null,modelId:s.sessionModelId??o?.modelId??null,externalChannel:{connected:!0,configured:!0,type:"wechat-rpa",channelId:s.id,machineId:s.machineId,name:s.conversationName,canReply:s.allowReply!==!1,systemPrompt:null,wechatRpaSource:"wechat-channel",wechatRpaGroups:[{name:s.conversationName}],downloadAttachments:s.downloadMedia!==!1}}})}listLocalWeChatRpaSessionContexts(e){const n=new Map;for(const t of this.listWeChatRpaSessionContexts())n.set(t.sessionId,t);for(const t of oe())!t?.id||t.deletedAt||t.machineId&&e&&t.machineId!==e||n.has(t.id)||n.set(t.id,{sessionId:t.id,workDir:t.workDir,agentType:t.agentType,agentSessionId:t.agentSessionId,modelId:t.modelId,externalChannel:t.externalChannel??null});return n}async notifyWeChatRpaSessionBinding(e){await this.weChatRpaSessionSync.reconcileSession(e).catch(n=>{console.error(`[wechat-rpa-sync] reconcileSession failed sessionId=${e.sessionId}: ${n instanceof Error?n.message:String(n)}`)})}async reconcileWeChatRpaSessionBindings(){await this.weChatRpaSessionSync.reconcileAll().catch(e=>{console.error(`[wechat-rpa-sync] reconcileAll failed: ${e instanceof Error?e.message:String(e)}`)})}async start(){return this.startPromise?this.startPromise:(this.startPromise=this.doStart(),this.startPromise)}async doStart(){if(this.server)return;this.server=Y.createServer((n,t)=>{this.handleIpc(n,t)}),this.server.requestTimeout=x,this.server.timeout=x,this.server.keepAliveTimeout=x,this.server.headersTimeout=x+5e3,await new Promise((n,t)=>{this.server.once("error",t),this.server.listen(0,"127.0.0.1",()=>n())});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.reconcileWeChatRpaSessionBindings(),this.broadcastConfiguredChannelStatuses(),this.healthTimer=setInterval(()=>this.scanWorkerHealth(),6e4),this.healthTimer.unref(),this.recoverPersistedInboxDeliveries()}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=O();M.mkdirSync(w.dirname(e),{recursive:!0}),M.writeFileSync(e,JSON.stringify({url:this.ipcUrl,token:this.ipcToken,pid:process.pid,updatedAt:new Date().toISOString()},null,2),{mode:384}),M.chmodSync(e,384)}catch{}}removeIpcRuntimeFile(){try{const e=O(),n=JSON.parse(M.readFileSync(e,"utf-8"));(n.url===this.ipcUrl||n.token===this.ipcToken)&&M.unlinkSync(e)}catch{}}getInjectedEnv(e,n,t,r){return this.ipcUrl?{SHENNIAN_MANAGER_SESSION_ID:e,SHENNIAN_MANAGER_AGENT_SESSION_ID:n??"",SHENNIAN_MANAGER_WORKDIR:I(t),SHENNIAN_MANAGER_MODEL:r,SHENNIAN_MANAGER_IPC_URL:this.ipcUrl,SHENNIAN_MANAGER_IPC_TOKEN:this.ipcToken}:{}}registerManager(e){this.registry.upsertManager({...e,workDir:I(e.workDir),machineId:process.env.SHENNIAN_MACHINE_ID??null})}setManagedAgentPolicy(e,n){n?this.managedPolicies.set(e,n):this.managedPolicies.delete(e)}getManagedAgentPolicyForWorker(e,n){const t=this.registry.getManager(e),r=this.managedPolicies.get(e);return!t||!r||I(n)!==t.workDir?null:r}setManagerWorkerDefaults(e,n,t){const r=this.registry.getManager(e);r&&this.registry.upsertManager({...r,defaultWorkerAgentType:n??null,defaultWorkerModelId:t??null})}getManagerWorkerDefaults(e){const n=this.registry.getManager(e);return{agentType:n?.defaultWorkerAgentType??null,modelId:n?.defaultWorkerModelId??null}}noteManagerAgentSession(e,n,t,r){const s=this.registry.getManager(e);this.registry.upsertManager({sessionId:e,agentSessionId:n,workDir:I(t),machineId:process.env.SHENNIAN_MACHINE_ID??null,modelId:r,agentType:s?.agentType,providerModelId:s?.providerModelId})}noteAgentEvent(e,n){if(!this.findWorker(e))return;const r={lastActivityAt:new Date().toISOString(),runId:n.runId};n.agentSessionId&&(r.agentSessionId=n.agentSessionId);const s=`${e}:${n.runId}`;if(n.state==="delta"&&n.text&&!n.thinking){const i=(this.workerTextAcc.get(s)??"")+n.text;this.workerTextAcc.set(s,i);const c=B(i);c&&(r.summary=c.length>160?`${c.slice(0,160)}...`:c)}if(n.state==="final"||n.state==="error"||n.state==="aborted"){r.status=n.state;const i=B(this.workerTextAcc.get(s)??"");i&&(r.summary=i.length>240?`${i.slice(0,240)}...`:i),this.workerTextAcc.delete(s)}else n.state==="start"&&(r.status="running");const o=this.registry.updateWorker(e,r);o&&(n.state==="final"||n.state==="error"||n.state==="aborted")&&this.enqueueWorkerEvent(o.managedBy,o,n.state,n.runId,n.message)}findWorker(e){return this.registry.load().workers[e]}async handleAppReq(e){const n=this.opts.getRuntime();if(e.method.includes("channel")||e.method.includes("wechat-rpa")){f(n,e.id,!1,{error:"feature_retired"});return}const t=e.params??{};try{const r=String(t.managerSessionId||t.sessionId||"");if(!r)throw new Error("sessionId is required");const s=this.registry.getManager(r),o=e.method==="session.wechat-rpa.channel.get"||e.method==="manager.wechat-rpa.channel.get",i=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"){f(n,e.id,!0,{channel:this.channelRuntime.getManagerChannel(r,"websocket",{includeSecret:!0})});return}if(e.method==="manager.channel.upsert"){const d=await this.channelRuntime.upsertManagerChannel({id:String(t.id||`websocket:${r}`),managerSessionId:r,sessionId:r,workDir:String(t.workDir||s?.workDir||""),type:"websocket",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,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.broadcastManagerChannelStatus(r),f(n,e.id,!0,{channel:d});return}if(o){f(n,e.id,!0,{channel:this.channelRuntime.getManagerChannel(r,"wechat-rpa",{includeSecret:!0})});return}if(i){const d=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(t.id||`wechat-rpa:${r}`),managerSessionId:r,sessionId:r,workDir:I(String(t.workDir||s?.workDir||"")),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:U(t.groups),canReply:t.canReply===void 0?void 0:!!t.canReply,systemPrompt:typeof t.systemPrompt=="string"?t.systemPrompt:void 0,source:H(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,selfTriggerMarker:typeof t.selfTriggerMarker=="string"?t.selfTriggerMarker:void 0,deferInitialPoll:t.deferInitialPoll===void 0?void 0:!!t.deferInitialPoll,privacyConsentAccepted:t.privacyConsentAccepted===void 0?void 0:!!t.privacyConsentAccepted,flowScriptPath:typeof t.flowScriptPath=="string"?t.flowScriptPath:void 0});this.broadcastManagerChannelStatus(r),f(n,e.id,!0,{channel:d});return}if(c){const d=await this.channelRuntime.syncManagerWeChatRpaChannel(r);this.broadcastManagerChannelStatus(r),f(n,e.id,!0,d);return}if(l){const d=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:r,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(r),f(n,e.id,!0,d);return}throw new Error(`Unsupported manager app method: ${e.method}`)}catch(r){f(n,e.id,!1,{error:r instanceof Error?r.message:String(r)})}}broadcastConfiguredChannelStatuses(){for(const e of this.channelRuntime.listManagerChannelStatuses())this.broadcastManagerChannelStatus(e.managerSessionId)}broadcastManagerChannelStatus(e){const n=this.opts.getRuntime();if(!n.client?.sendEvent)return;const t=this.registry.getManager(e),r=this.channelRuntime.getManagerChannelStatus(e),s=this.channelRuntime.getManagerChannel(e,"wechat-rpa")??this.channelRuntime.getManagerChannel(e,"websocket");n.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:t?"manager":s?.agentType,agentSessionId:t?.agentSessionId??s?.agentSessionId??null,modelId:t?.modelId??s?.modelId??null,workDir:t?.workDir??s?.workDir,externalChannel:r}}})}async handleIpc(e,n){if(e.headers.authorization!==`Bearer ${this.ipcToken}`){u(n,401,{ok:!1,error:"Unauthorized"});return}try{const t=new URL(e.url??"/","http://127.0.0.1"),r=await Ce(e);if(t.pathname.startsWith("/channel/")||t.pathname.startsWith("/external/")||t.pathname.startsWith("/wechat-rpa/")){u(n,410,{ok:!1,error:"feature_retired"});return}const s=String(r.managerSessionId||e.headers["x-shennian-manager-session-id"]||"");if(!s)throw new Error("managerSessionId is required");const o=this.registry.getManager(s);if(t.pathname==="/sessions/list"){if(!o)throw new Error("Manager runtime is not registered");const i=new Set(this.opts.getRuntime().sessions.keys());u(n,200,{ok:!0,sessions:this.registry.listWorkers(s,{runningSessionIds:i})});return}if(t.pathname==="/sessions/start"){if(!o)throw new Error("Manager runtime is not registered");const i=String(r.agentType||r.agent||o.defaultWorkerAgentType||"codex");if(!we(i))throw new Error(`Unsupported manager worker agent: ${i}`);const c=I(String(r.workDir||o.workDir));if(c!==o.workDir)throw new Error("Manager can only start workers in the same workDir");const l=String(r.message||"");if(!l)throw new Error("message is required");const d=this.registry.addWorker({managerSessionId:s,agentType:i,workDir:c,summary:l.slice(0,120)}),h=String(r.modelId||(i===o.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));await this.dispatchChatSend(d.sessionId,i,c,l,null,h,s),u(n,200,{ok:!0,session:d});return}if(t.pathname==="/sessions/send"){const i=String(r.sessionId||""),c=String(r.message||""),l=r.enqueue===void 0?!0:!!r.enqueue,d=this.registry.getWorkerForManager(s,i);if(!d)throw new Error("Worker not found in this manager scope");const h=String(r.modelId||(d.agentType===o?.defaultWorkerAgentType?o.defaultWorkerModelId??"":""));l?await this.dispatchChatEnqueue(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h,s):await this.dispatchChatSend(d.sessionId,d.agentType,d.workDir,c,d.agentSessionId??null,h,s),u(n,200,{ok:!0});return}if(t.pathname==="/sessions/queue"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const l=this.opts.getRuntime().chatQueue?.getSnapshot(i);u(n,200,{ok:!0,queue:l});return}if(t.pathname==="/sessions/queue/edit"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-edit-${p()}`,method:"chat.queue.edit",params:{sessionId:i,queueMessageId:String(r.queueMessageId||r.messageId||""),text:String(r.message||r.text||"")}}),u(n,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/queue/delete"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-queue-delete-${p()}`,method:"chat.queue.delete",params:{sessionId:i,queueMessageId:String(r.queueMessageId||r.messageId||"")}}),u(n,200,{ok:!0,queue:this.opts.getRuntime().chatQueue?.getSnapshot(i)});return}if(t.pathname==="/sessions/stop"||t.pathname==="/sessions/terminate"){const i=String(r.sessionId||"");if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");await this.opts.dispatchReq({type:"req",id:`manager-abort-${p()}`,method:"chat.abort",params:{sessionId:i}}),this.registry.updateWorker(i,{status:"aborted"}),u(n,200,{ok:!0});return}if(t.pathname==="/sessions/read"){const i=String(r.sessionId||""),c=Number(r.limit||200);if(!this.registry.getWorkerForManager(s,i))throw new Error("Worker not found in this manager scope");const d=ie(i,{limit:Math.max(c*20,c)});u(n,200,{ok:!0,messages:Re(d,c),rawMessageCount:d.length});return}if(t.pathname==="/memory/path"){if(!o)throw new Error("Manager runtime is not registered");u(n,200,{ok:!0,path:w.join(o.workDir,".shennian")});return}if(t.pathname==="/external/reply"){const i=typeof r.replyTarget=="string"?this.registry.getReplyTarget(r.replyTarget):this.registry.getLatestReplyTargetForManager(s),c=String(r.text||""),l=q(r.attachment),d=String(r.idempotencyKey||p()),h=String(r.channelId||""),g=String(r.conversationId||""),R=!i&&(!h||!g)?await this.channelRuntime.getDefaultReplyTarget(s).catch(()=>null):null,k=i?.channelId||h||R?.channelId||"",v=i?.conversationId||g||R?.conversationId||"",P=k?this.channelRuntime.getChannelById(k):void 0,J=P&&(!!(i||h)||P.managedBy!=="session-sync");if(k&&J){if(!v)throw new Error("No external channel target is available for this Manager");const y=await this.channelRuntime.reply({managerSessionId:s,channelId:k,conversationId:v,messageId:i?.messageId??void 0,text:c,attachment:l,idempotencyKey:d});u(n,y.ok?200:400,y);return}const b=await this.tryDirectWeChatRpaReply(s,c,l);if(b){u(n,b.ok?200:400,b);return}let S;try{S=await this.sendManagedWeComReply({managerSessionId:s,text:c,attachment:l,idempotencyKey:d})}catch(y){const W=y instanceof Error?y.message:String(y);if(!F(W))throw y;S={ok:!1,error:W}}if(S.ok){u(n,200,{ok:!0,payload:S.payload});return}if(!F(S.error||"")||!k||!v){u(n,400,{ok:!1,error:S.error||"External send failed"});return}u(n,400,{ok:!1,error:`No local external channel is configured for ${k}`});return}if(t.pathname==="/channel/get"){u(n,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"websocket")});return}if(t.pathname==="/channel/upsert"){if(!o)throw new Error("Manager runtime is not registered");const i=await this.channelRuntime.upsertManagerChannel({id:String(r.id||`websocket:${s}`),managerSessionId:s,workDir:o.workDir,type:"websocket",name:typeof r.name=="string"?r.name:void 0,enabled:!!r.enabled,wsUrl:typeof r.wsUrl=="string"?r.wsUrl:void 0,token:typeof r.token=="string"?r.token:void 0,canReply:r.canReply===void 0?void 0:!!r.canReply,systemPrompt:typeof r.systemPrompt=="string"?r.systemPrompt:void 0});this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:i});return}if(t.pathname==="/wechat-rpa/tool/read"){const i=L(r),c=m(r.limit)??10,l=typeof r.traceId=="string"?r.traceId:void 0;let d;try{d=await this.weChatAutomationLane.run(`wechat-tool:read:${i.channelId}`,()=>he(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,limit:c,recentLimit:c,download:r.download==="never"?"never":"auto",traceId:l,timeoutMs:m(r.timeoutMs)}))}catch(h){u(n,400,{ok:!1,...z(h,l)});return}u(n,200,{ok:!0,messages:d.messages,outDir:d.outDir,helperTracePath:d.helperTracePath,activityGuardPath:d.activityGuardPath});return}if(t.pathname==="/wechat-rpa/tool/send"){const i=L(r),c=String(r.text||""),l=q(r.attachment),d=typeof r.traceId=="string"?r.traceId:void 0;let h;try{h=await this.weChatAutomationLane.run(`wechat-tool:send:${i.channelId}`,()=>$(i,{conversation:i.conversationName,workDir:i.workDir,sessionId:i.sessionId,text:c,attachment:l,traceId:d,timeoutMs:m(r.timeoutMs)}))}catch(g){u(n,400,{ok:!1,...z(g,d)});return}u(n,200,{ok:!0,...h});return}if(t.pathname==="/wechat-rpa/channel/get"){u(n,200,{ok:!0,channel:this.channelRuntime.getManagerChannel(s,"wechat-rpa",{includeSecret:!0})});return}if(t.pathname==="/wechat-rpa/channel/upsert"){const i=I(String(r.workDir||o?.workDir||""));if(!i)throw new Error("workDir is required");const c=await this.channelRuntime.upsertManagerWeChatRpaChannel({id:String(r.id||`wechat-rpa:${s}`),managerSessionId:s,sessionId:s,workDir:i,name:typeof r.name=="string"?r.name:void 0,agentType:typeof r.agentType=="string"?r.agentType:void 0,agentSessionId:typeof r.agentSessionId=="string"?r.agentSessionId:null,modelId:typeof r.modelId=="string"?r.modelId:null,enabled:!!r.enabled,groups:U(r.groups),canReply:r.canReply===void 0?void 0:!!r.canReply,systemPrompt:typeof r.systemPrompt=="string"?r.systemPrompt:void 0,source:H(r.source),pollIntervalMs:m(r.pollIntervalMs),recentLimit:m(r.recentLimit),idleSeconds:m(r.idleSeconds),forceForeground:r.forceForeground===void 0?void 0:!!r.forceForeground,noRestore:r.noRestore===void 0?void 0:!!r.noRestore,downloadAttachments:r.downloadAttachments===void 0?void 0:!!r.downloadAttachments,downloadAttachmentsDir:typeof r.downloadAttachmentsDir=="string"?r.downloadAttachmentsDir:void 0,selfNickname:typeof r.selfNickname=="string"?r.selfNickname:void 0,selfTriggerMarker:typeof r.selfTriggerMarker=="string"?r.selfTriggerMarker:void 0,deferInitialPoll:r.deferInitialPoll===void 0?void 0:!!r.deferInitialPoll,privacyConsentAccepted:r.privacyConsentAccepted===void 0?void 0:!!r.privacyConsentAccepted,flowScriptPath:typeof r.flowScriptPath=="string"?r.flowScriptPath:void 0});o&&this.registry.upsertManager({...o,status:o.status}),this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:c});return}if(t.pathname==="/wechat-rpa/channel/sync"){const{channel:i,messages:c}=await this.channelRuntime.syncManagerWeChatRpaChannel(s);this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,channel:i,messages:c});return}if(t.pathname==="/wechat-rpa/outbound/cancel"){const i=await this.channelRuntime.cancelManagerWeChatRpaOutbound({managerSessionId:s,idempotencyKey:typeof r.idempotencyKey=="string"?r.idempotencyKey:null,replyId:typeof r.replyId=="string"?r.replyId:null,reason:typeof r.reason=="string"?r.reason:"user_cancelled"});this.broadcastManagerChannelStatus(s),u(n,200,{ok:!0,...i});return}u(n,404,{ok:!1,error:`Unknown manager IPC path: ${t.pathname}`})}catch(t){u(n,400,{ok:!1,error:t instanceof Error?t.message:String(t)})}}async dispatchChatSend(e,n,t,r,s,o,i){await this.opts.dispatchReq({type:"req",id:`manager-send-${p()}`,method:"chat.send",params:{...C(),sessionId:e,text:r,agentType:n,workDir:t,agentSessionId:s,modelId:o,managedWorkerParentSessionId:i}})}async dispatchChatEnqueue(e,n,t,r,s,o,i){await this.opts.dispatchReq({type:"req",id:`manager-enqueue-${p()}`,method:"chat.enqueue",params:{...C(),sessionId:e,text:r,agentType:n,workDir:t,agentSessionId:s,modelId:o,managedWorkerParentSessionId:i}})}async tryDirectWeChatRpaReply(e,n,t){const r=this.opts.getRuntime().sessions.get(e),s=r?.externalChannel;if(!s||s.type!=="wechat-rpa"||!s.channelId||!s.name)return null;const o=process.env.SHENNIAN_MACHINE_ID||D().machineId||"";if(s.machineId&&o&&s.machineId!==o)return{ok:!1,error:`WeChat \u7ED1\u5B9A\u5C5E\u4E8E\u5176\u4ED6\u673A\u5668 (${s.machineId})\uFF0C\u8BF7\u5728\u7ED1\u5B9A\u673A\u5668\u4E0A\u53D1\u9001\u3002`};if(!n.trim()&&!t)return{ok:!1,error:"text or attachment is required"};const i=r?.workDir||process.cwd(),c={sessionId:e,channelId:s.channelId,conversationId:pe(s.name),conversationName:s.name,workDir:i};try{return{ok:!0,payload:await this.weChatAutomationLane.run(`wechat-tool:send:${c.channelId}`,()=>$(c,{conversation:c.conversationName,workDir:c.workDir,sessionId:c.sessionId,text:n,attachment:t}))}}catch(l){return{ok:!1,error:l instanceof Error?l.message:String(l)}}}async sendManagedWeComReply(e){const n=ge(e.text);if(!n.length&&!e.attachment)return{ok:!1,error:"text or attachment is required"};const t=this.opts.getRuntime().client;if(!t||typeof t.sendReq!="function")return{ok:!1,error:"Relay is not connected"};const r=[];for(const[s,o]of n.entries()){const i=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,text:o,idempotencyKey:n.length>1?`${e.idempotencyKey}:${s+1}`:e.idempotencyKey}});if(!i.ok)return{ok:!1,error:i.error||"External send failed"};r.push(i.payload)}if(e.attachment){const s=await t.sendReq({type:"req",id:`external-send-${p()}`,method:"external.send",params:{managerSessionId:e.managerSessionId,attachment:e.attachment,idempotencyKey:n.length?`${e.idempotencyKey}:attachment`:e.idempotencyKey}});if(!s.ok)return{ok:!1,error:s.error||"External send failed"};r.push(s.payload)}return{ok:!0,payload:r.length===1?r[0]:r}}enqueueWorkerEvent(e,n,t,r,s){this.registry.enqueueInboxEvent({managerSessionId:e,workerSessionId:n.sessionId,kind:t==="final"?"worker.final":t==="error"?"worker.error":"worker.aborted",priority:"normal",runId:r??null,summary:s||n.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"})&&this.drainManagerInbox(e)}handleExternalMessage(e,n){const t=this.channelRuntime.getChannelById(n.channelId)??this.channelRuntime.getManagerChannel(e,n.channelType),r=this.channelRuntime.getChannelStatusById(n.channelId)??this.channelRuntime.getManagerChannelStatus(e),s=this.registry.getManager(e),o=t?.agentType||(s?"manager":"codex"),i=t?.workDir||s?.workDir||process.cwd(),c=t?.agentSessionId??s?.agentSessionId??null,l=t?.modelId||s?.modelId||"",d=xe(n.attachments,n.channelType),h=ve(n);this.dispatchExternalMessage({sessionId:e,agentType:o,workDir:i,agentSessionId:c,modelId:l,text:h,messageId:n.messageId,attachments:d,externalChannel:De(r),replyTarget:n.replyTarget})}async dispatchExternalMessage(e){const n=typeof e.messageId=="string"&&e.messageId.trim()?e.messageId.trim():void 0;await this.opts.dispatchReq({type:"req",id:`external-enqueue-${p()}`,method:"chat.enqueue",params:{...C(),sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:e.modelId,origin:"external",queueMessageId:n,clientMessageId:n,attachments:e.attachments,externalChannel:e.externalChannel??null,replyTarget:e.replyTarget}})}scanWorkerHealth(){const e=Date.now(),n=this.registry.load();for(const t of Object.values(n.workers)){if(t.status!=="running")continue;const r=e-Date.parse(t.createdAt);if(r<10*6e4)continue;const s=t.healthNotifiedAt?Date.parse(t.healthNotifiedAt):0;if(s&&e-s<10*6e4)continue;const o=n.managers[t.managedBy];if(!o)continue;const i=Math.max(0,Math.floor((e-Date.parse(t.lastActivityAt))/6e4)),c=`\u5DF2\u8FD0\u884C ${Math.floor(r/6e4)} \u5206\u949F\uFF0C\u5C1A\u672A\u7ED3\u675F\u3002
|
|
11
11
|
|
|
12
12
|
\u5F53\u524D\u53EF\u89C1\u8FDB\u5C55\uFF1A
|
|
13
13
|
- \u6700\u8FD1\u6587\u672C\u6458\u8981\uFF1A${t.summary||"(\u65E0\u53EF\u89C1\u6458\u8981)"}
|
|
14
14
|
- \u6700\u8FD1\u6D3B\u52A8\u65F6\u95F4\uFF1A${t.lastActivityAt}
|
|
15
15
|
- \u6700\u8FD1 ${i} \u5206\u949F\u6CA1\u6709\u65B0\u6D3B\u52A8\u3002
|
|
16
16
|
|
|
17
|
-
\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(t.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:c}),this.registry.enqueueInboxEvent({managerSessionId:o.sessionId,workerSessionId:t.sessionId,kind:"worker.health",priority:"normal",runId:t.runId??null,summary:c})&&this.drainManagerInbox(o.sessionId)}}async interruptAndResumeManager(e,n,t){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&
|
|
17
|
+
\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(t.sessionId,{healthNotifiedAt:new Date(e).toISOString(),lastHealthSummary:c}),this.registry.enqueueInboxEvent({managerSessionId:o.sessionId,workerSessionId:t.sessionId,kind:"worker.health",priority:"normal",runId:t.runId??null,summary:c})&&this.drainManagerInbox(o.sessionId)}}async interruptAndResumeManager(e,n,t,r){this.opts.getRuntime().sessions.get(e.sessionId)?.currentRunId&&await this.opts.dispatchReq({type:"req",id:`manager-interrupt-${p()}`,method:"chat.abort",params:{...C(),sessionId:e.sessionId}});const i=t?`\u4E8B\u4EF6\u7C7B\u578B\uFF1A${t}
|
|
18
18
|
|
|
19
|
-
${n}`:n;await this.dispatchManagerContinuation(e,
|
|
19
|
+
${n}`:n;await this.dispatchManagerContinuation(e,i,!1,r)}bindManagerAdapterEvents(e,n){n.on("agentEvent",t=>{if(t.state==="start"&&t.agentSessionId){const r=this.registry.getManager(e);r&&this.noteManagerAgentSession(e,t.agentSessionId,r.workDir,r.modelId)}t.state==="start"&&(this.acknowledgeManagerInboxDelivery(e),this.updateManagerStatus(e,"running")),(t.state==="final"||t.state==="error"||t.state==="aborted")&&(this.updateManagerStatus(e,"idle"),queueMicrotask(()=>{this.drainManagerInbox(e)}))})}updateManagerStatus(e,n){const t=this.registry.getManager(e);t&&this.registry.upsertManager({...t,status:n})}async drainManagerInbox(e){const n=this.registry.getManager(e);if(!n||n.deliveringInboxEventIds?.length)return;const t=this.registry.listInboxEvents(e);if(!t.length)return;const r=t.find(l=>l.priority==="high"),s=this.opts.getRuntime().sessions.get(e);if(n.status!=="idle"&&!r||s?.currentRunId&&!r)return;const o=r?[r]:t.slice(0,20),i=ke(o),c=Se(e,o);try{r&&s?.currentRunId?(this.registry.upsertManager({...n,status:"interrupting",deliveringInboxEventIds:o.map(h=>h.id)}),await this.interruptAndResumeManager(n,i,r.kind,c)):(this.registry.upsertManager({...n,status:"running",deliveringInboxEventIds:o.map(h=>h.id)}),await this.dispatchManagerContinuation(n,i,!0,c)),await Promise.resolve();const l=this.registry.getManager(e),d=!!this.opts.getRuntime().sessions.get(e)?.currentRunId;l?.deliveringInboxEventIds?.length&&!d&&this.registry.upsertManager({...l,status:"idle",deliveringInboxEventIds:[]})}catch(l){this.registry.upsertManager({...n,status:"idle",deliveringInboxEventIds:[]}),console.error(`[manager-inbox] delivery failed sessionId=${e}: ${l instanceof Error?l.message:String(l)}`)}}acknowledgeManagerInboxDelivery(e){const n=this.registry.getManager(e),t=n?.deliveringInboxEventIds??[];!n||t.length===0||(this.registry.removeInboxEvents(e,t),this.registry.upsertManager({...n,deliveringInboxEventIds:[]}))}recoverPersistedInboxDeliveries(){const e=this.registry.load();for(const n of Object.values(e.managers))this.opts.getRuntime().sessions.get(n.sessionId)?.currentRunId||((n.status!=="idle"||n.deliveringInboxEventIds?.length)&&this.registry.upsertManager({...n,status:"idle",deliveringInboxEventIds:[]}),this.registry.listInboxEvents(n.sessionId).length>0&&queueMicrotask(()=>{this.drainManagerInbox(n.sessionId)}))}async dispatchManagerContinuation(e,n,t=!1,r){const s=e.agentType??"manager",o=s==="manager"?e.modelId:e.providerModelId??void 0,i={...C(),sessionId:e.sessionId,text:n,agentType:s,sessionMode:"manager",workDir:e.workDir,agentSessionId:e.agentSessionId,modelId:o,managerDefaultWorkerAgentType:e.defaultWorkerAgentType??null,managerDefaultWorkerModelId:e.defaultWorkerModelId??null,clientMessageId:r};await this.opts.dispatchReq({type:"req",id:`manager-${t?"enqueue":"send"}-${p()}`,method:t?"chat.enqueue":"chat.send",params:i})}getExternalChannelStatus(e){return this.channelRuntime.getManagerChannelStatus(e)}getManagerExternalChannelSystemPrompt(e){return this.channelRuntime.listManagerExternalChannels(e).map(n=>fe(n,void 0,e,"manager")).join(`
|
|
20
20
|
|
|
21
|
-
`).trim()}}function U(a){return Array.isArray(a)?a.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function H(a){return a==="wechat-channel"||a==="macos-flow"||a==="macos-probe"||a==="windows-visual-flow"||a==="wechat-rpa-lab"||a==="fixture-jsonl"?a:void 0}function
|
|
22
|
-
${h}`;return s.has(
|
|
23
|
-
`)}function
|
|
24
|
-
`);return[
|
|
25
|
-
`)}function
|
|
21
|
+
`).trim()}}function U(a){return Array.isArray(a)?a.map(e=>({name:String(e?.name||"").trim()})).filter(e=>e.name):[]}function H(a){return a==="wechat-channel"||a==="macos-flow"||a==="macos-probe"||a==="windows-visual-flow"||a==="wechat-rpa-lab"||a==="fixture-jsonl"?a:void 0}function xe(a,e){const n=a.map(t=>j(t,e)).filter(t=>t!=null);return n.length?n:void 0}function Ee(a,e,n,t){const r=e||"",s=new Set;return a.map((i,c)=>{const l=j(i,t),d=ne({type:i.type,name:l?.name||i.name,path:l?.path,mimeType:l?.mimeType||i.mimeType,availability:i.availability,providerError:i.providerError||Te(i)},c),h=l?.path||"";if(h){const R=`${d}
|
|
22
|
+
${h}`;return s.has(R)||r.includes(h)?"":(s.add(R),`${n}: ${d}`)}const g=d;return s.has(g)?"":(s.add(g),`${n}: ${d}`)}).filter(i=>!r.includes(i)).join(`
|
|
23
|
+
`)}function ve(a){const n=(a.channelType==="wechat-rpa"?be(a.sender.name):a.sender.name||a.sender.id)||(a.channelType==="wechat-rpa"?"\u5BF9\u65B9":a.sender.id)||"\u5BF9\u65B9",t=Ee(a.attachments,a.text,n,a.channelType),s=[a.text?te({senderName:n,senderId:a.sender.id,text:a.text}):t?"":`${n}:`,t].filter(Boolean).join(`
|
|
24
|
+
`);return[ee({conversationName:a.conversationName||a.conversationId,messages:[]}),s].filter(Boolean).join(`
|
|
25
|
+
`)}function be(a){const e=a?.trim()||"";return!e||/^(contact|self|system|unknown)$/i.test(e)||/^wechat-sender:/i.test(e)?"":e}function Te(a){return!a.localPath||a.availability&&a.availability!=="edge-local"||N(a.localPath)?"":"edge-local-unavailable"}function j(a,e){return a.localPath&&Ne(a,e)&&N(a.localPath)?{path:a.localPath,name:a.name||w.basename(a.localPath)||"attachment",mimeType:a.mimeType||K(a)}:a.url&&Pe(a)?{path:a.url,name:a.name||a.url.split("/").filter(Boolean).at(-1)||"attachment",mimeType:a.mimeType||K(a)}:a.thumbnailPath&&!G(a,e)&&N(a.thumbnailPath)?{path:a.thumbnailPath,name:a.name?`${a.name}-preview.png`:w.basename(a.thumbnailPath)||"preview.png",mimeType:"image/png"}:null}function De(a){return a?a.type!=="wechat-rpa"?a:{configured:a.configured,connected:a.connected,type:a.type,channelId:a.channelId,name:a.name,canReply:a.canReply,systemPrompt:a.systemPrompt,wechatRpaSource:a.wechatRpaSource,wechatRpaGroups:a.wechatRpaGroups,pollIntervalMs:a.pollIntervalMs,recentLimit:a.recentLimit,idleSeconds:a.idleSeconds,forceForeground:a.forceForeground,noRestore:a.noRestore,downloadAttachments:a.downloadAttachments,selfNickname:a.selfNickname,wechatRpaPrivacyConsentAccepted:a.wechatRpaPrivacyConsentAccepted,wechatRpaServerDecisionAvailable:a.wechatRpaServerDecisionAvailable,wechatRpaPreflightChecks:a.wechatRpaPreflightChecks,wechatRpaRuntimeState:a.wechatRpaRuntimeState,wechatRpaLastMessageAt:a.wechatRpaLastMessageAt,wechatRpaPendingReplyCount:a.wechatRpaPendingReplyCount,wechatRpaLastError:a.wechatRpaLastError}:null}function Ne(a,e){return a.providerError||a.availability&&a.availability!=="edge-local"?!1:G(a,e)?a.materializationKind==="original-file"&&a.isOriginal===!0&&a.mimeKindMatches===!0&&!We(a.localPath):!0}function Pe(a){return!a.providerError&&(!a.availability||a.availability==="server-url")}function G(a,e){if(e!=="wechat-rpa")return!1;const n=String(a.type||"").toLowerCase();return n==="file"||n==="video"||n==="video-file"}function We(a){const e=String(a||"").replace(/\\/g,"/"),n=w.basename(e).toLowerCase();return/(^|\/)\.uploads(\/|$)/.test(e)||/(^|-)preview([-.]|$)/i.test(n)}function N(a){try{return M.statSync(a).isFile()}catch{return!1}}function K(a){return a.type==="image"?"image/*":a.type==="video"?"video/*":a.type==="audio"?"audio/*":"application/octet-stream"}function m(a){const e=Number(a);return Number.isFinite(e)?e:void 0}function z(a,e){const n=E(a,"reasonCode")||$e(a),t=E(a,"outDir"),r=E(a,"helperTracePath"),s=E(a,"activityGuardPath");return{error:a instanceof Error?a.message:String(a||n),reasonCode:n,...t?{outDir:t}:{},...r?{helperTracePath:r}:{},...s?{activityGuardPath:s}:{},...e?{traceId:e}:{}}}function E(a,e){if(!a||typeof a!="object")return"";const n=a[e];return typeof n=="string"&&n.trim()?n.trim():""}function $e(a){const e=a instanceof Error?a.message:String(a||"");return/^([a-z][a-z0-9_]*)(?::|\b)/i.exec(e.trim())?.[1]||"wechat_tool_failed"}export{Xe as ManagerRuntimeService,Qe as getManagerRuntimeService,Ve as setManagerRuntimeService};
|
|
@@ -55,7 +55,9 @@ export declare class SessionManager {
|
|
|
55
55
|
private handleExternalTerminal;
|
|
56
56
|
private publishManagedActivitySnapshots;
|
|
57
57
|
private reloadCustomAgents;
|
|
58
|
-
handleReq(req: ReqFrame
|
|
58
|
+
handleReq(req: ReqFrame, options?: {
|
|
59
|
+
propagateErrors?: boolean;
|
|
60
|
+
}): Promise<void>;
|
|
59
61
|
private evictIdleSessions;
|
|
60
62
|
private resolvePath;
|
|
61
63
|
private resolveAuthorizedPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as M,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as W}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as B}from"./handlers/remote-access.js";import{cleanupPendingTransfers as j,handleFsLs as $,handleFsRead as L,handleFsRename as H,handleFsWrite as Y,handleFsTransferAbort as G,handleFsTransferChunk as K,handleFsTransferFinish as X,handleFsTransferStart as Z,handleFsExportMarkdownPdf as J,handleFsExportMarkdownPdfSetup as q,handleFsArchiveZip as ee}from"./handlers/fs.js";import{handleRegionProbe as se,handleRegionSwitch as te,handleUpgradeSetPolicy as ae}from"./handlers/control.js";import{ManagerRuntimeService as ie,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ne,TerminalQueuedChatDispatchError as oe}from"./queue.js";import{createAuthorizedFsRoot as re,createAuthorizedFsRootAsync as ce,resolveAuthorizedPath as le,resolveAuthorizedPathAsync as he,resolveSessionWorkDir as de}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ue}from"../agents/custom.js";import{handleManagedRoomBind as me,handleManagedRoomRestart as pe}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as fe,purgeSessionRecord as ge}from"./store.js";import{PersonalHistorySubscriptions as ve}from"./history-subscriptions.js";import{sweepAttachmentTransfers as be}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ye}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Re}from"./tool-detail-store.js";import{readDeletionSyncRevision as we,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as ke,clearDeletionSyncFailure as Se,DeletionSyncRetryWaitError as Ae,DeletionSyncTerminalError as Te,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ie}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ce=50;function Pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let s=r.baselineRevision;for(const t of r.deletions){if(!t?.sessionId||!Number.isSafeInteger(t.deletionRevision)||t.deletionRevision!==s+1||!Array.isArray(t.sources)||Number.isNaN(Date.parse(t.deletedAt))||t.nativeCleanupPolicy!==void 0&&t.nativeCleanupPolicy!=="none"&&t.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of t.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");s=t.deletionRevision}if(s!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as bs}from"../fs/boundary.js";class fs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,s=null,t,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=s,this.cliVersion=t,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o)}),this.chatQueue=new ne({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new ve(this.client),this.nativeCleanupOutbox=new Ie({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{be(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,s)=>this.resolveAuthorizedPath(e,s),resolveAuthorizedPathAsync:(e,s)=>this.resolveAuthorizedPathAsync(e,s),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,s)=>this.publishSessionActivity(e,s)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(s=>s.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const s=e.map(t=>{const a=R().find(i=>i.sessionId===t.params.sessionId);return{context:t.context,params:{...t.params,modelId:a?.modelId??t.params.modelId,sessionMode:a?.sessionMode??t.params.sessionMode,managerConfig:a?.managerConfig??t.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??t.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??t.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(s)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async dispatchQueuedChat(e){const s={value:null},t=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{s.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:t},e,i),s.value?.payload?.deliveryState==="failed")throw new oe(s.value.payload.failureMessage??s.value.error??"queued_chat_dispatch_failed");if(!s.value?.ok)throw new Error(s.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){ke();try{await this.synchronizeSessionDeletionsOnce(),Se()}catch(e){if(e instanceof Ae||e instanceof Te)throw e;const s=e instanceof Error?e.message:String(e),t=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(s);throw m({code:t?"transport_unavailable":"invalid_deletion_sync_response",retryable:t}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),s=we(),t=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:s}});if(!t.ok){const n=t.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(t.error??"unstructured_rejection"))}const a=t.payload;l(a),Pe(a,s);let i=a.baselineRevision;i>s&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const s=new ye({daemonInstallationId:y()});s.recordSession(e);for(const a of e.sources)s.record({...e,...a});const t=this.sessions.get(e.sessionId);if(t){this.revokeManagedRoomToken(t);try{await t.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),Re(e.sessionId),fe(e.sessionId),ge(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,s){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:s}})}async handleExternalTerminal(e){const s=this.sessions.get(e);s&&(this.sessions.delete(e),this.revokeManagedRoomToken(s),s.heartbeatTimer&&clearInterval(s.heartbeatTimer),s.adapter.removeAllListeners(),await s.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,s]of this.sessions.entries()){if(s.heartbeatTimer||!s.currentRunId||!s.adapter.getStatus)continue;const t=await s.adapter.getStatus().catch(()=>null);if(!t?.active||!t.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:t.runId||s.currentRunId,runPhase:t.runPhase,startedAt:new Date(s.lastActiveAt).toISOString(),updatedAt:a,canStop:t.canStop??!0});const i=s.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:t.runId||s.currentRunId,seq:i,runPhase:t.runPhase,canStop:t.canStop??!0},seq:i,id:`agent-status-${t.runId||s.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const s of S())s.startsWith("custom:")&&A(s);const e=T();for(const[s,t]of Object.entries(e.customAgents??{}))ue(s,t)}async handleReq(e){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(s,e);break;case"session.refresh":l(e.params),await N(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await Q(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await z(s,e);break;case"room.agent.bind-managed":await me(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await pe(s,this.managedRoomBinder,e);break;case"fs.ls":await $(s,e);break;case"fs.read":await L(s,e);break;case"fs.write":await Y(s,e);break;case"fs.export.markdown-pdf":await J(s,e);break;case"fs.export.markdown-pdf.setup":await q(s,e);break;case"fs.archive.zip":await ee(s,e);break;case"fs.rename":await H(s,e);break;case"fs.transfer.start":await Z(s,e);break;case"fs.transfer.chunk":await K(s,e);break;case"fs.transfer.finish":await X(s,e);break;case"fs.transfer.abort":await G(s,e);break;case"region.probe":await se(s,e);break;case"region.switch":await te(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ae(s,e);break;case"machine.remote-access.pause":await B(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await _(s,e);break;case"agent.config.clear":await M(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.workspace.validate":await W(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(t){this.client.sendRes({type:"res",id:e.id,ok:!1,error:t instanceof Error?t.message:String(t)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ce)return;let e=null;for(const[s,t]of this.sessions)(!e||t.lastActiveAt<e.session.lastActiveAt)&&(e={key:s,session:t});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return de(e)}resolveAuthorizedPath(e,s){return le(e,re(s))}async resolveAuthorizedPathAsync(e,s){return he(e,await ce(s))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),j(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const s=e.managedRoomContextToken;e.managedRoomContextToken=null,s&&this.managedRoomContextRevoker?.(s)}}export{fs as SessionManager,bs as resolveSessionWorkDir};
|
|
1
|
+
import{SESSION_SYNC_PROTOCOL_VERSION as p,SESSION_SYNC_DATA_EPOCH as f,isAvailableAgentType as k}from"@shennian/wire";import g from"node:crypto";import{getRegisteredAgents as S,unregisterAgent as A}from"../agents/adapter.js";import{loadConfig as T}from"../config/index.js";import{handleUpgradeStart as I,handleUpgradeStatus as C}from"../commands/upgrade.js";import{handleAgentsRefresh as P,handleModelsRefresh as E}from"./handlers/agents.js";import{handleAgentCapabilitiesList as D}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as M,handleAgentConfigGet as x,handleAgentConfigTest as O,handleAgentConfigUpsert as _}from"./handlers/agent-config.js";import{handleChatAbort as F,handleChatSend as v}from"./handlers/chat.js";import{handleSessionRefresh as N}from"./handlers/session-refresh.js";import{handleSessionToolDetail as Q}from"./handlers/tool-detail.js";import{handleSessionTitleSet as z}from"./handlers/title.js";import{handleSessionMessageStatus as U}from"./handlers/message-status.js";import{handleAgentWorkspaceValidation as W}from"./handlers/agent-workspace-validation.js";import{handleVoiceSessionBodyWindow as V}from"./handlers/voice-window.js";import{handleRemoteAccessPause as B}from"./handlers/remote-access.js";import{cleanupPendingTransfers as j,handleFsLs as $,handleFsRead as L,handleFsRename as H,handleFsWrite as Y,handleFsTransferAbort as G,handleFsTransferChunk as K,handleFsTransferFinish as X,handleFsTransferStart as Z,handleFsExportMarkdownPdf as J,handleFsExportMarkdownPdfSetup as q,handleFsArchiveZip as ee}from"./handlers/fs.js";import{handleRegionProbe as se,handleRegionSwitch as te,handleUpgradeSetPolicy as ae}from"./handlers/control.js";import{ManagerRuntimeService as ie,setManagerRuntimeService as b}from"../manager/runtime.js";import{ChatQueueManager as ne,TerminalQueuedChatDispatchError as oe}from"./queue.js";import{createAuthorizedFsRoot as re,createAuthorizedFsRootAsync as ce,resolveAuthorizedPath as le,resolveAuthorizedPathAsync as he,resolveSessionWorkDir as de}from"../fs/boundary.js";import"../agents/claude.js";import"../agents/codex.js";import"../agents/workbuddy.js";import"../agents/gemini.js";import"../agents/cursor.js";import"../agents/opencode.js";import"../agents/pi-coding-agent.js";import"../agents/manager.js";import{registerCustomAgent as ue}from"../agents/custom.js";import{handleManagedRoomBind as me,handleManagedRoomRestart as pe}from"./handlers/room-managed.js";import{getDaemonInstallationId as y,listSessionRecords as R,purgeCanonicalSession as fe,purgeSessionRecord as ge}from"./store.js";import{PersonalHistorySubscriptions as ve}from"./history-subscriptions.js";import{sweepAttachmentTransfers as be}from"./attachment-transfer-store.js";import{NativeSourceDeletionStore as ye}from"../native-fusion/native-source-deletions.js";import{purgeToolDetails as Re}from"./tool-detail-store.js";import{readDeletionSyncRevision as we,writeDeletionSyncRevision as w}from"./deletion-sync-state.js";import{assertDeletionSyncRunnable as ke,clearDeletionSyncFailure as Se,DeletionSyncRetryWaitError as Ae,DeletionSyncTerminalError as Te,recordDeletionSyncFailure as m}from"./deletion-sync-state.js";import{NativeSessionCleanupOutbox as Ie}from"./native-cleanup-outbox.js";import{assertPersonalSyncIdentity as l}from"../personal-sync/epoch.js";const Ce=50;function Pe(r,e){if(!r||!Number.isSafeInteger(r.baselineRevision)||r.baselineRevision<e||!Number.isSafeInteger(r.latestRevision)||r.latestRevision<r.baselineRevision||!Array.isArray(r.deletions))throw new Error("invalid deletion sync response");let t=r.baselineRevision;for(const s of r.deletions){if(!s?.sessionId||!Number.isSafeInteger(s.deletionRevision)||s.deletionRevision!==t+1||!Array.isArray(s.sources)||Number.isNaN(Date.parse(s.deletedAt))||s.nativeCleanupPolicy!==void 0&&s.nativeCleanupPolicy!=="none"&&s.nativeCleanupPolicy!=="auto")throw new Error("invalid session deletion command");for(const a of s.sources)if(!a?.sourceSessionKey||!k(a.agentType))throw new Error("invalid native source deletion command");t=s.deletionRevision}if(t!==r.latestRevision)throw new Error("invalid deletion sync revision range")}import{resolveSessionWorkDir as bs}from"../fs/boundary.js";class fs{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;remoteAccessPauseController;managedRoomContextRevoker;managedRoomExecutor;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;historySubscriptions;nativeCleanupOutbox;activityProbeTimer=null;attachmentCleanupTimer=null;activeRequests=0;constructor(e,t=null,s,a=null,i=null,c=null,n=null,h=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=a,this.managedRoomBinder=i,this.remoteAccessPauseController=c,this.managedRoomContextRevoker=n,this.managedRoomExecutor=h,this.managerRuntime=new ie({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o,{propagateErrors:!0})}),this.chatQueue=new ne({getRuntime:()=>this.getRuntime(),dispatchReq:o=>this.handleReq(o),dispatchQueuedReq:o=>this.dispatchQueuedChat(o)}),this.historySubscriptions=new ve(this.client),this.nativeCleanupOutbox=new Ie({logger:console.info}),this.nativeFusion?.setExternalTerminalHandler?.(o=>{this.handleExternalTerminal(o)}),this.nativeCleanupOutbox.start(),b(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(o=>{console.error("[session.activity] managed probe failed",o)})},15e3),this.activityProbeTimer.unref?.(),this.attachmentCleanupTimer=setInterval(()=>{const o=new Set([...R().map(d=>d.workDir),...Array.from(this.pendingTransfers.values()).map(d=>d.rootPath)]);for(const d of o)try{be(this.resolvePath(d))}catch(u){console.error("[attachment.cleanup] failed",u instanceof Error?u.message:String(u))}},6e4),this.attachmentCleanupTimer.unref?.()}getRuntime(){return{client:this.client,pendingTransfers:this.pendingTransfers,processedReqIds:this.processedReqIds,reloadCustomAgents:()=>this.reloadCustomAgents(),resolvePath:e=>this.resolvePath(e),resolveAuthorizedPath:(e,t)=>this.resolveAuthorizedPath(e,t),resolveAuthorizedPathAsync:(e,t)=>this.resolveAuthorizedPathAsync(e,t),runTextAcc:this.runTextAcc,sessions:this.sessions,evictIdleSessions:()=>this.evictIdleSessions(),nativeFusion:this.nativeFusion,managerRuntime:this.managerRuntime,chatQueue:this.chatQueue,activityPublisher:{publish:(e,t)=>this.publishSessionActivity(e,t)},upgradePolicyController:this.upgradePolicyController,revokeManagedRoomContextToken:this.managedRoomContextRevoker,managedRoomExecutor:this.managedRoomExecutor}}getUpgradeIdleState(){const e=[];return[...this.sessions.values()].some(t=>t.currentRunId!==null)&&e.push("active-agent-turn"),this.pendingTransfers.size>0&&e.push("file-transfer"),this.activeRequests>0&&e.push("active-daemon-request"),{idle:e.length===0,reasons:e}}setManagedRoomTurnPreparer(e){this.chatQueue.setManagedRoomTurnPreparer(e)}async enqueueManagedRoomBatch(e){const t=e.map(s=>{const a=R().find(i=>i.sessionId===s.params.sessionId);return{context:s.context,params:{...s.params,modelId:a?.modelId??s.params.modelId,sessionMode:a?.sessionMode??s.params.sessionMode,managerConfig:a?.managerConfig??s.params.managerConfig,managerDefaultWorkerAgentType:a?.managerDefaultWorkerAgentType??s.params.managerDefaultWorkerAgentType,managerDefaultWorkerModelId:a?.managerDefaultWorkerModelId??s.params.managerDefaultWorkerModelId}}});await this.chatQueue.enqueueManagedRoomBatch(t)}discardPendingManagedRoomMessages(e){return this.chatQueue.discardPendingManagedRoomMessages(e)}async dispatchQueuedChat(e){const t={value:null},s=new Proxy(this.client,{get:(c,n)=>{if(n==="sendRes")return o=>{t.value=o};const h=Reflect.get(c,n,c);return typeof h=="function"?h.bind(c):h}}),a=e.params,i=a.roomActivation===!0?{source:"room_activation",managedAgentPolicy:a.managedAgentPolicy,managedRoomPromptSnapshot:a.managedRoomPromptSnapshot}:void 0;if(await v({...this.getRuntime(),client:s},e,i),t.value?.payload?.deliveryState==="failed")throw new oe(t.value.payload.failureMessage??t.value.error??"queued_chat_dispatch_failed");if(!t.value?.ok)throw new Error(t.value?.error??"queued_chat_dispatch_failed")}async synchronizeSessionDeletions(){ke();try{await this.synchronizeSessionDeletionsOnce(),Se()}catch(e){if(e instanceof Ae||e instanceof Te)throw e;const t=e instanceof Error?e.message:String(e),s=/offline|not connected|disconnect|timed out|timeout|temporar|unavailable|ECONN|ENOTFOUND|EIO|EBUSY/i.test(t);throw m({code:s?"transport_unavailable":"invalid_deletion_sync_response",retryable:s}),e}}async synchronizeSessionDeletionsOnce(){const e=y(),t=we(),s=await this.client.sendReq({type:"req",id:`session-deletions-sync-${g.randomUUID()}`,method:"session.deletions.sync",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,afterRevision:t}});if(!s.ok){const n=s.rejection;throw n?(m({code:n.code,retryable:n.retryable&&n.requiredAction==="retry"}),new Error(n.code)):(m({code:"unstructured_rejection",retryable:!1}),new Error(s.error??"unstructured_rejection"))}const a=s.payload;l(a),Pe(a,t);let i=a.baselineRevision;i>t&&w(i);for(const n of a.deletions)n.deletionRevision<=i||(await this.applySessionDeletion(n),i=n.deletionRevision,w(i));if(i!==a.latestRevision)throw new Error(`deletion revision gap: applied ${i}, latest ${a.latestRevision}`);if(i===0)return;const c=await this.client.sendReq({type:"req",id:`session-deletions-ack-${g.randomUUID()}`,method:"session.deletions.ack",params:{protocolVersion:p,dataEpoch:f,daemonInstallationId:e,deletionRevision:i}});if(!c.ok)throw new Error(c.error??"session deletion acknowledgement failed")}async applySessionDeletion(e){const t=new ye({daemonInstallationId:y()});t.recordSession(e);for(const a of e.sources)t.record({...e,...a});const s=this.sessions.get(e.sessionId);if(s){this.revokeManagedRoomToken(s);try{await s.adapter.stop()}catch{}this.sessions.delete(e.sessionId)}this.historySubscriptions.closeSession(e.sessionId),this.nativeFusion?.handleSessionDeleted(e.sessionId),this.chatQueue.purgeSession(e.sessionId),Re(e.sessionId),fe(e.sessionId),ge(e.sessionId),this.runTextAcc.delete(e.sessionId),this.publishSessionActivity(e.sessionId,null),this.nativeCleanupOutbox.enqueue(e),this.nativeCleanupOutbox.kick()}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async handleExternalTerminal(e){const t=this.sessions.get(e);t&&(this.sessions.delete(e),this.revokeManagedRoomToken(t),t.heartbeatTimer&&clearInterval(t.heartbeatTimer),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})),this.chatQueue.noteTerminal(e)}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(t.heartbeatTimer||!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const a=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:a,canStop:s.canStop??!0});const i=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:i,runPhase:s.runPhase,canStop:s.canStop??!0},seq:i,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of S())t.startsWith("custom:")&&A(t);const e=T();for(const[t,s]of Object.entries(e.customAgents??{}))ue(t,s)}async handleReq(e,t={}){const s=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":l(e.params),await v(s,e);break;case"chat.enqueue":l(e.params),await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":l(e.params),await this.chatQueue.handleGet(e);break;case"chat.queue.edit":l(e.params),await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":l(e.params),await this.chatQueue.handleDelete(e);break;case"chat.abort":l(e.params),await F(s,e);break;case"session.refresh":l(e.params),await N(s,e);break;case"session.history.open":this.historySubscriptions.handleOpen(e);break;case"session.history.sync":this.historySubscriptions.handleSync(e);break;case"session.history.page":this.historySubscriptions.handlePage(e);break;case"session.history.close":this.historySubscriptions.handleClose(e);break;case"session.body.replica.subscribe":this.historySubscriptions.handleReplicaSubscribe(e);break;case"session.body.replica.unsubscribe":this.historySubscriptions.handleReplicaUnsubscribe(e);break;case"session.history":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"upgrade_required"});break;case"session.message.status":U(s,e);break;case"session.tool.detail":await Q(s,e);break;case"voice.session.body.window":V(s,e);break;case"session.title.set":await z(s,e);break;case"room.agent.bind-managed":await me(s,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await pe(s,this.managedRoomBinder,e);break;case"fs.ls":await $(s,e);break;case"fs.read":await L(s,e);break;case"fs.write":await Y(s,e);break;case"fs.export.markdown-pdf":await J(s,e);break;case"fs.export.markdown-pdf.setup":await q(s,e);break;case"fs.archive.zip":await ee(s,e);break;case"fs.rename":await H(s,e);break;case"fs.transfer.start":await Z(s,e);break;case"fs.transfer.chunk":await K(s,e);break;case"fs.transfer.finish":await X(s,e);break;case"fs.transfer.abort":await G(s,e);break;case"region.probe":await se(s,e);break;case"region.switch":await te(s,e);break;case"upgrade.start":await I(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await C(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await ae(s,e);break;case"machine.remote-access.pause":await B(this.client,this.remoteAccessPauseController,e);break;case"agents.refresh":await P(s,e);break;case"models.refresh":await E(s,e);break;case"agent.config.get":await x(s,e);break;case"agent.config.upsert":await _(s,e);break;case"agent.config.clear":await M(s,e);break;case"agent.config.test":await O(s,e);break;case"agent.workspace.validate":await W(s,e);break;case"agent.capabilities.list":await D(s,e);break;case"skill.list":case"skill.install":case"skill.doctor":case"skill.setup":case"skill.use":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;case"session.wechat-rpa.channel.get":case"session.wechat-rpa.channel.upsert":case"session.wechat-rpa.channel.sync":case"manager.wechat-rpa.channel.get":case"manager.wechat-rpa.channel.upsert":case"manager.wechat-rpa.channel.sync":this.client.sendRes({type:"res",id:e.id,ok:!1,error:"feature_retired"});break;default:this.client.sendRes({type:"res",id:e.id,ok:!1,error:`Unknown method: ${e.method}`})}}catch(a){if(t.propagateErrors)throw a;this.client.sendRes({type:"res",id:e.id,ok:!1,error:a instanceof Error?a.message:String(a)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<Ce)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(this.revokeManagedRoomToken(e.session),e.session.heartbeatTimer&&(clearInterval(e.session.heartbeatTimer),e.session.heartbeatTimer=null),e.session.adapter.removeAllListeners(),e.session.adapter.stop().catch(()=>{}),this.sessions.delete(e.key))}resolvePath(e){return de(e)}resolveAuthorizedPath(e,t){return le(e,re(t))}async resolveAuthorizedPathAsync(e,t){return he(e,await ce(t))}async cleanup(){this.nativeFusion?.setExternalTerminalHandler?.(null),this.nativeCleanupOutbox.stop(),this.historySubscriptions.closeAll(),this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null),this.attachmentCleanupTimer&&(clearInterval(this.attachmentCleanupTimer),this.attachmentCleanupTimer=null);for(const[,e]of this.sessions)this.revokeManagedRoomToken(e),e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),j(this.getRuntime()),await this.managerRuntime.stop(),b(null)}revokeManagedRoomToken(e){const t=e.managedRoomContextToken;e.managedRoomContextToken=null,t&&this.managedRoomContextRevoker?.(t)}}export{fs as SessionManager,bs as resolveSessionWorkDir};
|