shennian 0.3.17 → 0.3.19
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 +10 -10
- package/dist/src/agents/workbuddy.js +1 -1
- package/dist/src/commands/daemon.d.ts +2 -1
- package/dist/src/commands/daemon.js +8 -8
- package/dist/src/room/invite-bootstrap.d.ts +14 -0
- package/dist/src/room/invite-bootstrap.js +1 -1
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/queue.js +1 -1
- package/package.json +1 -1
|
@@ -136,8 +136,8 @@
|
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"file": "src/agents/workbuddy.js",
|
|
139
|
-
"beforeBytes":
|
|
140
|
-
"afterBytes":
|
|
139
|
+
"beforeBytes": 11913,
|
|
140
|
+
"afterBytes": 6466
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"file": "src/commands/agent.js",
|
|
@@ -161,8 +161,8 @@
|
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"file": "src/commands/daemon.js",
|
|
164
|
-
"beforeBytes":
|
|
165
|
-
"afterBytes":
|
|
164
|
+
"beforeBytes": 38631,
|
|
165
|
+
"afterBytes": 18928
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"file": "src/commands/integration.js",
|
|
@@ -446,8 +446,8 @@
|
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"file": "src/room/invite-bootstrap.js",
|
|
449
|
-
"beforeBytes":
|
|
450
|
-
"afterBytes":
|
|
449
|
+
"beforeBytes": 6136,
|
|
450
|
+
"afterBytes": 3363
|
|
451
451
|
},
|
|
452
452
|
{
|
|
453
453
|
"file": "src/room/invocation-token-ipc.js",
|
|
@@ -566,8 +566,8 @@
|
|
|
566
566
|
},
|
|
567
567
|
{
|
|
568
568
|
"file": "src/session/handlers/chat.js",
|
|
569
|
-
"beforeBytes":
|
|
570
|
-
"afterBytes":
|
|
569
|
+
"beforeBytes": 29212,
|
|
570
|
+
"afterBytes": 12287
|
|
571
571
|
},
|
|
572
572
|
{
|
|
573
573
|
"file": "src/session/handlers/control.js",
|
|
@@ -611,8 +611,8 @@
|
|
|
611
611
|
},
|
|
612
612
|
{
|
|
613
613
|
"file": "src/session/queue.js",
|
|
614
|
-
"beforeBytes":
|
|
615
|
-
"afterBytes":
|
|
614
|
+
"beforeBytes": 14491,
|
|
615
|
+
"afterBytes": 6801
|
|
616
616
|
},
|
|
617
617
|
{
|
|
618
618
|
"file": "src/session/remote-attachments.js",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{createInterface as g}from"node:readline";import{randomUUID as y}from"node:crypto";import{AgentAdapter as I,registerAgent as k}from"./adapter.js";import{resolveBuiltinCommand as S,spawnAgentCommand as _}from"./command-spec.js";import{buildAgentProcessEnv as x}from"../agent-env.js";import{buildPlatformInstructions as w}from"./platform-instructions.js";import{ensureWorkBuddyRoomPluginBundle as E}from"../integrations/workbuddy-plugin-bundle.js";import{roomMcpHostToolName as P}from"../integrations/room-plugin-shared.js";const B=new Set(["minimal","low","medium","high","xhigh","max"]);function T(i){const t=i?.trim();return t&&t!=="auto"?t:void 0}function W(i){const t=i?.trim();if(t){if(B.has(t))return t;throw new Error(`Unsupported WorkBuddy reasoning effort "${t}". Supported values: minimal, low, medium, high, xhigh, max.`)}}class C extends I{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,s,e){this.workDir=s,this.seq=0,e&&(this.agentSessionId=e)}async send(t,s,e,o,a,n){await this.killProcess(),this.runId=y(),this.resetRunState();const u=n?.source==="room_activation",r=["-p",t,"--output-format","stream-json","--verbose","--permission-mode",u?"dontAsk":"auto","--setting-sources","user,project,local"],h=T(s);h&&r.push("--model",h);const m=E(),l=P("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=w(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,a);p&&r.push("--append-system-prompt",p);const f=W(e);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 s=S("workbuddy");if(!s){this.emit("error",new Error("WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry."));return}const e=_(s,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:x(this.extraEnv)});this.process=e,g({input:e.stdout}).on("line",n=>{if(n.trim())try{this.handleStreamEvent(JSON.parse(n))}catch{}});let a="";e.stderr?.on("data",n=>{a=`${a}${n.toString()}`.slice(-8e3)}),e.on("close",n=>{this.process===e&&(this.process=null,this.runPhase=null,this.handleProcessClose(n,a))}),e.on("error",n=>{if(this.process!==e)return;this.process=null,this.runPhase=null;const u=n.code==="ENOENT";this.emitErrorIfOpen({state:"error",message:u?"WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.":d(n.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 e=t.message?.content;if(e?.length)for(const o of e)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:c(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 s=t.usage??t.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:s?{inputTokens:s.input_tokens??0,outputTokens:s.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:c(t.content)})}emitText(t,s){this.runPhase=s?"thinking":"streaming_text";const e=!s&&this.hasEmittedText?`
|
|
2
2
|
|
|
3
|
-
`:"";this.emitEvent({state:"delta",text:e+t,thinking:s||void 0}),s||(this.hasEmittedText=!0)}handleProcessClose(t,s){if(t!==0&&t!==null){this.emitErrorIfOpen({state:"error",message:d(s)||`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.kill("SIGTERM"),await new Promise(s=>{let e=!1;const o=()=>{e||(e=!0,s())};t.once("close",o),setTimeout(()=>{t.kill("SIGKILL"),o()},3e3).unref?.()}))}}function c(i){if(typeof i=="string")return i;try{return JSON.stringify(i)}catch{return"[unserializable WorkBuddy tool result]"}}function d(i){return i.replace(/(Authorization\s*:\s*)Bearer\s+[A-Za-z0-9._
|
|
3
|
+
`:"";this.emitEvent({state:"delta",text:e+t,thinking:s||void 0}),s||(this.hasEmittedText=!0)}handleProcessClose(t,s){if(t!==0&&t!==null){this.emitErrorIfOpen({state:"error",message:d(s)||`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.kill("SIGTERM"),await new Promise(s=>{let e=!1;const o=()=>{e||(e=!0,s())};t.once("close",o),setTimeout(()=>{t.kill("SIGKILL"),o()},3e3).unref?.()}))}}function c(i){if(typeof i=="string")return i;try{return JSON.stringify(i)}catch{return"[unserializable WorkBuddy tool result]"}}function d(i){return i.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)}k("workbuddy",()=>new C);export{C as WorkBuddyAdapter,T as normalizeWorkBuddyModelId,W as normalizeWorkBuddyReasoningEffort,d as sanitizeWorkBuddyError};
|
|
@@ -53,7 +53,8 @@ export declare function writeDaemonLauncher(pid: number, launcher?: DaemonLaunch
|
|
|
53
53
|
export declare function clearDaemonLauncher(instanceId?: string): void;
|
|
54
54
|
export declare function recordStartedDaemon(childPid: number | undefined): void;
|
|
55
55
|
export declare function findRunningDaemonProcessIds(excludePid?: number): number[];
|
|
56
|
-
export declare function isIsolatedDaemonE2eInstance(env?: NodeJS.ProcessEnv, shennianDir?: string, tempDir?: string): boolean;
|
|
56
|
+
export declare function isIsolatedDaemonE2eInstance(env?: NodeJS.ProcessEnv, shennianDir?: string, tempDir?: string, defaultDir?: string): boolean;
|
|
57
|
+
export declare function shouldDiscoverGlobalDaemonProcesses(env?: NodeJS.ProcessEnv, shennianDir?: string): boolean;
|
|
57
58
|
export declare function getDaemonStatus(opts?: {
|
|
58
59
|
cleanupStale?: boolean;
|
|
59
60
|
}): DaemonStatus;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
`)}function X(e){const n=e?.trim();if(!n)return;const t=
|
|
3
|
-
<string>${N(
|
|
4
|
-
`),r=[n.command,...n.args].map(
|
|
1
|
+
import a from"chalk";import o from"node:fs";import s from"node:path";import u from"node:os";import{execSync as c,spawn as ue}from"node:child_process";import{randomUUID as de}from"node:crypto";import{fileURLToPath as le}from"node:url";import{getShennianDir as pe,loadConfig as U,resolveShennianPath as h,saveConfig as me}from"../config/index.js";import{buildAugmentedPath as fe}from"../env-path.js";import{buildWindowsLauncherCommand as Se,buildWindowsScheduledTaskCommands as he,buildWindowsScheduledTaskXml as ge,buildWindowsStartupVbs as we,escapeXml as N,isWindowsCmdScript as ye,quoteCmdArg as j,quoteSystemdArg as ve,suspendWindowsScheduledTask as Ee}from"./daemon-windows.js";import{waitForDaemonReadyStatus as b}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Pe,isShennianRunServiceCommand as Ie}from"./daemon-process.js";import{getCurrentVersion as ke}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as En,buildWindowsScheduledTaskCommands as Pn,buildWindowsScheduledTaskSuspendCommands as In,buildWindowsScheduledTaskXml as kn,buildWindowsStartupVbs as An,suspendWindowsScheduledTask as Dn}from"./daemon-windows.js";import{isShennianRunServiceCommand as bn}from"./daemon-process.js";const Ae=s.dirname(le(import.meta.url)),f=pe(),g=h("daemon.pid"),d=h("daemon.log"),R=h("remote-access.disabled"),k=h("daemon-launcher.json"),W=s.resolve(Ae,"../../bin/shennian.js"),F=process.execPath,B=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function De(e){return B.has(e)}function l(){const e=u.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function T(e){const n=e.replace(/\\/g,"/").toLowerCase(),t=u.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(t.endsWith("/")?t:`${t}/`)}function Ne(e){return o.existsSync(e.scriptPath)&&!T(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!T(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function be(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const r=JSON.parse(n),i=Number(r.pid);return!Number.isInteger(i)||i<=0?null:{pid:i,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(n,10);return isNaN(t)?null:{pid:t}}function A(){try{return be(o.readFileSync(g,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function V(e,n=3e3){const t=Date.now();for(;Date.now()-t<n;){if(!v(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!v(e)}function Re(){return o.existsSync(R)}function Te(){try{o.unlinkSync(R)}catch{}}function J(){return"global-cli"}function Le(){return`${process.pid}-${Date.now()}-${de()}`}function Oe(e,n,t={}){o.mkdirSync(f,{recursive:!0}),o.writeFileSync(g,JSON.stringify({pid:e,instanceId:n,...t.version?{version:t.version}:{},launcher:t.launcher??J(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function gn(e,n){const t=A();if(!(t?.pid!==e||t.instanceId!==n))try{o.unlinkSync(g)}catch{}}function G(e,n=J(),t){o.mkdirSync(f,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function xe(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function He(e){e&&G(e)}function _e(e,n){if(!e||!n)return"unknown";try{const t=JSON.parse(o.readFileSync(k,"utf-8"));if(t.pid!==e)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return L(e)}function L(e){if(l()==="win32")return"unknown";try{const n=c(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function K(e=process.pid){if(l()==="win32")return Pe(e);try{return c("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const i=Number(r[1]),m=r[2];return!Number.isInteger(i)||i===e?null:Ie(m)?i:null}).filter(t=>typeof t=="number")}catch{return[]}}function $e(e=process.env,n=f,t=u.tmpdir(),r=s.join(u.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||s.resolve(n)===s.resolve(r))return!1;const i=s.relative(s.resolve(t),s.resolve(n));return i!==""&&i!==".."&&!i.startsWith(`..${s.sep}`)&&!s.isAbsolute(i)||s.resolve(n)===s.resolve(e.SHENNIAN_HOME)}function Ce(e=process.env,n=f){return!$e(e,n)}function S(e={}){let n=A(),t=n?.pid??null,r=t!==null&&v(t),i=t!==null&&!r,m=n?.adopted===!0;const y=U();if(i&&e.cleanupStale){try{o.unlinkSync(g)}catch{}n=null,t=null,r=!1,i=!1}if(!r&&Ce()){const w=K()[0];if(w&&(t=w,r=!0,i=!1,m=!0,e.cleanupStale)){const M=Le();Oe(w,M,{launcher:L(w),adopted:!0}),G(w,L(w)),n={pid:w,instanceId:M,adopted:!0}}}return{running:r,pid:t,stale:i,remoteAccessDisabled:Re(),launcher:_e(t,r),platform:l(),shennianDir:f,pidFile:g,logFile:d,...n?.instanceId?{instanceId:n.instanceId}:{},...m?{adopted:!0}:{},...y.machineId?{machineId:y.machineId}:{},paired:!!(y.machineToken&&y.machineId),...y.serverUrl?{serverUrl:y.serverUrl}:{}}}function E(e){process.stdout.write(`${JSON.stringify(e,null,2)}
|
|
2
|
+
`)}function X(e){const n=e?.trim();if(!n)return;const t=U();t.serverUrl=n,me(t)}function q(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const O="com.shennian.agent",p=s.join(u.homedir(),"Library/LaunchAgents",`${O}.plist`),Me=s.join(u.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),x=h("autostart.cmd"),H=h("autostart.vbs"),_=h("autostart.xml"),Ue=s.join(Me,"ShennianAgent.vbs");function Y(e){const n=l()==="win32"?`where ${e}`:`command -v ${e}`;try{return c(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(i=>i.trim()).find(Boolean)??null}catch{return null}}function je(){const e=l()==="win32"?"npx.cmd":"npx",n=s.join(s.dirname(F),e);return o.existsSync(n)?n:Y("npx")}function P(){return Ne({nodeExec:F,scriptPath:W,shennianCommandPath:Y("shennian"),npxPath:je()})}function z(){try{c(`schtasks /delete /tn "${I}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function Q(){try{o.unlinkSync(Ue)}catch{}}function We(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||u.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function Fe(e){if(l()==="win32"&&ye(e.command)){const n=[e.command,...e.args].map(j).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Be(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ve(){const e=P();o.writeFileSync(x,Se(e,d)),o.writeFileSync(H,we(x)),Q(),z();const n=ge({userId:We(),command:s.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[j(H)]});o.writeFileSync(_,n,"utf8");const t=he({taskName:I,xmlPath:_});try{return c(t.create,{stdio:"pipe",windowsHide:!0}),c(t.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Je(){Ee(I,e=>{c(e,{stdio:"pipe",windowsHide:!0})})}function Z(){const e={};for(const n of B)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=u.homedir(),e.PATH=fe({pathValue:e.PATH,env:e}),e.USER??=u.userInfo().username,l()==="win32"){const n=process.env.TEMP||process.env.TMP||u.tmpdir(),t=u.homedir();e.USERPROFILE??=t;const r=s.win32.parse(t);e.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=t.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||u.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ge(){const e=Z(),n=P(),t=Object.entries(e).map(([i,m])=>` <key>${N(i)}</key>
|
|
3
|
+
<string>${N(m)}</string>`).join(`
|
|
4
|
+
`),r=[n.command,...n.args].map(i=>` <string>${N(i)}</string>`).join(`
|
|
5
5
|
`);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
6
6
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
7
7
|
<plist version="1.0">
|
|
@@ -27,8 +27,8 @@ ${t}
|
|
|
27
27
|
<key>StandardErrorPath</key>
|
|
28
28
|
<string>${d}</string>
|
|
29
29
|
</dict>
|
|
30
|
-
</plist>`}const D=
|
|
31
|
-
`),r=[n.command,...n.args].map(
|
|
30
|
+
</plist>`}const D=s.join(u.homedir(),".config/systemd/user/shennian.service");function Ke(){const e=Z(),n=P(),t=Object.entries(e).map(([i,m])=>`Environment=${i}=${m}`).join(`
|
|
31
|
+
`),r=[n.command,...n.args].map(ve).join(" ");return`[Unit]
|
|
32
32
|
Description=Shennian Agent Daemon
|
|
33
33
|
After=network.target
|
|
34
34
|
|
|
@@ -41,6 +41,6 @@ StandardOutput=append:${d}
|
|
|
41
41
|
StandardError=append:${d}
|
|
42
42
|
|
|
43
43
|
[Install]
|
|
44
|
-
WantedBy=default.target`}const I="ShennianAgent";function
|
|
44
|
+
WantedBy=default.target`}const I="ShennianAgent";function Xe(e){c(e,{stdio:"pipe",windowsHide:!0})}function qe(){const e=process.getuid?.();if(typeof e=="number")return e;const n=u.userInfo();return typeof n.uid=="number"?n.uid:n.username}function ee(e=qe()){return`gui/${e}/${O}`}function Ye(e=Xe){try{return e(`launchctl unload "${p}" 2>/dev/null; launchctl load -w "${p}"`),e(`launchctl kickstart -k "${ee()}"`),!0}catch{try{return e(`launchctl load -w "${p}"`),e(`launchctl kickstart -k "${ee()}"`),!0}catch{return!1}}}function ze(){o.mkdirSync(f,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&De(n)&&(e[n]=t);o.writeFileSync(h("env.json"),JSON.stringify(e,null,2))}function ne(e={}){o.mkdirSync(f,{recursive:!0});const n=S({cleanupStale:!0});if(n.pid!==null&&n.running&&!te(n)){e.quiet||console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}re(n);const t=o.openSync(d,"a"),r=Fe(P()),i=ue(r.command,r.args,Be(r,t));i.unref(),o.closeSync(t),He(i.pid),e.quiet||(console.log(a.green(`\u2713 Background service started (PID ${i.pid})`)),console.log(a.gray(` Logs: ${d}`)))}function te(e,n=A(),t=ke()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==t}function re(e=S({cleanupStale:!0})){if(!(!te(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),V(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),V(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(g)}catch{}}catch{}}function oe(){o.mkdirSync(f,{recursive:!0});const e=l();switch(P().mode==="direct"&&T(W)&&console.warn(a.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),e){case"darwin":{const t=s.dirname(p);return o.mkdirSync(t,{recursive:!0}),o.writeFileSync(p,Ge()),Ye()}case"linux":{const t=s.dirname(D);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(D,Ke());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${u.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return c("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ve()}}function Qe(){const n=S({cleanupStale:!0}).pid;if(n===null)return{};if(!v(n)){try{o.unlinkSync(g)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function ie(e=5e3){const n=Qe();if(!n.stoppedPid)return n;if(!await C(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await C(n.stoppedPid,2e3)}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{o.unlinkSync(g)}catch{}xe();for(const r of K())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await C(r,2e3)}catch{}return n}function Ze(e={}){X(q(e.api)),Te(),re(),oe()||ne({quiet:e.json}),e.json?E(b(()=>S({cleanupStale:!0}))):console.log(a.green("\u2713 Background service started"))}async function en(e={}){switch(o.mkdirSync(f,{recursive:!0}),o.writeFileSync(R,new Date().toISOString()),l()){case"darwin":{if(o.existsSync(p))try{c(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}break}case"win32":{try{c(`schtasks /change /tn "${I}" /disable`,{stdio:"pipe",windowsHide:!0})}catch{}try{c(`schtasks /end /tn "${I}"`,{stdio:"pipe",windowsHide:!0})}catch{}break}}const t=await ie(),r=S({cleanupStale:!0});if(e.json){E(t.error?{...r,error:t.error}:r);return}t.error?console.error(a.red(`\u2717 Failed to stop: ${t.error}`)):console.log(a.green("\u2713 Background service stopped"))}function se(e){ze(),Ze(e)}async function $(e={}){await en(e)}async function C(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!v(e))return!0;await new Promise(r=>setTimeout(r,100))}return!v(e)}async function nn(e={}){X(q(e.api));const n=l();n==="win32"&&Je();const t=await ie();if(t.error){e.json?E({...S(),error:t.error}):console.error(a.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(a.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(a.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(a.gray("\u25CF Background service not running, starting it now")),(n==="linux"||n==="win32")&&oe()){e.json&&E(b(()=>S({cleanupStale:!0})));return}ne({quiet:e.json}),e.json&&E(b(()=>S({cleanupStale:!0})))}function ae(e={}){const n=S({cleanupStale:!0});if(e.json){E(n);return}if(n.pid===null){console.log(a.gray("\u25CF Background service not running"));return}n.running?(console.log(a.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(a.gray(` Logs: ${d}`))):console.log(a.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function ce(e){if(!o.existsSync(d)){console.log(a.gray("No logs yet"));return}try{const n=c(u.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${d}"`:`tail -n ${e.lines} "${d}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(d,"utf-8").split(`
|
|
45
45
|
`).slice(-e.lines);console.log(n.join(`
|
|
46
|
-
`))}}async function
|
|
46
|
+
`))}}async function tn(){switch(await $(),l()){case"darwin":{if(o.existsSync(p)){try{c(`launchctl unload -w "${p}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(p),console.log(a.green("\u2713 Auto-start uninstalled (launchd)"))}break}case"linux":{try{c("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(D)){o.unlinkSync(D);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(a.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{z();try{o.unlinkSync(x)}catch{}try{o.unlinkSync(H)}catch{}try{o.unlinkSync(_)}catch{}Q(),console.log(a.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function wn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>se(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>$(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>ae(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>ce({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,i)=>{i||console.error(a.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),se(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await $(r)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),nn(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),ae(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),ce({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(tn)}export{Be as buildDaemonSpawnOptions,En as buildWindowsLauncherCommand,Pn as buildWindowsScheduledTaskCommands,In as buildWindowsScheduledTaskSuspendCommands,kn as buildWindowsScheduledTaskXml,An as buildWindowsStartupVbs,Z as captureEnvForService,xe as clearDaemonLauncher,gn as clearDaemonPidIfOwner,Te as clearRemoteAccessDisabled,Le as createDaemonInstanceId,K as findRunningDaemonProcessIds,J as getCurrentProcessDaemonLauncher,S as getDaemonStatus,ee as getLaunchdServiceTarget,oe as installService,T as isEphemeralCliPath,$e as isIsolatedDaemonE2eInstance,Re as isRemoteAccessDisabled,De as isSafeSnapshotEnvKey,bn as isShennianRunServiceCommand,He as recordStartedDaemon,wn as registerDaemonCommand,Ye as reloadLaunchdAgent,Ne as resolveServiceLaunchSpec,ze as saveEnvSnapshot,Ce as shouldDiscoverGlobalDaemonProcesses,te as shouldReplaceRunningDaemon,ne as startDaemonProcess,Dn as suspendWindowsScheduledTask,G as writeDaemonLauncher,Oe as writeDaemonPid};
|
|
@@ -13,6 +13,20 @@ export type TrustedRoomAgentBootstrap = {
|
|
|
13
13
|
'room.status'
|
|
14
14
|
];
|
|
15
15
|
supportedRuntimes: readonly ['codex', 'claude_code', 'workbuddy'];
|
|
16
|
+
integrationCommands: {
|
|
17
|
+
codex: {
|
|
18
|
+
install: string;
|
|
19
|
+
update: string;
|
|
20
|
+
};
|
|
21
|
+
claude_code: {
|
|
22
|
+
install: string;
|
|
23
|
+
update: string;
|
|
24
|
+
};
|
|
25
|
+
workbuddy: {
|
|
26
|
+
install: string;
|
|
27
|
+
update: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
16
30
|
};
|
|
17
31
|
export type RoomClientPreparation = {
|
|
18
32
|
status: 'ready';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const p="https://shennian.net/api/room-invites/agent-bootstrap",d=["room.create","room.join","room.read","room.send","room.mode","room.status"],r=["codex","claude_code","workbuddy"],m={codex:{install:"shennian integration codex install --confirm-user-action",update:"shennian integration codex update --confirm-user-action"},claude_code:{install:"shennian integration claude-code install --confirm-user-action",update:"shennian integration claude-code update --confirm-user-action"},workbuddy:{install:"shennian integration workbuddy install --confirm-user-action",update:"shennian integration workbuddy update --confirm-user-action"}};class a extends Error{code;constructor(t,n){super(n),this.code=t,this.name="RoomInviteBootstrapError"}}async function y(o=fetch){let t;try{t=await o(p,{headers:{accept:"application/json"},redirect:"error",signal:AbortSignal.timeout(1e4)})}catch{throw new a("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.")}if(!t.ok)throw new a("room_bootstrap_unavailable","Official Shennian Room setup information is unavailable.");let n;try{n=await t.json()}catch{throw s()}return f(n)}function f(o){if(!o||typeof o!="object"||Array.isArray(o))throw s();const t=o,n=c(t.install),e=c(t.roomTool),i=e.integrationCommands===void 0?m:c(e.integrationCommands);if(t.schemaVersion!==1||t.product!=="shennian"||t.inviteKind!=="room"||n.documentationUrl!=="https://shennian.net/agent-install.md"||n.npmPackage!=="shennian"||n.distTag!=="latest"||n.installCommand!=="npm install -g shennian@latest"||n.upgradeCommand!=="shennian upgrade"||typeof n.minimumCliVersion!="string"||!/^\d+\.\d+\.\d+$/.test(n.minimumCliVersion)||e.schemaVersion!==1||!l(e.actions,d)||!l(e.supportedRuntimes,r)||!h(i))throw s();return{documentationUrl:"https://shennian.net/agent-install.md",installCommand:"npm install -g shennian@latest",upgradeCommand:"shennian upgrade",minimumCliVersion:n.minimumCliVersion,actions:d,supportedRuntimes:r,integrationCommands:m}}function h(o){return Object.keys(o).sort().join(",")!==[...r].sort().join(",")?!1:r.every(t=>{const n=o[t];if(!n||typeof n!="object"||Array.isArray(n))return!1;const e=n,i=m[t];return Object.keys(e).sort().join(",")==="install,update"&&e.install===i.install&&e.update===i.update})}function C(o,t){if(o===null)return{status:"install_required",documentationUrl:t.documentationUrl,command:t.installCommand};const n=u(o),e=u(t.minimumCliVersion);if(!n||!e)throw new a("invalid_local_cli_version","The installed Shennian CLI version could not be verified.");return g(n,e)<0?{status:"upgrade_required",documentationUrl:t.documentationUrl,command:t.upgradeCommand}:{status:"ready"}}function c(o){if(!o||typeof o!="object"||Array.isArray(o))throw s();return o}function l(o,t){return Array.isArray(o)&&o.length===t.length&&o.every((n,e)=>n===t[e])}function u(o){const t=/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$/.exec(o.trim());if(!t)return null;const n=[Number(t[1]),Number(t[2]),Number(t[3])];return n.every(Number.isSafeInteger)?n:null}function g(o,t){for(let n=0;n<3;n+=1)if(o[n]!==t[n])return o[n]-t[n];return 0}function s(){return new a("invalid_room_bootstrap","Official Shennian Room setup information failed validation.")}export{a as RoomInviteBootstrapError,y as fetchTrustedRoomAgentBootstrap,C as resolveRoomClientPreparation,f as validateTrustedRoomAgentBootstrap};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`),a=e>0?Math.min(e,80):Math.min(t.length,80);return t.slice(0,a)}function ae(t,e,a={}){if(t.state==="tool-call"||t.state==="tool-result"){const r={runId:t.runId,sourceSeq:t.seq};return{state:t.state,runId:t.runId,seq:t.seq,sessionId:e,detailRef:r,...t.name?{name:t.name}:{},...t.source?{source:t.source}:{},...t.agentSessionId?{agentSessionId:t.agentSessionId}:{},...a}}return{...t,sessionId:e,...a}}const re=3e4;function se(t){return t.state==="heartbeat"?t.runPhase??null:t.state==="tool-call"||t.state==="tool-result"?"tool_running":t.state==="approval-pending"?"waiting_approval":t.state==="delta"?t.thinking?"thinking":"streaming_text":t.state==="init"||t.state==="start"?"thinking":null}function oe(t,e){return`Agent send failed: ${e instanceof Error?e.message:String(e)}`}function L(t,e){return t!=="manager"?t:e==="claude"?"claude":"codex"}function O(t,e,a){t.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:e.sessionId,message:e,session:{id:e.sessionId,agentType:a.agentType,agentSessionId:a.agentSessionId??null,modelId:a.modelId??null,workDir:a.workDir,status:"active",externalChannel:null}}})}function le(t,e){B({sessionId:e.sessionId,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e.sessionId,agentType:e.agentType,agentSessionId:e.agentSessionId??null,modelId:e.modelId??null,workDir:e.workDir,status:"active",externalChannel:null}}})}function Re(t){if(!t||typeof t!="object")return null;const e=t;return{configured:e.configured===void 0?void 0:!!e.configured,connected:!!e.connected,type:typeof e.type=="string"?e.type:null,channelId:typeof e.channelId=="string"?e.channelId:null,name:typeof e.name=="string"?e.name:null,canReply:e.canReply===void 0||e.canReply===null?null:!!e.canReply,systemPrompt:typeof e.systemPrompt=="string"?e.systemPrompt:null,wechatRpaSource:typeof e.wechatRpaSource=="string"?e.wechatRpaSource:null,wechatRpaGroups:Array.isArray(e.wechatRpaGroups)?e.wechatRpaGroups.map(a=>({name:String(a?.name||"").trim()})).filter(a=>a.name):null,pollIntervalMs:Number.isFinite(e.pollIntervalMs)?Number(e.pollIntervalMs):null,recentLimit:Number.isFinite(e.recentLimit)?Number(e.recentLimit):null,idleSeconds:Number.isFinite(e.idleSeconds)?Number(e.idleSeconds):null,forceForeground:e.forceForeground===void 0||e.forceForeground===null?null:!!e.forceForeground,noRestore:e.noRestore===void 0||e.noRestore===null?null:!!e.noRestore,downloadAttachments:e.downloadAttachments===void 0||e.downloadAttachments===null?null:!!e.downloadAttachments,downloadAttachmentsDir:typeof e.downloadAttachmentsDir=="string"?e.downloadAttachmentsDir:null}}function Te(t){return!!(t?.configured??t?.connected)}function W(t){return Z(t)}function H(t,e,a){return{}}function j(t,e,a,r,l,d){t.configure?.({sessionId:e,externalChannel:r??null,env:{...W(a),...H(e,r,l),...d?{SHENNIAN_ROOM_REGISTRY_KEY:d}:{}}})}function xe(t,e,a){return null}function ie(t,e,a){if(t!=="claude"||!a)return e;const r=e.trim();return!!r&&r.startsWith("-")&&!r.includes("/")?V(a)??e:e}function de(t,e,a,r){let l=null;function d(n,s={}){t.client.sendAgentEvent({type:"event",event:"agent",payload:ae(n,e,s),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function i(n){if(!n.currentRunId||!n.currentRunPhase)return;const s=n.heartbeatSeq++;t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:e,runId:n.currentRunId,seq:s,runPhase:n.currentRunPhase},seq:s,id:`agent-heartbeat-${n.currentRunId}-${s}-${Date.now()}`})}function f(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{i(n)},re),n.heartbeatTimer.unref?.())}function m(n){const s=n?.pendingTextEvent;!n||!s||!s.text||(d({state:"delta",runId:s.runId,seq:s.seq,text:s.text,thinking:s.thinking||void 0}),n.pendingTextEvent=null)}r.on("agentEvent",n=>{const s=t.sessions.get(e),w=se(n),S=n.state==="final"||n.state==="error"||n.state==="aborted";s&&(s.nextEventSeq=n.seq+1,S||(s.currentRunId=n.runId),!S&&w&&(s.currentRunPhase=w,f(s)),n.agentSessionId&&(s.agentSessionId=n.agentSessionId)),t.managerRuntime?.noteAgentEvent(e,n),n.state!=="delta"&&R({level:"info",sessionId:e,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:a}}),n.state==="delta"&&n.text&&!n.thinking?T(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?T(e,{id:`agent-${n.runId}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?T(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:Q(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&T(e,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:e,role:"agent",ts:Date.now(),payload:n.message});const y=`${e}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&t.runTextAcc.set(y,(t.runTextAcc.get(y)??"")+n.text),n.agentSessionId&&n.agentSessionId!==l&&(l=n.agentSessionId,t.nativeFusion?.noteManagedSourceSession(e,L(a,n.source),n.agentSessionId),s&&B({sessionId:e,agentType:a,workDir:s.workDir,agentSessionId:n.agentSessionId}),t.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:e,agentType:a,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const I=n.text??"";if(!I)return;const E=!!n.thinking;s?.pendingTextEvent&&(s.pendingTextEvent.runId!==n.runId||s.pendingTextEvent.thinking!==E)&&m(s),s&&!s.pendingTextEvent&&(s.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:E}),s?.pendingTextEvent&&(s.pendingTextEvent.text+=I,s.pendingTextEvent.seq=n.seq);return}let b={};if(n.state==="final"){m(s);const I=t.runTextAcc.get(y)??"";I&&(b={messageSummary:ne(I)}),t.runTextAcc.delete(y)}else n.state==="error"||n.state==="aborted"?(m(s),t.runTextAcc.delete(y)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&m(s);S&&s?.currentRunId===n.runId&&(s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,g(s),t.chatQueue?.noteTerminal(e)),d(n,b)}),r.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${e} agentType=${a}: ${n.message}`),t.sessions.delete(e),t.chatQueue?.noteTerminal(e),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:e,message:n.message,runId:"",seq:0}})})}function ce(t,e){if(t.processedReqIds.add(e),t.processedReqIds.size>1e3){const a=t.processedReqIds.values().next().value;t.processedReqIds.delete(a)}}async function M(t){t.heartbeatTimer&&(clearInterval(t.heartbeatTimer),t.heartbeatTimer=null),t.adapter.removeAllListeners(),await t.adapter.stop().catch(()=>{})}function G(t,e,a,r,l,d,g){return g?t.send(e,a,r,l,d,g):d?.trim()?t.send(e,a,r,l,d):l?.length?t.send(e,a,r,l):r?t.send(e,a,r):t.send(e,a)}async function ue(t,e,a,r,l,d,g){t.evictIdleSessions();const i=K(a);if(!i)throw new Error(`Unsupported agent: ${a}`);j(i,e,a,d,g),await i.start(e,r,l);const f={adapter:i,workDir:r,agentType:a,agentSessionId:l??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:d??null,externalReplyTarget:g??null,externalChannelEnv:{...W(a),...H(e,d,g)}};return t.sessions.set(e,f),de(t,e,a,i),f}function ge(t,e){const a=t.sessions.get(e),r=a?.currentRunId;if(!a||!r)return;const l=a.nextEventSeq;t.runTextAcc.delete(`${e}:${r}`),a.pendingTextEvent=null,a.currentRunId=null,a.currentRunPhase=null,a.nextEventSeq=0,a.heartbeatTimer&&(clearInterval(a.heartbeatTimer),a.heartbeatTimer=null),t.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:e,runId:r,seq:l},seq:l,id:`agent-evt-${r}-${l}`}),t.chatQueue?.noteTerminal(e)}async function be(t,e,a){if(t.processedReqIds.has(e.id)){t.client.sendRes({type:"res",id:e.id,ok:!0});return}ce(t,e.id);const{sessionId:r,text:l,agentType:d,workDir:g,agentSessionId:i,modelId:f,systemPrompt:m,managerDefaultWorkerAgentType:n,managerDefaultWorkerModelId:s,reasoningEffort:w,clientMessageId:S,sessionListProjection:y,waitForDispatch:b,responseId:I,roomContextRegistryKey:E,suppressExternalOwnerEcho:U}=e.params,x=I||e.id;X(y);const $=null,_="";if(!r||!l){t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:x,ok:!1,error:"sessionId and text are required"});return}const u=d;u==="manager"&&t.managerRuntime?.setManagerWorkerDefaults(r,n??null,s??null);const q=(u==="claude"||u==="codex"||u==="workbuddy")&&typeof w=="string"&&w.trim()?w.trim():void 0,A=t.resolvePath(ie(u,g||z.homedir(),i)||z.homedir()),D=A,P=te(e.params.attachments),p=P?.length?await ee({text:l,attachments:P,workDir:A}):{text:l,attachments:P,localized:!1};let o=t.sessions.get(r);if(o){if(o.lastActiveAt=Date.now(),o.agentType!==u||o.workDir!==A||JSON.stringify(o.externalChannel??null)!==JSON.stringify($??null)){t.sessions.delete(r);try{await M(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}else if(i&&o.agentSessionId!==i)try{await o.adapter.resume(i),o.agentSessionId=i}catch{t.sessions.delete(r);try{await M(o)}catch{t.processedReqIds.delete(e.id)}o=void 0}}if(!o)try{o=await ue(t,r,u,A,i,$,_)}catch(c){const h=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${d}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${e.id} sessionId=${r} agentType=${d} workDir=${A} agentSessionId=${i??""}: ${h}`),t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:h,runId:"",seq:0}}),t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:x,ok:!1,error:h});return}E&&/^[a-f0-9]{64}$/.test(E)&&j(o.adapter,r,u,$,_,E);const C={id:S??`user-${e.id}`,sessionId:r,role:"user",ts:Date.now(),payload:Y(p.text,p.attachments)};R({level:"info",sessionId:r,wsEvent:"chat.send.start",metadata:{reqId:e.id,agentType:u,modelId:f,reasoningEffort:q}});const F=c=>{le(t,{sessionId:r,agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f}),o.currentRunId||(o.nextEventSeq=0),t.nativeFusion?.registerManagedSend({sessionId:r,agentType:u,sourceAgentType:L(u,f),canonicalMessageId:S??null,sourceSessionKey:o.agentSessionId??i??null,text:p.text,managedEchoPolicy:c?.deliveryMode==="external_owner_queue"&&!U?"import_following":"suppress_following"}),T(r,C),O(t,C,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f})},N=async(c,h)=>{const k=oe(u,c);console.error(`[chat.send] send failed reqId=${e.id} sessionId=${r} agentType=${d} workDir=${A} agentSessionId=${o.agentSessionId??i??""}: ${k}`),t.sessions.delete(r);try{await M(o)}catch{}if(t.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:r,message:k,runId:"",seq:0}}),!h){const v={id:`agent-error-${e.id}-${Date.now()}`,sessionId:r,role:"agent",ts:Date.now(),payload:k};T(r,v),O(t,v,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f})}h&&(t.processedReqIds.delete(e.id),t.client.sendRes({type:"res",id:x,ok:!1,error:k}))};if(b){let c;try{const h=p.attachments;c=await G(o.adapter,p.text,f,q,h,m,a),R({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}catch(h){await N(h,!0);return}F(c??void 0),t.client.sendRes({type:"res",id:x,ok:!0,...p.localized||c?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...c?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:c.deliveryMode,queuedSubmissionId:c.queuedSubmissionId}:{}}}:{}}),R({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});return}F(),t.client.sendRes({type:"res",id:x,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),R({level:"info",sessionId:r,wsEvent:"chat.send.res",metadata:{reqId:e.id,ok:!0}});const J=p.attachments;G(o.adapter,p.text,f,q,J,m,a).then(()=>{R({level:"info",sessionId:r,wsEvent:"chat.send.done",metadata:{reqId:e.id}})}).catch(c=>{N(c,!1)})}async function ke(t,e){const{sessionId:a}=e.params,r=t.sessions.get(a);if(r){try{await r.adapter.stop()}catch{}ge(t,a),t.activityPublisher?.publish(a,null)}t.client.sendRes({type:"res",id:e.id,ok:!0})}export{ke as handleChatAbort,be as handleChatSend,le as sendSessionUpdateEvent};
|
|
1
|
+
import F from"node:os";import{createAgent as Q}from"../../agents/adapter.js";import{buildApprovalPendingPayload as Y,buildUserMessagePayload as V}from"@shennian/wire";import{reportLog as x}from"../../log-reporter.js";import{lookupClaudeTranscriptCwd as G}from"../../native-fusion/parsers.js";import{appendMessage as A,recordSession as O}from"../store.js";import{mergeProjectedSessions as X}from"../projection.js";import{buildManagedAgentEnv as Z}from"../../agents/config-status.js";import{materializeRemoteChatAttachments as ee}from"../remote-attachments.js";function te(e){if(!Array.isArray(e))return;const t=e.map(s=>{if(!s||typeof s!="object")return null;const a=s,d=typeof a.path=="string"?a.path:"",l=typeof a.name=="string"?a.name:"",g=typeof a.mimeType=="string"?a.mimeType:"";if(!d||!l||!g)return null;const i=typeof a.previewData=="string"&&a.previewData.trim()?a.previewData.trim():void 0;return{path:d,name:l,mimeType:g,kind:g.startsWith("image/")?"image":"file",...i?{previewData:i}:{}}}).filter(s=>s!=null);return t.length?t:void 0}function ne(e){const t=e.indexOf(`
|
|
2
|
+
`),s=t>0?Math.min(t,80):Math.min(e.length,80);return e.slice(0,s)}function ae(e,t,s={}){if(e.state==="tool-call"||e.state==="tool-result"){const a={runId:e.runId,sourceSeq:e.seq};return{state:e.state,runId:e.runId,seq:e.seq,sessionId:t,detailRef:a,...e.name?{name:e.name}:{},...e.source?{source:e.source}:{},...e.agentSessionId?{agentSessionId:e.agentSessionId}:{},...s}}return{...e,sessionId:t,...s}}const se=3e4;function re(e){return e.state==="heartbeat"?e.runPhase??null:e.state==="tool-call"||e.state==="tool-result"?"tool_running":e.state==="approval-pending"?"waiting_approval":e.state==="delta"?e.thinking?"thinking":"streaming_text":e.state==="init"||e.state==="start"?"thinking":null}function oe(e,t){return`Agent send failed: ${t instanceof Error?t.message:String(t)}`}function W(e,t){return e!=="manager"?e:t==="claude"?"claude":"codex"}function L(e,t,s){e.client.sendEvent({type:"event",event:"session.message",payload:{sessionId:t.sessionId,message:t,session:{id:t.sessionId,agentType:s.agentType,agentSessionId:s.agentSessionId??null,modelId:s.modelId??null,workDir:s.workDir,status:"active",externalChannel:null}}})}function de(e,t){O({sessionId:t.sessionId,agentType:t.agentType,workDir:t.workDir,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t.sessionId,agentType:t.agentType,agentSessionId:t.agentSessionId??null,modelId:t.modelId??null,workDir:t.workDir,status:"active",externalChannel:null}}})}function H(e){return Z(e)}function B(e,t,s){return{}}function U(e,t,s,a,d,l){e.configure?.({sessionId:t,externalChannel:a??null,env:{...H(s),...B(t,a,d),...l?{SHENNIAN_ROOM_REGISTRY_KEY:l}:{}}})}function ie(e,t,s){if(e!=="claude"||!s)return t;const a=t.trim();return!!a&&a.startsWith("-")&&!a.includes("/")?G(s)??t:t}function le(e,t,s,a){let d=null;function l(n,r={}){e.client.sendAgentEvent({type:"event",event:"agent",payload:ae(n,t,r),seq:n.seq,id:`agent-evt-${n.runId}-${n.seq}`})}function g(n){n?.heartbeatTimer&&(clearInterval(n.heartbeatTimer),n.heartbeatTimer=null)}function i(n){if(!n.currentRunId||!n.currentRunPhase)return;const r=n.heartbeatSeq++;e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"heartbeat",sessionId:t,runId:n.currentRunId,seq:r,runPhase:n.currentRunPhase},seq:r,id:`agent-heartbeat-${n.currentRunId}-${r}-${Date.now()}`})}function f(n){!n||n.heartbeatTimer||(n.heartbeatTimer=setInterval(()=>{i(n)},se),n.heartbeatTimer.unref?.())}function y(n){const r=n?.pendingTextEvent;!n||!r||!r.text||(l({state:"delta",runId:r.runId,seq:r.seq,text:r.text,thinking:r.thinking||void 0}),n.pendingTextEvent=null)}a.on("agentEvent",n=>{const r=e.sessions.get(t),S=re(n),E=n.state==="final"||n.state==="error"||n.state==="aborted";r&&(r.nextEventSeq=n.seq+1,E||(r.currentRunId=n.runId),!E&&S&&(r.currentRunPhase=S,f(r)),n.agentSessionId&&(r.agentSessionId=n.agentSessionId)),e.managerRuntime?.noteAgentEvent(t,n),n.state!=="delta"&&x({level:"info",sessionId:t,wsEvent:`agent.${n.state}`,wsDirection:"out",metadata:{runId:n.runId,seq:n.seq,agentType:s}}),n.state==="delta"&&n.text&&!n.thinking?A(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.text}):n.state==="tool-call"||n.state==="tool-result"?A(t,{id:`agent-${n.runId}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:JSON.stringify({v:1,type:n.state==="tool-call"?"tool_use":"tool_result",name:n.name,status:n.state==="tool-call"?"running":"completed",detailRef:{runId:n.runId,sourceSeq:n.seq},args:n.args,result:n.result})}):n.state==="approval-pending"?A(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:Y(n.approval)}):(n.state==="error"||n.state==="aborted")&&n.message&&A(t,{id:`agent-${n.runId||"run"}-${n.seq}`,sessionId:t,role:"agent",ts:Date.now(),payload:n.message});const I=`${t}:${n.runId}`;if(n.state==="delta"&&!n.thinking&&n.text&&e.runTextAcc.set(I,(e.runTextAcc.get(I)??"")+n.text),n.agentSessionId&&n.agentSessionId!==d&&(d=n.agentSessionId,e.nativeFusion?.noteManagedSourceSession(t,W(s,n.source),n.agentSessionId),r&&O({sessionId:t,agentType:s,workDir:r.workDir,agentSessionId:n.agentSessionId}),e.client.sendEvent({type:"event",event:"session.update",payload:{session:{id:t,agentType:s,agentSessionId:n.agentSessionId}}})),n.state==="delta"){const m=n.text??"";if(!m)return;const w=!!n.thinking;r?.pendingTextEvent&&(r.pendingTextEvent.runId!==n.runId||r.pendingTextEvent.thinking!==w)&&y(r),r&&!r.pendingTextEvent&&(r.pendingTextEvent={runId:n.runId,seq:n.seq,text:"",thinking:w}),r?.pendingTextEvent&&(r.pendingTextEvent.text+=m,r.pendingTextEvent.seq=n.seq);return}let k={};if(n.state==="final"){y(r);const m=e.runTextAcc.get(I)??"";m&&(k={messageSummary:ne(m)}),e.runTextAcc.delete(I)}else n.state==="error"||n.state==="aborted"?(y(r),e.runTextAcc.delete(I)):(n.state==="tool-call"||n.state==="tool-result"||n.state==="approval-pending")&&y(r);E&&r?.currentRunId===n.runId&&(r.currentRunId=null,r.currentRunPhase=null,r.nextEventSeq=0,g(r),e.chatQueue?.noteTerminal(t)),l(n,k)}),a.on("error",n=>{console.error(`[chat.send] adapter error sessionId=${t} agentType=${s}: ${n.message}`),e.sessions.delete(t),e.chatQueue?.noteTerminal(t),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:t,message:n.message,runId:"",seq:0}})})}function ce(e,t){if(e.processedReqIds.add(t),e.processedReqIds.size>1e3){const s=e.processedReqIds.values().next().value;e.processedReqIds.delete(s)}}async function P(e){e.heartbeatTimer&&(clearInterval(e.heartbeatTimer),e.heartbeatTimer=null),e.adapter.removeAllListeners(),await e.adapter.stop().catch(()=>{})}function j(e,t,s,a,d,l,g){return g?e.send(t,s,a,d,l,g):l?.trim()?e.send(t,s,a,d,l):d?.length?e.send(t,s,a,d):a?e.send(t,s,a):e.send(t,s)}async function ue(e,t,s,a,d,l,g){e.evictIdleSessions();const i=Q(s);if(!i)throw new Error(`Unsupported agent: ${s}`);U(i,t,s,l,g),await i.start(t,a,d);const f={adapter:i,workDir:a,agentType:s,agentSessionId:d??null,lastActiveAt:Date.now(),currentRunId:null,currentRunPhase:null,nextEventSeq:0,heartbeatSeq:0,heartbeatTimer:null,pendingTextEvent:null,externalChannel:l??null,externalReplyTarget:g??null,externalChannelEnv:{...H(s),...B(t,l,g)}};return e.sessions.set(t,f),le(e,t,s,i),f}function ge(e,t){const s=e.sessions.get(t),a=s?.currentRunId;if(!s||!a)return;const d=s.nextEventSeq;e.runTextAcc.delete(`${t}:${a}`),s.pendingTextEvent=null,s.currentRunId=null,s.currentRunPhase=null,s.nextEventSeq=0,s.heartbeatTimer&&(clearInterval(s.heartbeatTimer),s.heartbeatTimer=null),e.client.sendAgentEvent({type:"event",event:"agent",payload:{state:"aborted",sessionId:t,runId:a,seq:d},seq:d,id:`agent-evt-${a}-${d}`}),e.chatQueue?.noteTerminal(t)}async function xe(e,t,s){if(e.processedReqIds.has(t.id)){e.client.sendRes({type:"res",id:t.id,ok:!0});return}ce(e,t.id);const{sessionId:a,text:d,agentType:l,workDir:g,agentSessionId:i,modelId:f,systemPrompt:y,managerDefaultWorkerAgentType:n,managerDefaultWorkerModelId:r,reasoningEffort:S,clientMessageId:E,sessionListProjection:I,waitForDispatch:k,responseId:m,roomContextRegistryKey:w,suppressExternalOwnerEcho:J}=t.params,R=m||t.id;X(I);const b=null,M="";if(!a||!d){e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:R,ok:!1,error:"sessionId and text are required"});return}const u=l;u==="manager"&&e.managerRuntime?.setManagerWorkerDefaults(a,n??null,r??null);const q=(u==="claude"||u==="codex"||u==="workbuddy")&&typeof S=="string"&&S.trim()?S.trim():void 0,T=e.resolvePath(ie(u,g||F.homedir(),i)||F.homedir()),D=T,_=te(t.params.attachments),p=_?.length?await ee({text:d,attachments:_,workDir:T}):{text:d,attachments:_,localized:!1};let o=e.sessions.get(a);if(o){if(o.lastActiveAt=Date.now(),o.agentType!==u||o.workDir!==T||JSON.stringify(o.externalChannel??null)!==JSON.stringify(b??null)){e.sessions.delete(a);try{await P(o)}catch{e.processedReqIds.delete(t.id)}o=void 0}else if(i&&o.agentSessionId!==i)try{await o.adapter.resume(i),o.agentSessionId=i}catch{e.sessions.delete(a);try{await P(o)}catch{e.processedReqIds.delete(t.id)}o=void 0}}if(!o)try{o=await ue(e,a,u,T,i,b,M)}catch(c){const h=c instanceof Error&&c.message.startsWith("Unsupported agent:")?c.message:`Failed to start ${l}: ${c instanceof Error?c.message:String(c)}`;console.error(`[chat.send] start failed reqId=${t.id} sessionId=${a} agentType=${l} workDir=${T} agentSessionId=${i??""}: ${h}`),e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:h,runId:"",seq:0}}),e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:R,ok:!1,error:h});return}w&&/^[a-f0-9]{64}$/.test(w)&&U(o.adapter,a,u,b,M,w);const C={id:E??`user-${t.id}`,sessionId:a,role:"user",ts:Date.now(),payload:V(p.text,p.attachments)};x({level:"info",sessionId:a,wsEvent:"chat.send.start",metadata:{reqId:t.id,agentType:u,modelId:f,reasoningEffort:q}});const v=c=>{de(e,{sessionId:a,agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f}),o.currentRunId||(o.nextEventSeq=0),e.nativeFusion?.registerManagedSend({sessionId:a,agentType:u,sourceAgentType:W(u,f),canonicalMessageId:E??null,sourceSessionKey:o.agentSessionId??i??null,text:p.text,managedEchoPolicy:c?.deliveryMode==="external_owner_queue"&&!J?"import_following":"suppress_following"}),A(a,C),L(e,C,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f})},z=async(c,h)=>{const $=oe(u,c);console.error(`[chat.send] send failed reqId=${t.id} sessionId=${a} agentType=${l} workDir=${T} agentSessionId=${o.agentSessionId??i??""}: ${$}`),e.sessions.delete(a);try{await P(o)}catch{}if(e.client.sendEvent({type:"event",event:"agent",payload:{state:"error",sessionId:a,message:$,runId:"",seq:0}}),!h){const N={id:`agent-error-${t.id}-${Date.now()}`,sessionId:a,role:"agent",ts:Date.now(),payload:$};A(a,N),L(e,N,{agentType:u,workDir:D,agentSessionId:o.agentSessionId??i??null,modelId:f})}h&&(e.processedReqIds.delete(t.id),e.client.sendRes({type:"res",id:R,ok:!1,error:$}))};if(k){let c;try{const h=p.attachments;c=await j(o.adapter,p.text,f,q,h,y,s),x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}catch(h){await z(h,!0);return}v(c??void 0),e.client.sendRes({type:"res",id:R,ok:!0,...p.localized||c?.deliveryMode==="external_owner_queue"?{payload:{...p.localized?{localizedAttachments:!0}:{},...c?.deliveryMode==="external_owner_queue"?{queued:!0,deliveryMode:c.deliveryMode,queuedSubmissionId:c.queuedSubmissionId}:{}}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});return}v(),e.client.sendRes({type:"res",id:R,ok:!0,...p.localized?{payload:{localizedAttachments:!0}}:{}}),x({level:"info",sessionId:a,wsEvent:"chat.send.res",metadata:{reqId:t.id,ok:!0}});const K=p.attachments;j(o.adapter,p.text,f,q,K,y,s).then(()=>{x({level:"info",sessionId:a,wsEvent:"chat.send.done",metadata:{reqId:t.id}})}).catch(c=>{z(c,!1)})}async function Ae(e,t){const{sessionId:s}=t.params,a=e.sessions.get(s);if(a){try{await a.adapter.stop()}catch{}ge(e,s),e.activityPublisher?.publish(s,null)}e.client.sendRes({type:"res",id:t.id,ok:!0})}export{Ae as handleChatAbort,xe as handleChatSend,de as sendSessionUpdateEvent};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import h from"node:fs";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as I}from"../config/index.js";import{mergeProjectedSessions as w}from"./projection.js";import{materializeRemoteChatAttachments as S}from"./remote-attachments.js";const x=I("chat-queue.json");function M(){return{sessions:{}}}function p(){return new Date().toISOString()}function R(n){return`enqueue-send-${n}-${y()}`}function u(){try{const n=JSON.parse(h.readFileSync(x,"utf-8"));return{sessions:n.sessions&&typeof n.sessions=="object"?n.sessions:{}}}catch{return M()}}function d(n){h.mkdirSync(I(""),{recursive:!0}),h.writeFileSync(x,JSON.stringify(n,null,2))}function f(n){if(!Array.isArray(n))return;const e=n.map(s=>{if(!s||typeof s!="object")return null;const t=s,i={path:typeof t.path=="string"?t.path:"",name:typeof t.name=="string"?t.name:"",mimeType:typeof t.mimeType=="string"?t.mimeType:"",...typeof t.previewData=="string"&&t.previewData.trim()?{previewData:t.previewData.trim()}:{}};return i.path&&i.name&&i.mimeType?i:null}).filter(s=>s!=null);return e.length?e:void 0}function q(n){const e=p();return{id:n.queueMessageId||n.clientMessageId||`queue-${y()}`,sessionId:n.sessionId,text:n.text,agentType:n.agentType,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,systemPrompt:n.systemPrompt??null,reasoningEffort:n.reasoningEffort??null,clientMessageId:n.clientMessageId??null,attachments:f(n.attachments),externalChannel:n.externalChannel??null,replyTarget:n.replyTarget??null,origin:n.origin,createdAt:e,updatedAt:e}}class z{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:u().sessions[e]??[]}}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(w(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=f(t.attachments),r=i?.length?await S({text:t.text,attachments:i,workDir:t.workDir}):{text:t.text,attachments:i,localized:!1},a=await this.isSessionBusy(t.sessionId),o=u(),c=o.sessions[t.sessionId]??[];if(!a&&!c.length){await this.opts.dispatchReq({...e,id:R(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const l=q({...t,text:r.text,attachments:r.attachments}),m=t.origin==="external"?D(c,l):null;m||(o.sessions[t.sessionId]=[...c,l],d(o)),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=u(),r=i.sessions[t.sessionId]??[],a=r.findIndex(o=>o.id===t.queueMessageId);if(a<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r[a]={...r[a],text:t.text,attachments:f(t.attachments),updatedAt:p()},i.sessions[t.sessionId]=r,d(i),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=u(),r=i.sessions[t.sessionId]??[],a=r.filter(o=>o.id!==t.queueMessageId);if(a.length===r.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],d(i),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}drainIdleQueues(){const e=u();for(const s of Object.keys(e.sessions))this.drainNext(s)}async drainNext(e){if(this.draining.has(e)
|
|
1
|
+
import h from"node:fs";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as I}from"../config/index.js";import{mergeProjectedSessions as w}from"./projection.js";import{materializeRemoteChatAttachments as S}from"./remote-attachments.js";const x=I("chat-queue.json");function M(){return{sessions:{}}}function p(){return new Date().toISOString()}function R(n){return`enqueue-send-${n}-${y()}`}function u(){try{const n=JSON.parse(h.readFileSync(x,"utf-8"));return{sessions:n.sessions&&typeof n.sessions=="object"?n.sessions:{}}}catch{return M()}}function d(n){h.mkdirSync(I(""),{recursive:!0}),h.writeFileSync(x,JSON.stringify(n,null,2))}function f(n){if(!Array.isArray(n))return;const e=n.map(s=>{if(!s||typeof s!="object")return null;const t=s,i={path:typeof t.path=="string"?t.path:"",name:typeof t.name=="string"?t.name:"",mimeType:typeof t.mimeType=="string"?t.mimeType:"",...typeof t.previewData=="string"&&t.previewData.trim()?{previewData:t.previewData.trim()}:{}};return i.path&&i.name&&i.mimeType?i:null}).filter(s=>s!=null);return e.length?e:void 0}function q(n){const e=p();return{id:n.queueMessageId||n.clientMessageId||`queue-${y()}`,sessionId:n.sessionId,text:n.text,agentType:n.agentType,workDir:n.workDir,agentSessionId:n.agentSessionId??null,modelId:n.modelId??null,systemPrompt:n.systemPrompt??null,reasoningEffort:n.reasoningEffort??null,clientMessageId:n.clientMessageId??null,attachments:f(n.attachments),externalChannel:n.externalChannel??null,replyTarget:n.replyTarget??null,origin:n.origin,createdAt:e,updatedAt:e}}class z{opts;draining=new Set;constructor(e){this.opts=e,setTimeout(()=>this.drainIdleQueues(),0).unref?.()}getSnapshot(e){return{sessionId:e,busy:!!this.opts.getRuntime().sessions.get(e)?.currentRunId,pending:u().sessions[e]??[]}}async handleEnqueue(e){const s=this.opts.getRuntime(),t=e.params;if(w(t.sessionListProjection),!t.sessionId||!t.text||!t.agentType||!t.workDir){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, text, agentType and workDir are required"});return}const i=f(t.attachments),r=i?.length?await S({text:t.text,attachments:i,workDir:t.workDir}):{text:t.text,attachments:i,localized:!1},a=await this.isSessionBusy(t.sessionId),o=u(),c=o.sessions[t.sessionId]??[];if(!a&&!c.length){await this.opts.dispatchReq({...e,id:R(e.id),method:"chat.send",params:{...t,text:r.text,responseId:e.id,clientMessageId:t.clientMessageId??t.queueMessageId,waitForDispatch:!0,attachments:r.attachments}});return}const l=q({...t,text:r.text,attachments:r.attachments}),m=t.origin==="external"?D(c,l):null;m||(o.sessions[t.sessionId]=[...c,l],d(o)),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queued:!0,queueMessageId:m?.id??l.id,queue:this.getSnapshot(t.sessionId),...r.localized?{localizedAttachments:!0}:{}}}),a||this.drainNext(t.sessionId)}async handleGet(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId is required"});return}s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}}),this.getSnapshot(t.sessionId).busy||this.drainNext(t.sessionId)}async handleEdit(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId||!t.text){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId, queueMessageId and text are required"});return}const i=u(),r=i.sessions[t.sessionId]??[],a=r.findIndex(o=>o.id===t.queueMessageId);if(a<0){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}r[a]={...r[a],text:t.text,attachments:f(t.attachments),updatedAt:p()},i.sessions[t.sessionId]=r,d(i),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}async handleDelete(e){const s=this.opts.getRuntime(),t=e.params;if(!t.sessionId||!t.queueMessageId){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"sessionId and queueMessageId are required"});return}const i=u(),r=i.sessions[t.sessionId]??[],a=r.filter(o=>o.id!==t.queueMessageId);if(a.length===r.length){s.client.sendRes({type:"res",id:e.id,ok:!1,error:"Queued message not found"});return}a.length?i.sessions[t.sessionId]=a:delete i.sessions[t.sessionId],d(i),this.broadcast(t.sessionId),s.client.sendRes({type:"res",id:e.id,ok:!0,payload:{queue:this.getSnapshot(t.sessionId)}})}noteTerminal(e){this.drainNext(e)}drainIdleQueues(){const e=u();for(const s of Object.keys(e.sessions))this.drainNext(s)}async drainNext(e){if(this.draining.has(e)||await this.isSessionBusy(e))return;const s=u(),t=s.sessions[e]??[],i=t.shift();if(!i){this.broadcast(e);return}const r=i.origin==="external"?this.mergeExternalMessages(i,t):i;t.length?s.sessions[e]=t:delete s.sessions[e],d(s),this.broadcast(e),this.draining.add(e);try{await this.dispatchQueuedMessage(r)}finally{this.draining.delete(e)}}mergeExternalMessages(e,s){const t=[e];for(;s[0]?.origin==="external";)t.push(s.shift());const i=k(t);return i.length===1?i[0]:{...e,id:`external-batch-${e.id}`,text:i.map(r=>r.text).filter(Boolean).join(`
|
|
2
2
|
|
|
3
3
|
`),attachments:b(i.flatMap(r=>r.attachments??[])),updatedAt:p()}}async dispatchQueuedMessage(e){await this.opts.dispatchReq({type:"req",id:`queue-send-${e.id}-${Date.now()}`,method:"chat.send",params:{sessionId:e.sessionId,text:e.text,agentType:e.agentType,workDir:e.workDir,agentSessionId:e.agentSessionId??null,modelId:e.modelId??void 0,systemPrompt:e.systemPrompt??null,reasoningEffort:e.reasoningEffort??void 0,clientMessageId:e.clientMessageId??e.id,attachments:e.attachments,externalChannel:e.externalChannel,replyTarget:e.replyTarget,waitForDispatch:!0}})}async isSessionBusy(e){const s=this.opts.getRuntime().sessions.get(e);if(!s)return!1;if(s.currentRunId)return!0;if(!s.adapter.getStatus)return!1;const t=await s.adapter.getStatus().catch(()=>null);return t?.active?(t.runId&&(s.currentRunId=t.runId,s.currentRunPhase=t.runPhase??s.currentRunPhase),!0):!1}broadcast(e){this.opts.getRuntime().client.sendEvent({type:"event",event:"session.queue.update",payload:{queue:this.getSnapshot(e)}})}}function k(n){const e=new Set,s=[];for(const t of n){const i=g(t);e.has(i)||(e.add(i),s.push(t))}return s}function D(n,e){const s=g(e);return n.find(t=>g(t)===s)??null}function g(n){const e=T(n);return e?`id:${e}`:["body",E(n.text),...(n.attachments??[]).map(s=>s.path||`${s.name}:${s.mimeType}`)].join(`
|
|
4
4
|
`)}function T(n){const e=typeof n.clientMessageId=="string"?n.clientMessageId.trim():"";if(e)return e;const s=typeof n.id=="string"?n.id.trim():"";return s&&!s.startsWith("queue-")?s:""}function b(n){const e=new Set,s=[];for(const t of n){const i=t.path||`${t.name}:${t.mimeType}`;e.has(i)||(e.add(i),s.push(t))}return s.length?s:void 0}function E(n){return String(n||"").replace(/\s+/g," ").trim()}export{z as ChatQueueManager};
|