shennian 0.3.30 → 0.3.31
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.
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"file": "src/agents/codex.js",
|
|
34
|
-
"beforeBytes":
|
|
35
|
-
"afterBytes":
|
|
34
|
+
"beforeBytes": 42123,
|
|
35
|
+
"afterBytes": 20861
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"file": "src/agents/command-spec.js",
|
|
@@ -136,8 +136,8 @@
|
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"file": "src/agents/workbuddy.js",
|
|
139
|
-
"beforeBytes":
|
|
140
|
-
"afterBytes":
|
|
139
|
+
"beforeBytes": 14157,
|
|
140
|
+
"afterBytes": 7328
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"file": "src/commands/agent.js",
|
|
@@ -601,8 +601,8 @@
|
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
603
|
"file": "src/session/manager.js",
|
|
604
|
-
"beforeBytes":
|
|
605
|
-
"afterBytes":
|
|
604
|
+
"beforeBytes": 21419,
|
|
605
|
+
"afterBytes": 11034
|
|
606
606
|
},
|
|
607
607
|
{
|
|
608
608
|
"file": "src/session/projection.js",
|
package/dist/src/agents/codex.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import{spawnSync as C}from"node:child_process";import{createInterface as
|
|
1
|
+
import{spawnSync as C}from"node:child_process";import{createInterface as S}from"node:readline";import{randomUUID as _}from"node:crypto";import A from"node:path";import{AgentAdapter as R,registerAgent as P}from"./adapter.js";import{resolveBuiltinCommand as g,spawnAgentCommand as v}from"./command-spec.js";import{buildAgentProcessEnv as x}from"../agent-env.js";import{ensurePlatformInstructionsFile as q}from"./platform-instructions.js";import{CODEX_APP_SERVER_CLIENT_INFO as O,extractAppServerErrorMessage as $,formatCodexErrorMessage as b,formatTransientCodexStatus as F,isCodexCollabAgentItem as L,isCodexCollabAgentToolName as f,isCodexLegacyCollabAgentItem as M,isCodexUnsupportedEffortError as U,isMissingCodexRolloutError as N,isTransientCodexErrorMessage as D,looksLikeCodexInteractiveAuthPrompt as W,looksLikeFatalCodexStderr as z,makeThreadTitle as B,normalizeCodexModelId as y,normalizeCodexReasoningEffort as G,normalizeCodexStderr as T,normalizeTerminalText as J,safeStringify as w,stripGitDirectiveArtifacts as H}from"./codex-utils.js";import{isCodexUnsupportedEffortError as ce,isMissingCodexRolloutError as he,normalizeCodexModelId as me,normalizeCodexReasoningEffort as fe}from"./codex-utils.js";function V(a){const e=a instanceof Error?a.message:String(a);return/already has an active writer|thread-store conflict/i.test(e)}function Q(a,e){const t=e?.flatMap(r=>K(r)).filter(r=>!/^https?:\/\//i.test(r.path)).filter(r=>r.path);return{type:"text",text:Y(a,e),text_elements:[],...t?.length?{local_images:t}:{}}}function K(a){if(a.kind==="image")return a.previewData?[{path:a.path,data:a.previewData,mimeType:a.mimeType}]:[{path:a.path}];if(a.mimeType.startsWith("video/")){const e=X(a.path);return e?[{path:e}]:[]}return[]}function X(a){if(!a||/^https?:\/\//i.test(a))return null;const e=A.extname(a),s=`${e?a.slice(0,-e.length):a}.preview.png`;return C("ffmpeg",["-y","-ss","0","-i",a,"-frames:v","1","-vf","scale=320:-1",s],{stdio:"ignore",timeout:15e3}).status===0?s:null}function Y(a,e){const t=e?.filter(i=>i.kind!=="image").filter(i=>i.path&&!/^https?:\/\//i.test(i.path));if(!t?.length||t.every(i=>a.includes(i.path)))return a;const r=t.map((i,n)=>{const o=i.name||`attachment-${n+1}`,d=i.mimeType?` (${i.mimeType})`:"";return`[${k(o)}${d}](<${i.path}>)`});return[a,"",...r].join(`
|
|
2
2
|
`)}function Z(a,e){const t=e?.filter(r=>r.path&&!/^https?:\/\//i.test(r.path)&&!a.includes(r.path));if(!t?.length)return a;const s=t.map((r,i)=>{const n=k(r.name||`attachment-${i+1}`);return r.kind==="image"?``:`[${n}](<${r.path}>)`});return[a,"",...s].join(`
|
|
3
|
-
`)}function j(a,e,t){return["queue","--thread",a,"--message",Z(e,t)]}function k(a){return a.replace(/[[\]\\]/g,"\\$&")}class ee extends R{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;namedThread=!1;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{}}async start(e,t,s){this.workDir=t,this.seq=0,s&&(this.agentSessionId=s)}async send(e,t,s,r,i){this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess());const n=
|
|
4
|
-
${p}`.trim();if(
|
|
5
|
-
`,p=>{p&&(clearTimeout(u),this.pendingRequests.delete(i),
|
|
6
|
-
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(f(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:w(t.error??t.result??t.contentItems??"")});break}case"fileChange":{const
|
|
7
|
-
|
|
8
|
-
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&f(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 s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??w(t.result)})}break}case"file_change":{if(e.type==="item.completed"&&t.changes?.length){const s=t.changes.map(r=>`${r.kind}: ${r.path}`).join(`
|
|
9
|
-
`);this.emitText(s,"block")}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 s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
|
|
3
|
+
`)}function j(a,e,t){return["queue","--thread",a,"--message",Z(e,t)]}function k(a){return a.replace(/[[\]\\]/g,"\\$&")}class ee extends R{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;namedThread=!1;deltaItemIds=new Set;lastTransientStatus="";agentSystemPrompt="";constructor(e={}){super(),this.options=e}externalChannel=null;shennianSessionId=null;extraEnv={};configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{}}async start(e,t,s){this.workDir=t,this.seq=0,s&&(this.agentSessionId=s)}async send(e,t,s,r,i){this.activeTurnId&&(await this.interruptActiveTurn().catch(()=>{}),await this.killProcess());const n=y(t),o=G(s),d=i?.trim()??"";d!==this.agentSystemPrompt&&(this.agentSystemPrompt=d,this.process&&await this.killProcess()),this.runId=_(),this.seq=0,this.resetRunState();try{await this.ensureAppServer(n)}catch(c){if(!this.agentSessionId||!V(c))throw c;return{deliveryMode:"external_owner_queue",queuedSubmissionId:await this.enqueueForExternalOwner(this.agentSessionId,e,r)}}const u=this.agentSessionId;if(!u)throw new Error("codex app-server did not create a thread");this.emitEvent({state:"start",agentSessionId:u}),this.namedThread||(await this.sendRpc("thread/name/set",{threadId:u,name:B(e)}).catch(()=>{}),this.namedThread=!0);const p=await this.startTurnWithRecovery(u,e,n,o,r);return this.activeTurnId=p.turn?.id??null,{deliveryMode:"direct"}}async enqueueForExternalOwner(e,t,s){const r=g("codex");if(!r)throw new Error('Command "codex" not found. Is OpenAI Codex CLI installed?');const i=j(e,t,s),n=v(r,i,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x({NO_COLOR:"1",...this.extraEnv})});return await new Promise((o,d)=>{let u="",p="",c=!1;const h=(l,m)=>{c||(c=!0,clearTimeout(I),l?d(l):o(m))},I=setTimeout(()=>{n.kill("SIGTERM"),h(new Error("Codex Desktop message queue timed out"))},6e4);I.unref?.(),n.stdout?.on("data",l=>{u+=l.toString()}),n.stderr?.on("data",l=>{p+=l.toString()}),n.once("error",l=>h(l)),n.once("close",l=>{const m=`${u}
|
|
4
|
+
${p}`.trim();if(l!==0){h(new Error(m||`codex queue exited with code ${l??"unknown"}`));return}const E=m.match(/Queued message\s+([^\s]+)\s+for thread/i)?.[1];h(void 0,E)})})}async resume(e){await this.killProcess(),this.agentSessionId=e,this.namedThread=!0,this.resetTextState()}async setTitle(e,t,s){const r=e.trim(),i=t.replace(/\s+/g," ").trim();!r||!i||(this.agentSessionId=r,s?.trim()&&(this.workDir=s),await this.ensureAppServer(),await this.sendRpc("thread/name/set",{threadId:r,name:i}),this.namedThread=!0)}async stop(){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 s=null;try{s=(await this.sendRpc("thread/read",{threadId:e,includeTurns:!1},5e3)).thread?.status??null}catch{}const r=s?.type==="active"||!!t;return{active:r,runId:t,runPhase:r?this.mapThreadStatusToRunPhase(s):null,canStop:!!(r&&t&&this.process)}}spawnCodex(e){const t=g("codex");if(!t){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const s=v(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x(this.extraEnv)});this.process=s,S({input:s.stdout}).on("line",n=>{if(n.trim())try{const o=JSON.parse(n);this.handleStreamEvent(o)}catch{this.emitEvent({state:"delta",text:n})}});let i="";s.stderr?.on("data",n=>{i+=n.toString()}),s.on("close",n=>{this.process===s&&(this.process=null,this.clearForceCloseTimer(),this.handleProcessClose(n,i))}),s.on("error",n=>{this.process===s&&(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=g("codex");if(!e){this.emit("error",new Error('Command "codex" not found. Is OpenAI Codex CLI installed?'));return}const t=["app-server","--listen","stdio://"],s=this.options.modelInstructionsFile??q(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,this.agentSystemPrompt);s&&t.push("-c",`model_instructions_file=${JSON.stringify(s)}`);const r=v(e,t,{cwd:this.workDir??void 0,stdio:["pipe","pipe","pipe"],env:x({NO_COLOR:"1",...this.extraEnv})});this.process=r,this.stderrBuf="",this.appServerStartupError=null,S({input:r.stdout}).on("line",n=>{if(!n.trim())return;let o;try{o=JSON.parse(n)}catch{this.handleAppServerNonJsonStdout(n);return}this.handleAppServerMessage(o)}),r.stderr?.on("data",n=>{this.stderrBuf+=n.toString()}),r.on("close",n=>{if(this.process!==r)return;const o=this.activeTurnId!=null;this.process=null,this.appServerReady=null,this.activeTurnId=null,this.rejectAllPending(new Error(T(this.stderrBuf)||`codex app-server exited with code ${n}`)),this.terminalState==="open"&&o&&this.handleProcessClose(n,this.stderrBuf)}),r.on("error",n=>{this.process===r&&(this.process=null,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=y(e);if(await this.sendRpc("initialize",{clientInfo:O,capabilities:{experimentalApi:!0}}),this.agentSessionId){const i=await this.sendRpc("thread/resume",{threadId:this.agentSessionId,cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",persistExtendedHistory:!0,...this.options.hidden?{threadSource:"subagent"}:{},...t?{model:t}:{}});this.agentSessionId=i.thread?.id??this.agentSessionId,this.namedThread=!!i.thread?.name;return}const s=await this.sendRpc("thread/start",{cwd:this.workDir,approvalPolicy:"never",sandbox:"danger-full-access",serviceName:"Shennian",threadSource:this.options.hidden?"subagent":"user",experimentalRawEvents:!1,persistExtendedHistory:!0,...t?{model:t}:{}}),r=s.thread?.id;if(!r)throw new Error("codex app-server thread/start returned no thread id");this.agentSessionId=r,this.namedThread=!!s.thread?.name}async startTurnWithRecovery(e,t,s,r,i){try{return await this.startTurn(e,t,s,r,i)}catch(n){if(!N(n))throw n;return await this.killProcess(),await this.ensureAppServer(s),await this.startTurn(e,t,s,r,i)}}async startTurn(e,t,s,r,i){try{return await this.sendRpc("turn/start",{threadId:e,input:[Q(t,i)],approvalPolicy:"never",sandboxPolicy:{type:"dangerFullAccess"},...s?{model:s}:{},...r?{effort:r}:{}})}catch(n){throw r&&U(n)?new Error(`Codex app-server does not accept reasoning effort "${r}" for this turn. Refresh models or upgrade Codex CLI, then retry.`,{cause:n}):n}}async interruptActiveTurn(){const e=this.agentSessionId,t=this.activeTurnId;!e||!t||!this.process||(await this.sendRpc("turn/interrupt",{threadId:e,turnId:t},5e3),this.activeTurnId=null)}sendRpc(e,t,s=6e4){if(this.appServerStartupError)return Promise.reject(this.appServerStartupError);const r=this.process;if(!r?.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((o,d)=>{const u=setTimeout(()=>{this.pendingRequests.delete(i),d(new Error(`codex app-server request timed out: ${e}`))},s);this.pendingRequests.set(i,{resolve:o,reject:d,timer:u}),r.stdin.write(`${n}
|
|
5
|
+
`,p=>{p&&(clearTimeout(u),this.pendingRequests.delete(i),d(p))})})}handleAppServerMessage(e){if(e.id!=null){const s=this.pendingRequests.get(e.id);s&&(clearTimeout(s.timer),this.pendingRequests.delete(e.id),e.error?s.reject(new Error(e.error.message||`codex app-server error ${e.error.code??""}`.trim())):s.resolve(e.result))}if(!e.method)return;const t=e.params??{};switch(e.method){case"turn/started":{const s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;r&&(this.activeTurnId=r);break}case"thread/status/changed":{const s=typeof t.threadId=="string"?t.threadId:"";if(s&&this.agentSessionId&&s!==this.agentSessionId)break;const r=typeof t.status=="object"&&t.status!==null?t.status:null;r?.type==="active"&&this.emitEvent({state:"heartbeat",runPhase:this.mapThreadStatusToRunPhase(r)});break}case"item/agentMessage/delta":{const s=typeof t.itemId=="string"?t.itemId:"";s&&this.deltaItemIds.add(s),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 s=typeof t.turn=="object"&&t.turn!==null?t.turn:null,r=typeof s?.id=="string"?s.id:null;(!this.activeTurnId||r===this.activeTurnId)&&(this.activeTurnId=null,this.emitFinalIfOpen());break}case"error":{const s=$(t);if(D(s)){this.emitTransientStatus(s);return}this.emitErrorIfOpen(s);break}}}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=J(e);W(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")&&!L(t))switch(t.type){case"agentMessage":{const s=typeof t.id=="string"?t.id:"";if(s&&this.deltaItemIds.has(s))return;typeof t.text=="string"&&this.emitText(t.text,"block");break}case"commandExecution":{const s=typeof t.command=="string"?t.command:"",r=typeof t.aggregatedOutput=="string"?t.aggregatedOutput:"";s&&this.emitEvent({state:"tool-call",name:"command_execution",args:{command:s}}),this.emitEvent({state:"tool-result",name:"command_execution",result:[r,t.exitCode!=null?`
|
|
6
|
+
(exit ${t.exitCode})`:""].join("").trim()});break}case"mcpToolCall":case"dynamicToolCall":{const s=typeof t.tool=="string"?t.tool:t.type;if(f(s))return;const r=typeof t.arguments=="object"&&t.arguments!==null?t.arguments:{};this.emitEvent({state:"tool-call",name:s,args:r}),this.emitEvent({state:"tool-result",name:s,result:w(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,s=typeof t?.last=="object"&&t.last!==null?t.last:null,r=typeof t?.total=="object"&&t.total!==null?t.total:null,i=s??r;if(!i)return;const n=typeof i.inputTokens=="number"?i.inputTokens:void 0,o=typeof i.outputTokens=="number"?i.outputTokens:void 0;(n!=null||o!=null)&&(this.pendingUsage={inputTokens:n??0,outputTokens:o??0})}handleCodexItemLine(e){const t=e.item;if(t?.type&&!M(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 s=[t.aggregated_output??"",t.exit_code!=null?`
|
|
7
|
+
(exit ${t.exit_code})`:""];this.emitEvent({state:"tool-result",name:"command_execution",result:s.join("").trim()||String(t.status??"")})}break}case"mcp_tool_call":{if(t.tool&&f(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 s=t.error&&typeof t.error=="object"?t.error.message:void 0;this.emitEvent({state:"tool-result",name:t.tool??"mcp_tool",result:s??w(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 s=t.items.map(r=>`- [${r.completed?"x":" "}] ${r.text}`).join(`
|
|
10
8
|
`);this.emitText(s,"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 r=e.usage;if(r){const i=r.input_tokens??r.prompt_tokens,n=r.output_tokens??r.completion_tokens;(i!=null||n!=null)&&(this.pendingUsage={inputTokens:i??0,outputTokens:n??0})}this.emitFinalIfOpen();return}case"turn.failed":{const r=typeof e.error=="object"&&e.error?.message?e.error.message:typeof e.error=="string"?e.error:"turn failed";this.emitErrorIfOpen(r);return}case"error":{const r=typeof e.message=="string"?e.message:"";if(r.includes("Reconnecting"))return;this.emitErrorIfOpen(r||(typeof e.error=="string"?e.error:"stream error"));return}case"thread.started":{const r=e.thread_id??e.session_id??e.id;typeof r=="string"&&r&&(this.agentSessionId=r,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 r=e.content??e.text;r&&this.emitText(r);break}case"function_call":case"tool_call":{if(this.isApprovalLikeName(e.name)){this.emitApprovalPending(e);break}if(e.name&&f(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&&f(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:"",s=typeof e.title=="string"&&e.title.trim()?e.title:"\u9700\u8981\u7528\u6237\u786E\u8BA4",r=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:s,content:r,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(){const e=this.process;if(this.appServerReady=null,this.activeTurnId=null,!e){this.rejectAllPending(new Error("codex app-server stopped"));return}this.process=null,this.clearForceCloseTimer(),e.kill("SIGTERM"),await new Promise(t=>{e.on("close",t),setTimeout(()=>{e.kill("SIGKILL"),t()},3e3)}),this.rejectAllPending(new Error("codex app-server stopped"))}rejectAllPending(e){for(const[t,s]of this.pendingRequests.entries())clearTimeout(s.timer),s.reject(e),this.pendingRequests.delete(t)}handleProcessClose(e,t){const s=T(t);if(e!==0&&e!==null){const r=s||`codex exited with code ${e}`;this.emitErrorIfOpen(r)}else z(s)?this.emitErrorIfOpen(s):this.emitFinalIfOpen();this.pendingUsage=void 0}emitFinalIfOpen(){this.terminalState==="open"&&(this.terminalState="final",this.emitEvent({state:"final",usage:this.pendingUsage,agentSessionId:this.agentSessionId??void 0}),this.killProcess().catch(()=>{}))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.emitEvent({state:"error",message:b(e)}),this.scheduleProcessClose())}resetRunState(){this.pendingUsage=void 0,this.terminalState="open",this.deltaItemIds.clear(),this.lastTransientStatus="",this.clearForceCloseTimer(),this.resetTextState()}emitTransientStatus(e){const t=F(e);!t||t===this.lastTransientStatus||this.terminalState!=="open"||(this.lastTransientStatus=t,this.emitEvent({state:"delta",text:`${t}
|
|
11
9
|
`,thinking:!0}))}resetTextState(){this.hasEmittedText=!1,this.textTail=""}emitText(e,t="delta"){if(!e)return;let s=H(e);s&&(t==="block"&&this.hasEmittedText&&!s.startsWith(`
|
|
12
10
|
`)&&(this.textTail.endsWith(`
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
1
2
|
import type { ChatAttachmentMeta, ExternalChannelSessionStatus, SessionRunPhase } from '@shennian/wire';
|
|
2
3
|
import { AgentAdapter, type AgentSendContext } from './adapter.js';
|
|
3
4
|
export declare function normalizeWorkBuddyModelId(modelId?: string | null): string | undefined;
|
|
4
5
|
export declare function normalizeWorkBuddyReasoningEffort(value?: string | null): string | undefined;
|
|
5
6
|
export declare function workBuddySystemPromptArgs(systemPrompt: string | null, platform: NodeJS.Platform, promptFile?: string): string[];
|
|
7
|
+
export declare function terminateWorkBuddyProcess(proc: Pick<ChildProcess, 'pid' | 'kill'>, platform?: NodeJS.Platform, terminateWindowsTree?: (pid: number) => boolean): void;
|
|
6
8
|
export declare class WorkBuddyAdapter extends AgentAdapter {
|
|
7
9
|
readonly type: "workbuddy";
|
|
8
10
|
private process;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createInterface as
|
|
1
|
+
import{spawnSync as w}from"node:child_process";import{createInterface as k}from"node:readline";import{randomUUID as I}from"node:crypto";import S from"node:path";import{AgentAdapter as x,registerAgent as _}from"./adapter.js";import{resolveBuiltinCommand as E,spawnAgentCommand as B}from"./command-spec.js";import{buildAgentProcessEnv as P}from"../agent-env.js";import{buildPlatformInstructions as T,ensurePlatformInstructionsFile as W}from"./platform-instructions.js";import{ensureWorkBuddyRoomPluginBundle as C}from"../integrations/workbuddy-plugin-bundle.js";import{roomMcpHostToolName as A}from"../integrations/room-plugin-shared.js";import{resolveWindowsSystemRoot as D}from"../windows-shell.js";const O=new Set(["minimal","low","medium","high","xhigh","max"]);function R(n){const t=n?.trim();return t&&t!=="auto"?t:void 0}function q(n){const t=n?.trim();if(t){if(O.has(t))return t;throw new Error(`Unsupported WorkBuddy reasoning effort "${t}". Supported values: minimal, low, medium, high, xhigh, max.`)}}function $(n,t,e){if(!n)return[];if(t==="win32"){if(!e)throw new Error("WorkBuddy system prompt file is required on Windows.");return["--system-prompt-file",e]}return["--append-system-prompt",n]}function c(n,t=process.platform,e=F){t==="win32"&&n.pid&&e(n.pid)||n.kill("SIGTERM")}function F(n){const t=S.win32.join(D(),"System32","taskkill.exe");return w(t,["/pid",String(n),"/T","/F"],{stdio:"ignore",windowsHide:!0,timeout:5e3}).status===0}class N extends x{type="workbuddy";process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="open";hasEmittedText=!1;runPhase=null;externalChannel=null;shennianSessionId=null;extraEnv={};configure(t){this.shennianSessionId=t.sessionId??null,this.externalChannel=t.externalChannel??null,this.extraEnv=t.env??{}}async start(t,e,s){this.workDir=e,this.seq=0,s&&(this.agentSessionId=s)}async send(t,e,s,o,a,i){await this.killProcess(),this.runId=I(),this.resetRunState();const u=i?.source==="room_activation",r=["-p",t,"--output-format","stream-json","--verbose","--permission-mode",u?"dontAsk":"auto","--setting-sources","user,project,local"],h=R(e);h&&r.push("--model",h);const m=C(),l=A("workbuddy");u?r.push("--allowedTools",l,"--tools",`NoDefer(${l})`,"--mcp-config",m.mcpFile,"--strict-mcp-config","--disallowedTools","mcp__shennian_room__shennian_agent","--max-turns","2"):r.push("--allowedTools",l,"--tools",`*,NoDefer(${l})`),r.push("--plugin-dir",m.pluginRoot);const p=this.shennianSessionId?.startsWith("room-managed-")&&!this.agentSessionId&&!u?null:T(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,a);if(p){const y=process.platform==="win32"?W(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,a):void 0;r.push(...$(p,process.platform,y))}const f=q(s);f&&r.push("--effort",f),this.agentSessionId&&r.push("--resume",this.agentSessionId),this.spawnAndParse(r)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.resetRunState()}async stop(){await this.killProcess()}async getStatus(){return{active:this.process!=null&&this.terminalState==="open",runId:this.runId||null,runPhase:this.runPhase,canStop:this.process!=null&&this.terminalState==="open"}}spawnAndParse(t){const e=E("workbuddy");if(!e){this.emit("error",new Error("WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry."));return}const s=B(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:P(this.extraEnv)});this.process=s,k({input:s.stdout}).on("line",i=>{if(i.trim())try{this.handleStreamEvent(JSON.parse(i))}catch{}});let a="";s.stderr?.on("data",i=>{a=`${a}${i.toString()}`.slice(-8e3)}),s.on("close",i=>{this.process===s&&(this.process=null,this.runPhase=null,this.handleProcessClose(i,a))}),s.on("error",i=>{if(this.process!==s)return;this.process=null,this.runPhase=null;const u=i.code==="ENOENT";this.emitErrorIfOpen({state:"error",message:u?"WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.":d(i.message)})})}handleStreamEvent(t){if(t.type==="system"&&t.subtype==="init"&&t.session_id){this.agentSessionId=t.session_id,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:t.session_id});return}if(t.type==="control_request"&&t.request?.subtype==="can_use_tool"){this.runPhase="waiting_approval",this.emitEvent({state:"approval-pending",name:t.request.tool_name,args:t.request.input,approval:{title:`WorkBuddy \u8BF7\u6C42\u4F7F\u7528 ${t.request.tool_name||"\u5DE5\u5177"}`,source:"workbuddy",actionHint:"\u8BF7\u5728 WorkBuddy \u6216\u795E\u5FF5\u4E2D\u786E\u8BA4\u540E\u7EE7\u7EED\u3002"}});return}if(t.type==="assistant"){const s=t.message?.content;if(s?.length)for(const o of s)this.handleContentBlock(o);else t.text&&this.emitText(t.text,!!t.thinking);return}if(t.type!=="result")return;if(t.subtype==="tool_result"){this.runPhase="tool_running",this.emitEvent({state:"tool-result",name:t.name,result:g(t.content)});return}if(t.subtype==="error"||t.error){this.emitErrorIfOpen({state:"error",message:d(t.error||t.result||"WorkBuddy failed.")});return}!this.hasEmittedText&&t.result&&this.emitText(t.result,!1),t.session_id&&(this.agentSessionId=t.session_id);const e=t.usage??t.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:e?{inputTokens:e.input_tokens??0,outputTokens:e.output_tokens??0}:void 0})}handleContentBlock(t){t.type==="text"&&t.text?this.emitText(t.text,!1):t.type==="thinking"&&t.thinking?this.emitText(t.thinking,!0):t.type==="tool_use"?(this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:t.name,args:t.input})):t.type==="tool_result"&&this.emitEvent({state:"tool-result",name:t.name,result:g(t.content)})}emitText(t,e){this.runPhase=e?"thinking":"streaming_text";const s=!e&&this.hasEmittedText?`
|
|
2
2
|
|
|
3
|
-
`:"";this.emitEvent({state:"delta",text:s+t,thinking:e||void 0}),e||(this.hasEmittedText=!0)}handleProcessClose(t,e){if(t!==0&&t!==null){this.emitErrorIfOpen({state:"error",message:d(e)||`WorkBuddy exited with code ${t}.`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="final",this.runPhase=null,this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="error",this.runPhase=null,this.emitEvent(t))}emitEvent(t){this.emit("agentEvent",{...t,runId:this.runId,seq:this.seq++})}resetRunState(){this.seq=0,this.terminalState="open",this.hasEmittedText=!1,this.runPhase=null}async killProcess(){const t=this.process;t&&(this.process=null,this.runPhase=null,t
|
|
3
|
+
`:"";this.emitEvent({state:"delta",text:s+t,thinking:e||void 0}),e||(this.hasEmittedText=!0)}handleProcessClose(t,e){if(t!==0&&t!==null){this.emitErrorIfOpen({state:"error",message:d(e)||`WorkBuddy exited with code ${t}.`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="final",this.runPhase=null,this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="error",this.runPhase=null,this.emitEvent(t))}emitEvent(t){this.emit("agentEvent",{...t,runId:this.runId,seq:this.seq++})}resetRunState(){this.seq=0,this.terminalState="open",this.hasEmittedText=!1,this.runPhase=null}async killProcess(){const t=this.process;t&&(this.process=null,this.runPhase=null,c(t),await new Promise(e=>{let s=!1;const o=()=>{s||(s=!0,e())};t.once("close",o),setTimeout(()=>{c(t),o()},3e3).unref?.()}))}}function g(n){if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return"[unserializable WorkBuddy tool result]"}}function d(n){return n.replace(/(Authorization\s*:\s*)Bearer\s+[A-Za-z0-9._~+/-]+/gi,"$1Bearer [redacted]").replace(/(api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|connector[-_ ]?token)(\s*[:=]\s*)[^\s,;]+/gi,"$1$2[redacted]").trim().slice(0,2e3)}_("workbuddy",()=>new N);export{N as WorkBuddyAdapter,R as normalizeWorkBuddyModelId,q as normalizeWorkBuddyReasoningEffort,d as sanitizeWorkBuddyError,c as terminateWorkBuddyProcess,$ as workBuddySystemPromptArgs};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import h from"node:crypto";import{getRegisteredAgents as f,unregisterAgent as p}from"../agents/adapter.js";import{loadConfig as w}from"../config/index.js";import{handleUpgradeStart as b,handleUpgradeStatus as v}from"../commands/upgrade.js";import{handleAgentsRefresh as R,handleModelsRefresh as y}from"./handlers/agents.js";import{handleAgentCapabilitiesList as k}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as S,handleAgentConfigGet as I,handleAgentConfigTest as A,handleAgentConfigUpsert as T}from"./handlers/agent-config.js";import{handleChatAbort as u,handleChatSend as c,sendSessionUpdateEvent as P}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as C}from"./handlers/title.js";import{cleanupPendingTransfers as F,handleFsLs as x,handleFsRead as D,handleFsRename as E,handleFsWrite as O,handleFsTransfer as Q,handleFsTransferAbort as j,handleFsTransferChunk as U,handleFsTransferFinish as z,handleFsTransferStart as $,handleFsExportMarkdownPdf as V,handleFsExportMarkdownPdfSetup as B,handleFsArchiveZip as W}from"./handlers/fs.js";import{handleRegionProbe as L,handleRegionSwitch as G,handleUpgradeSetPolicy as K}from"./handlers/control.js";import{ManagerRuntimeService as N,setManagerRuntimeService as m}from"../manager/runtime.js";import{ChatQueueManager as H}from"./queue.js";import{createAuthorizedFsRoot as X,resolveAuthorizedPath as Z,resolveSessionWorkDir as J}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/manager.js";import{registerCustomAgent as Y}from"../agents/custom.js";import{handleManagedRoomBind as q,handleManagedRoomRestart as ee}from"./handlers/room-managed.js";import{listSessionRecords as d}from"./store.js";const te=50;import{resolveSessionWorkDir as xe}from"../fs/boundary.js";class _e{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;activeRequests=0;constructor(e,t=null,s,n=null,a=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=n,this.managedRoomBinder=a,this.managerRuntime=new N({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),this.chatQueue=new H({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),m(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(i=>{console.error("[session.activity] managed probe failed",i)})},15e3),this.activityProbeTimer.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),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}}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}}async createManagedRoomSession(e){const t=h.createHash("sha256").update(e.creationKey,"utf8").digest("hex"),s=`room-managed-${t.slice(0,48)}`,n=d().find(r=>r.sessionId===s);if(n&&(n.agentType!==e.agentType||n.workDir!==e.workDir))throw Object.assign(new Error("Managed Room Session identity conflicts with local state."),{code:"managed_room_session_conflict"});if(n?.agentSessionId)return{nianSessionId:s};const a={value:null},i=new Proxy(this.client,{get:(r,l)=>{if(l==="sendRes")return g=>{a.value=g};const o=Reflect.get(r,l,r);return typeof o=="function"?o.bind(r):o}});if(await c({...this.getRuntime(),client:i},{type:"req",id:`managed-create-${t.slice(0,32)}`,method:"chat.send",params:{sessionId:s,text:e.initialMessage,agentType:e.agentType,workDir:e.workDir,modelId:e.modelId??void 0,clientMessageId:`managed-bootstrap-${t.slice(0,32)}`,waitForDispatch:!0}}),a.value?.ok!==!0)throw Object.assign(new Error(a.value?.error||"Managed Agent failed to start."),{code:"managed_agent_start_failed"});return{nianSessionId:s}}async waitForManagedNativeSession(e){const t=Date.now();for(;Date.now()-t<e.timeoutMs;){const s=this.sessions.get(e.nianSessionId);if(s&&s.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent runtime does not match the created session."),{code:"managed_room_session_conflict"});const n=d().find(i=>i.sessionId===e.nianSessionId),a=s?.agentSessionId??n?.agentSessionId;if(a)return{sourceSessionKey:a};await new Promise(i=>setTimeout(i,100))}throw Object.assign(new Error("Timed out waiting for the exact native Agent Session."),{code:"native_session_timeout"})}publishManagedRoomSession(e){const t=d().find(s=>s.sessionId===e.nianSessionId);if(!t?.agentSessionId||t.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent Session is unavailable for publication."),{code:"managed_room_session_unavailable"});if(this.client.getState()!=="connected")throw Object.assign(new Error("Shennian Relay is disconnected."),{code:"managed_room_relay_disconnected"});P(this.getRuntime(),{sessionId:t.sessionId,agentType:t.agentType,workDir:t.workDir,agentSessionId:t.agentSessionId,modelId:t.modelId??void 0})}async activateRoomSession(e){const t={value:null},s=new Proxy(this.client,{get:(n,a)=>{if(a==="sendRes")return r=>{t.value=r};const i=Reflect.get(n,a,n);return typeof i=="function"?i.bind(n):i}});return await c({...this.getRuntime(),client:s},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:t.value?.ok===!0&&t.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(s,n)=>{if(n==="sendRes")return()=>{};const a=Reflect.get(s,n,s);return typeof a=="function"?a.bind(s):a}});await u({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}async publishManagedActivitySnapshots(){for(const[e,t]of this.sessions.entries()){if(!t.currentRunId||!t.adapter.getStatus)continue;const s=await t.adapter.getStatus().catch(()=>null);if(!s?.active||!s.runPhase)continue;const n=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:n,canStop:s.canStop??!0});const a=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:a,runPhase:s.runPhase,canStop:s.canStop??!0},seq:a,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of f())t.startsWith("custom:")&&p(t);const e=w();for(const[t,s]of Object.entries(e.customAgents??{}))Y(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await c(t,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await u(t,e);break;case"session.refresh":await M(t,e);break;case"session.tool.detail":await _(t,e);break;case"session.title.set":await C(t,e);break;case"room.agent.bind-managed":await q(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ee(t,this.managedRoomBinder,e);break;case"fs.ls":await x(t,e);break;case"fs.read":await D(t,e);break;case"fs.write":await O(t,e);break;case"fs.export.markdown-pdf":await V(t,e);break;case"fs.export.markdown-pdf.setup":await B(t,e);break;case"fs.archive.zip":await W(t,e);break;case"fs.rename":await E(t,e);break;case"fs.transfer":await Q(t,e);break;case"fs.transfer.start":await $(t,e);break;case"fs.transfer.chunk":await U(t,e);break;case"fs.transfer.finish":await z(t,e);break;case"fs.transfer.abort":await j(t,e);break;case"region.probe":await L(t,e);break;case"region.switch":await G(t,e);break;case"upgrade.start":await b(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await v(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await K(t,e);break;case"agents.refresh":await R(t,e);break;case"models.refresh":await y(t,e);break;case"agent.config.get":await I(t,e);break;case"agent.config.upsert":await T(t,e);break;case"agent.config.clear":await S(t,e);break;case"agent.config.test":await A(t,e);break;case"agent.capabilities.list":await k(t,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(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<te)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(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 J(e)}resolveAuthorizedPath(e,t){return Z(e,X(t))}async cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),F(this.getRuntime()),await this.managerRuntime.stop(),m(null)}}export{_e as SessionManager,xe as resolveSessionWorkDir};
|
|
1
|
+
import h from"node:crypto";import{getRegisteredAgents as f,unregisterAgent as p}from"../agents/adapter.js";import{loadConfig as w}from"../config/index.js";import{handleUpgradeStart as b,handleUpgradeStatus as v}from"../commands/upgrade.js";import{handleAgentsRefresh as R,handleModelsRefresh as y}from"./handlers/agents.js";import{handleAgentCapabilitiesList as k}from"./handlers/agent-capabilities.js";import{handleAgentConfigClear as S,handleAgentConfigGet as I,handleAgentConfigTest as A,handleAgentConfigUpsert as T}from"./handlers/agent-config.js";import{handleChatAbort as u,handleChatSend as c,sendSessionUpdateEvent as P}from"./handlers/chat.js";import{handleSessionRefresh as M}from"./handlers/session-refresh.js";import{handleSessionToolDetail as _}from"./handlers/tool-detail.js";import{handleSessionTitleSet as C}from"./handlers/title.js";import{cleanupPendingTransfers as F,handleFsLs as x,handleFsRead as D,handleFsRename as E,handleFsWrite as O,handleFsTransfer as Q,handleFsTransferAbort as j,handleFsTransferChunk as U,handleFsTransferFinish as z,handleFsTransferStart as $,handleFsExportMarkdownPdf as V,handleFsExportMarkdownPdfSetup as B,handleFsArchiveZip as W}from"./handlers/fs.js";import{handleRegionProbe as L,handleRegionSwitch as G,handleUpgradeSetPolicy as K}from"./handlers/control.js";import{ManagerRuntimeService as N,setManagerRuntimeService as m}from"../manager/runtime.js";import{ChatQueueManager as H}from"./queue.js";import{createAuthorizedFsRoot as X,resolveAuthorizedPath as Z,resolveSessionWorkDir as J}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/manager.js";import{registerCustomAgent as Y}from"../agents/custom.js";import{handleManagedRoomBind as q,handleManagedRoomRestart as ee}from"./handlers/room-managed.js";import{listSessionRecords as d}from"./store.js";const te=50;import{resolveSessionWorkDir as xe}from"../fs/boundary.js";class _e{client;nativeFusion;cliVersion;upgradePolicyController;managedRoomBinder;sessions=new Map;processedReqIds=new Set;runTextAcc=new Map;pendingTransfers=new Map;managerRuntime;chatQueue;activityProbeTimer=null;activeRequests=0;constructor(e,t=null,s,n=null,a=null){this.client=e,this.nativeFusion=t,this.cliVersion=s,this.upgradePolicyController=n,this.managedRoomBinder=a,this.managerRuntime=new N({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),this.chatQueue=new H({getRuntime:()=>this.getRuntime(),dispatchReq:i=>this.handleReq(i)}),m(this.managerRuntime),this.managerRuntime.start(),this.reloadCustomAgents(),this.activityProbeTimer=setInterval(()=>{this.publishManagedActivitySnapshots().catch(i=>{console.error("[session.activity] managed probe failed",i)})},15e3),this.activityProbeTimer.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),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}}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}}async createManagedRoomSession(e){const t=h.createHash("sha256").update(e.creationKey,"utf8").digest("hex"),s=`room-managed-${t.slice(0,48)}`,n=d().find(r=>r.sessionId===s);if(n&&(n.agentType!==e.agentType||n.workDir!==e.workDir))throw Object.assign(new Error("Managed Room Session identity conflicts with local state."),{code:"managed_room_session_conflict"});if(n?.agentSessionId)return{nianSessionId:s};const a={value:null},i=new Proxy(this.client,{get:(r,l)=>{if(l==="sendRes")return g=>{a.value=g};const o=Reflect.get(r,l,r);return typeof o=="function"?o.bind(r):o}});if(await c({...this.getRuntime(),client:i},{type:"req",id:`managed-create-${t.slice(0,32)}`,method:"chat.send",params:{sessionId:s,text:e.initialMessage,agentType:e.agentType,workDir:e.workDir,modelId:e.modelId??void 0,clientMessageId:`managed-bootstrap-${t.slice(0,32)}`,waitForDispatch:!0}}),a.value?.ok!==!0)throw Object.assign(new Error(a.value?.error||"Managed Agent failed to start."),{code:"managed_agent_start_failed"});return{nianSessionId:s}}async waitForManagedNativeSession(e){const t=Date.now();for(;Date.now()-t<e.timeoutMs;){const s=this.sessions.get(e.nianSessionId);if(s&&s.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent runtime does not match the created session."),{code:"managed_room_session_conflict"});const n=d().find(i=>i.sessionId===e.nianSessionId),a=s?.agentSessionId??n?.agentSessionId;if(a)return{sourceSessionKey:a};await new Promise(i=>setTimeout(i,100))}throw Object.assign(new Error("Timed out waiting for the exact native Agent Session."),{code:"native_session_timeout"})}publishManagedRoomSession(e){const t=d().find(s=>s.sessionId===e.nianSessionId);if(!t?.agentSessionId||t.agentType!==e.agentType)throw Object.assign(new Error("Managed Agent Session is unavailable for publication."),{code:"managed_room_session_unavailable"});if(this.client.getState()!=="connected")throw Object.assign(new Error("Shennian Relay is disconnected."),{code:"managed_room_relay_disconnected"});P(this.getRuntime(),{sessionId:t.sessionId,agentType:t.agentType,workDir:t.workDir,agentSessionId:t.agentSessionId,modelId:t.modelId??void 0})}async activateRoomSession(e){const t={value:null},s=new Proxy(this.client,{get:(n,a)=>{if(a==="sendRes")return r=>{t.value=r};const i=Reflect.get(n,a,n);return typeof i=="function"?i.bind(n):i}});return await c({...this.getRuntime(),client:s},{type:"req",id:e.clientMessageId,method:"chat.send",params:{...e,waitForDispatch:!0,suppressExternalOwnerEcho:!0}},{source:"room_activation"}),{externalOwnerQueued:t.value?.ok===!0&&t.value.payload?.deliveryMode==="external_owner_queue"}}async abortRoomSession(e){const t=new Proxy(this.client,{get:(s,n)=>{if(n==="sendRes")return()=>{};const a=Reflect.get(s,n,s);return typeof a=="function"?a.bind(s):a}});await u({...this.getRuntime(),client:t},{type:"req",id:`room-activation-abort-${h.randomUUID()}`,method:"chat.abort",params:{sessionId:e.sessionId}})}publishSessionActivity(e,t){this.client.sendEvent({type:"event",event:"session.activity",payload:{sessionId:e,activity:t}})}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 n=new Date().toISOString();this.publishSessionActivity(e,{sessionId:e,runId:s.runId||t.currentRunId,runPhase:s.runPhase,startedAt:new Date(t.lastActiveAt).toISOString(),updatedAt:n,canStop:s.canStop??!0});const a=t.heartbeatSeq++;this.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:s.runId||t.currentRunId,seq:a,runPhase:s.runPhase,canStop:s.canStop??!0},seq:a,id:`agent-status-${s.runId||t.currentRunId}-${Date.now()}`})}}reloadCustomAgents(){for(const t of f())t.startsWith("custom:")&&p(t);const e=w();for(const[t,s]of Object.entries(e.customAgents??{}))Y(t,s)}async handleReq(e){const t=this.getRuntime();this.activeRequests++;try{switch(e.method){case"chat.send":await c(t,e);break;case"chat.enqueue":await this.chatQueue.handleEnqueue(e);break;case"chat.queue.get":await this.chatQueue.handleGet(e);break;case"chat.queue.edit":await this.chatQueue.handleEdit(e);break;case"chat.queue.delete":await this.chatQueue.handleDelete(e);break;case"chat.abort":await u(t,e);break;case"session.refresh":await M(t,e);break;case"session.tool.detail":await _(t,e);break;case"session.title.set":await C(t,e);break;case"room.agent.bind-managed":await q(t,this.managedRoomBinder,e);break;case"room.agent.restart-managed":await ee(t,this.managedRoomBinder,e);break;case"fs.ls":await x(t,e);break;case"fs.read":await D(t,e);break;case"fs.write":await O(t,e);break;case"fs.export.markdown-pdf":await V(t,e);break;case"fs.export.markdown-pdf.setup":await B(t,e);break;case"fs.archive.zip":await W(t,e);break;case"fs.rename":await E(t,e);break;case"fs.transfer":await Q(t,e);break;case"fs.transfer.start":await $(t,e);break;case"fs.transfer.chunk":await U(t,e);break;case"fs.transfer.finish":await z(t,e);break;case"fs.transfer.abort":await j(t,e);break;case"region.probe":await L(t,e);break;case"region.switch":await G(t,e);break;case"upgrade.start":await b(this.client,e.id,e.params.version,{currentVersion:this.cliVersion,confirmedMajor:e.params.confirmedMajor===!0});break;case"upgrade.status":await v(this.client,e.id,{currentVersion:this.cliVersion});break;case"upgrade.set-policy":await K(t,e);break;case"agents.refresh":await R(t,e);break;case"models.refresh":await y(t,e);break;case"agent.config.get":await I(t,e);break;case"agent.config.upsert":await T(t,e);break;case"agent.config.clear":await S(t,e);break;case"agent.config.test":await A(t,e);break;case"agent.capabilities.list":await k(t,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(s){this.client.sendRes({type:"res",id:e.id,ok:!1,error:s instanceof Error?s.message:String(s)})}finally{this.activeRequests--}}evictIdleSessions(){if(this.sessions.size<te)return;let e=null;for(const[t,s]of this.sessions)(!e||s.lastActiveAt<e.session.lastActiveAt)&&(e={key:t,session:s});e&&(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 J(e)}resolveAuthorizedPath(e,t){return Z(e,X(t))}async cleanup(){this.activityProbeTimer&&(clearInterval(this.activityProbeTimer),this.activityProbeTimer=null);for(const[,e]of this.sessions)e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),await e.adapter.stop().catch(()=>{});this.sessions.clear(),this.runTextAcc.clear(),F(this.getRuntime()),await this.managerRuntime.stop(),m(null)}}export{_e as SessionManager,xe as resolveSessionWorkDir};
|