shennian 0.5.9 → 0.5.13

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.
Files changed (37) hide show
  1. package/dist/publish-build-manifest.json +114 -59
  2. package/dist/src/agents/acp/adapter.js +2 -2
  3. package/dist/src/agents/claude.js +7 -7
  4. package/dist/src/agents/codex.js +3 -3
  5. package/dist/src/agents/command-spec.js +3 -3
  6. package/dist/src/agents/cursor-managed-permissions.js +2 -2
  7. package/dist/src/agents/cursor.js +1 -1
  8. package/dist/src/agents/custom.js +5 -5
  9. package/dist/src/agents/opencode-managed-permissions.js +2 -2
  10. package/dist/src/agents/opencode.js +1 -1
  11. package/dist/src/agents/pi-coding-agent.js +3 -3
  12. package/dist/src/agents/public-agent-client.js +1 -1
  13. package/dist/src/agents/public-delivery-client.js +1 -0
  14. package/dist/src/agents/public-delivery-worker.js +1 -0
  15. package/dist/src/agents/public-reply-collector.js +1 -0
  16. package/dist/src/agents/public-turn-runner.js +3 -0
  17. package/dist/src/agents/public-workdir.js +1 -0
  18. package/dist/src/agents/workbuddy-managed-permissions.js +2 -2
  19. package/dist/src/agents/workbuddy.js +2 -2
  20. package/dist/src/commands/agent-readiness.js +2 -2
  21. package/dist/src/commands/agent-workspace.js +3 -3
  22. package/dist/src/commands/daemon-ipc-health.js +1 -0
  23. package/dist/src/commands/daemon-stop-ipc.js +1 -0
  24. package/dist/src/commands/daemon.js +16 -16
  25. package/dist/src/commands/upgrade.js +1 -1
  26. package/dist/src/daemon-ipc/client.js +3 -3
  27. package/dist/src/daemon-ipc/control.js +1 -1
  28. package/dist/src/daemon-ipc/server.js +4 -4
  29. package/dist/src/daemon-ipc/windows-acl.js +43 -0
  30. package/dist/src/index.js +3 -3
  31. package/dist/src/native-fusion/local-repository.js +2 -2
  32. package/dist/src/native-fusion/test-workspace.js +1 -0
  33. package/dist/src/security/macos-file-sandbox.js +3 -0
  34. package/dist/src/security/managed-agent-policy.js +1 -1
  35. package/dist/src/security/public-agent-policy.js +1 -0
  36. package/node_modules/@shennian/wire/dist/agent-workspace.js +1 -1
  37. package/package.json +8 -1
@@ -1,3 +1,3 @@
1
- import{spawnSync as y}from"node:child_process";import{createInterface as w}from"node:readline";import{randomUUID as k}from"node:crypto";import E from"node:path";import{AgentAdapter as P,registerAgent as S}from"./adapter.js";import{resolveBuiltinCommand as I,spawnAgentCommand as x}from"./command-spec.js";import{buildAgentProcessEnv as _,buildManagedAgentProcessEnv as v,buildManagedNativeFullProcessEnv as B}from"../agent-env.js";import{buildPlatformInstructions as C,ensurePlatformInstructionsFile as A}from"./platform-instructions.js";import{resolveWindowsSystemRoot as T}from"../windows-shell.js";import{resolveManagedAgentAdapterEnforcement as W,isManagedAgentNativeFull as D}from"../security/managed-agent-policy.js";import{registerManagedAgentRuntimeSecurity as M}from"../security/managed-agent-runtime.js";import{resolveManagedRoomIpcPaths as O}from"../daemon-ipc/paths.js";import{compileWorkBuddyManagedPermissions as R,createWorkBuddyManagedConfigFiles as N,disposeWorkBuddyManagedConfigFiles as b,WORKBUDDY_MANAGED_RUNTIME_SECURITY as q}from"./workbuddy-managed-permissions.js";M("workbuddy",q);const F=new Set(["minimal","low","medium","high","xhigh","max"]);function U(n){const e=n?.trim();return e&&e!=="auto"?e:void 0}function $(n){const e=n?.trim();if(e){if(F.has(e))return e;throw new Error(`Unsupported WorkBuddy reasoning effort "${e}". Supported values: minimal, low, medium, high, xhigh, max.`)}}function z(n,e,t){if(!n)return[];if(e==="win32"){if(!t)throw new Error("WorkBuddy system prompt file is required on Windows.");return["--system-prompt-file",t]}return["--append-system-prompt",n]}function j(n){const e=n.split(/\n\n(?=<[^>\n]+>\n)/);return(e[e.length-1]??n).replace(/^\[群消息\][ \t]+<[^>\n]+>\n/,"").replace(/^\[群消息\]\n?/,"").replace(/^<[^>\n]+>\n/,"").trim()}function p(n,e=process.platform,t=L){e==="win32"&&n.pid&&t(n.pid)||n.kill("SIGTERM")}function L(n){const e=E.win32.join(T(),"System32","taskkill.exe");return y(e,["/pid",String(n),"/T","/F"],{stdio:"ignore",windowsHide:!0,timeout:5e3}).status===0}class Y extends P{type="workbuddy";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="open";hasEmittedText=!1;runPhase=null;persistNativeSession=!0;externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;managedProcessEnv=null;configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir)}async start(e,t,s,r){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=r?.persistNativeSession!==!1,s&&(this.agentSessionId=s)}async send(e,t,s,r,o,i){if((i?.source==="room_activation"||this.shennianSessionId?.startsWith("room-managed-")===!0)&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=k(),this.resetRunState();const d=i?.source==="room_activation",a=["-p",d?j(e):e,"--output-format","stream-json","--verbose"];this.persistNativeSession||a.push("--no-session-persistence");const h=U(t);h&&a.push("--model",h),d&&a.push("--max-turns","8");const m=this.shennianSessionId?.startsWith("room-managed-")&&!this.agentSessionId&&!d?null:C(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o);if(m){const f=process.platform==="win32"?A(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o):void 0;a.push(...z(m,process.platform,f))}const c=$(s);c&&a.push("--effort",c),this.agentSessionId&&a.push("--resume",this.agentSessionId),a.push(...await this.prepareManagedLaunch()),this.spawnAndParse(a)}async resume(e){await this.killProcess(),this.agentSessionId=e,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(e){const t=I("workbuddy");if(!t){this.disposeManagedConfig(),this.emit("error",new Error("WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry."));return}let s;try{s=x(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()})}catch(i){throw this.disposeManagedConfig(),i}this.process=s,w({input:s.stdout}).on("line",i=>{if(i.trim())try{this.handleStreamEvent(JSON.parse(i))}catch{}});let o="";s.stderr?.on("data",i=>{o=`${o}${i.toString()}`.slice(-8e3)}),s.on("close",i=>{this.process===s&&(this.process=null,this.runPhase=null,this.disposeManagedConfig(),this.handleProcessClose(i,o))}),s.on("error",i=>{if(this.process!==s)return;this.process=null,this.runPhase=null,this.disposeManagedConfig();const l=i.code==="ENOENT";this.emitErrorIfOpen({state:"error",message:l?"WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.":u(i.message)})})}handleStreamEvent(e){if(e.type==="system"&&e.subtype==="init"&&e.session_id){this.agentSessionId=e.session_id,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:e.session_id});return}if(e.type==="control_request"&&e.request?.subtype==="can_use_tool"){this.runPhase="waiting_approval",this.emitEvent({state:"approval-pending",name:e.request.tool_name,args:e.request.input,approval:{title:`WorkBuddy \u8BF7\u6C42\u4F7F\u7528 ${e.request.tool_name||"\u5DE5\u5177"}`,source:"workbuddy",actionHint:"\u8BF7\u5728 WorkBuddy \u6216\u795E\u5FF5\u4E2D\u786E\u8BA4\u540E\u7EE7\u7EED\u3002"}});return}if(e.type==="assistant"){const s=e.message?.content;if(s?.length)for(const r of s)this.handleContentBlock(r);else e.text&&this.emitText(e.text,!!e.thinking);return}if(e.type!=="result")return;if(e.subtype==="tool_result"){this.runPhase="tool_running",this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)});return}if(e.subtype==="error"||e.error){this.emitErrorIfOpen({state:"error",message:u(e.error||e.result||"WorkBuddy failed.")});return}!this.hasEmittedText&&e.result&&this.emitText(e.result,!1),e.session_id&&(this.agentSessionId=e.session_id);const t=e.usage??e.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:t?{inputTokens:t.input_tokens??0,outputTokens:t.output_tokens??0}:void 0})}handleContentBlock(e){e.type==="text"&&e.text?this.emitText(e.text,!1):e.type==="thinking"&&e.thinking?this.emitText(e.thinking,!0):e.type==="tool_use"?(this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:e.name,args:e.input})):e.type==="tool_result"&&this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)})}emitText(e,t){this.runPhase=t?"thinking":"streaming_text";const s=!t&&this.hasEmittedText?`
1
+ import{spawnSync as y}from"node:child_process";import{createInterface as w}from"node:readline";import{randomUUID as k}from"node:crypto";import E from"node:path";import{AgentAdapter as P,registerAgent as S}from"./adapter.js";import{resolveBuiltinCommand as I,spawnAgentCommand as _}from"./command-spec.js";import{buildAgentProcessEnv as x,buildManagedAgentProcessEnv as v,buildManagedNativeFullProcessEnv as B}from"../agent-env.js";import{buildPlatformInstructions as A,ensurePlatformInstructionsFile as C}from"./platform-instructions.js";import{resolveWindowsSystemRoot as W}from"../windows-shell.js";import{resolveManagedAgentAdapterEnforcement as D,isManagedAgentNativeFull as M}from"../security/managed-agent-policy.js";import{assertPublicAgentPolicy as T}from"../security/public-agent-policy.js";import{registerManagedAgentRuntimeSecurity as O}from"../security/managed-agent-runtime.js";import{resolveManagedRoomIpcPaths as R}from"../daemon-ipc/paths.js";import{compileWorkBuddyManagedPermissions as b,createWorkBuddyManagedConfigFiles as N,disposeWorkBuddyManagedConfigFiles as q,WORKBUDDY_MANAGED_RUNTIME_SECURITY as F}from"./workbuddy-managed-permissions.js";O("workbuddy",F);const U=new Set(["minimal","low","medium","high","xhigh","max"]);function $(n){const e=n?.trim();return e&&e!=="auto"?e:void 0}function z(n){const e=n?.trim();if(e){if(U.has(e))return e;throw new Error(`Unsupported WorkBuddy reasoning effort "${e}". Supported values: minimal, low, medium, high, xhigh, max.`)}}function j(n,e,t){if(!n)return[];if(e==="win32"){if(!t)throw new Error("WorkBuddy system prompt file is required on Windows.");return["--system-prompt-file",t]}return["--append-system-prompt",n]}function L(n){const e=n.split(/\n\n(?=<[^>\n]+>\n)/);return(e[e.length-1]??n).replace(/^\[群消息\][ \t]+<[^>\n]+>\n/,"").replace(/^\[群消息\]\n?/,"").replace(/^<[^>\n]+>\n/,"").trim()}function p(n,e=process.platform,t=Y){e==="win32"&&n.pid&&t(n.pid)||n.kill("SIGTERM")}function Y(n){const e=E.win32.join(W(),"System32","taskkill.exe");return y(e,["/pid",String(n),"/T","/F"],{stdio:"ignore",windowsHide:!0,timeout:5e3}).status===0}class G extends P{type="workbuddy";get recoveryCapabilities(){return{inspectTerminal:!1,reattachRun:!1,resumeRun:!1,resumeSession:!0}}process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="open";hasEmittedText=!1;runPhase=null;persistNativeSession=!0;externalChannel=null;shennianSessionId=null;extraEnv={};managedAgentPolicy=null;managedConfigDir=null;managedProcessEnv=null;configure(e){this.shennianSessionId=e.sessionId??null,this.externalChannel=e.externalChannel??null,this.extraEnv=e.env??{},"managedAgentPolicy"in e&&(this.managedAgentPolicy=e.managedAgentPolicy??null),this.managedAgentPolicy&&(this.workDir=this.managedAgentPolicy.adapterPolicy.workDir)}async start(e,t,s,r){this.workDir=this.managedAgentPolicy?.adapterPolicy.workDir??t,this.seq=0,this.persistNativeSession=r?.persistNativeSession!==!1,s&&(this.agentSessionId=s)}async send(e,t,s,r,o,i){if(i?.source==="published_agent"&&T(this.managedAgentPolicy),(i?.source==="room_activation"||this.shennianSessionId?.startsWith("room-managed-")===!0)&&!this.managedAgentPolicy)throw new Error("managed_agent_policy_missing");await this.killProcess(),this.runId=k(),this.resetRunState();const d=i?.source==="room_activation",a=["-p",d?L(e):e,"--output-format","stream-json","--verbose"];this.persistNativeSession||a.push("--no-session-persistence");const h=$(t);h&&a.push("--model",h),d&&a.push("--max-turns","8");const m=this.shennianSessionId?.startsWith("room-managed-")&&!this.agentSessionId&&!d?null:A(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o);if(m){const f=process.platform==="win32"?C(this.workDir??process.cwd(),this.externalChannel,this.shennianSessionId??void 0,o):void 0;a.push(...j(m,process.platform,f))}const c=z(s);c&&a.push("--effort",c),this.agentSessionId&&a.push("--resume",this.agentSessionId),a.push(...await this.prepareManagedLaunch(i?.source==="published_agent")),this.spawnAndParse(a)}async resume(e){await this.killProcess(),this.agentSessionId=e,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(e){const t=I("workbuddy");if(!t){this.disposeManagedConfig(),this.emit("error",new Error("WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry."));return}let s;try{s=_(t,e,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:this.buildProcessEnv()})}catch(i){throw this.disposeManagedConfig(),i}this.process=s,w({input:s.stdout}).on("line",i=>{if(i.trim())try{this.handleStreamEvent(JSON.parse(i))}catch{}});let o="";s.stderr?.on("data",i=>{o=`${o}${i.toString()}`.slice(-8e3)}),s.on("close",i=>{this.process===s&&(this.process=null,this.runPhase=null,this.disposeManagedConfig(),this.handleProcessClose(i,o))}),s.on("error",i=>{if(this.process!==s)return;this.process=null,this.runPhase=null,this.disposeManagedConfig();const l=i.code==="ENOENT";this.emitErrorIfOpen({state:"error",message:l?"WorkBuddy was not found. Install WorkBuddy Desktop or the CodeBuddy CLI, then retry.":u(i.message)})})}handleStreamEvent(e){if(e.type==="system"&&e.subtype==="init"&&e.session_id){this.agentSessionId=e.session_id,this.runPhase="thinking",this.emitEvent({state:"start",agentSessionId:e.session_id}),this.emit("nativeSession","workbuddy",e.session_id);return}if(e.type==="control_request"&&e.request?.subtype==="can_use_tool"){this.runPhase="waiting_approval",this.emitEvent({state:"approval-pending",name:e.request.tool_name,args:e.request.input,approval:{title:`WorkBuddy \u8BF7\u6C42\u4F7F\u7528 ${e.request.tool_name||"\u5DE5\u5177"}`,source:"workbuddy",actionHint:"\u8BF7\u5728 WorkBuddy \u6216\u795E\u5FF5\u4E2D\u786E\u8BA4\u540E\u7EE7\u7EED\u3002"}});return}if(e.type==="assistant"){const s=e.message?.content;if(s?.length)for(const r of s)this.handleContentBlock(r);else e.text&&this.emitText(e.text,!!e.thinking);return}if(e.type!=="result")return;if(e.subtype==="tool_result"){this.runPhase="tool_running",this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)});return}if(e.subtype==="error"||e.error){this.emitErrorIfOpen({state:"error",message:u(e.error||e.result||"WorkBuddy failed.")});return}!this.hasEmittedText&&e.result&&this.emitText(e.result,!1),e.session_id&&e.session_id!==this.agentSessionId&&(this.agentSessionId=e.session_id,this.emit("nativeSession","workbuddy",e.session_id));const t=e.usage??e.message?.usage;this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0,usage:t?{inputTokens:t.input_tokens??0,outputTokens:t.output_tokens??0}:void 0})}handleContentBlock(e){e.type==="text"&&e.text?this.emitText(e.text,!1):e.type==="thinking"&&e.thinking?this.emitText(e.thinking,!0):e.type==="tool_use"?(this.runPhase="tool_running",this.emitEvent({state:"tool-call",name:e.name,args:e.input})):e.type==="tool_result"&&this.emitEvent({state:"tool-result",name:e.name,result:g(e.content)})}emitText(e,t){this.runPhase=t?"thinking":"streaming_text";const s=!t&&this.hasEmittedText?`
2
2
 
3
- `:"";this.emitEvent({state:"delta",text:s+e,thinking:t||void 0}),t||(this.hasEmittedText=!0)}handleProcessClose(e,t){if(e!==0&&e!==null){this.emitErrorIfOpen({state:"error",message:u(t)||`WorkBuddy exited with code ${e}.`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(e){this.terminalState==="open"&&(this.terminalState="final",this.runPhase=null,this.emitEvent(e))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.runPhase=null,this.emitEvent(e))}emitEvent(e){this.emit("agentEvent",{...e,runId:this.runId,seq:this.seq++})}resetRunState(){this.seq=0,this.terminalState="open",this.hasEmittedText=!1,this.runPhase=null}async killProcess(){const e=this.process;if(!e){await this.disposeManagedConfig();return}this.process=null,this.runPhase=null,p(e),await new Promise(t=>{let s=!1;const r=()=>{s||(s=!0,t())};e.once("close",r),setTimeout(()=>{p(e),r()},3e3).unref?.()}),await this.disposeManagedConfig()}async prepareManagedLaunch(){const e=this.managedAgentPolicy;if(!e||D(e))return this.managedProcessEnv=null,e&&(this.managedProcessEnv=B(this.extraEnv)),["--permission-mode","bypassPermissions","--setting-sources","user,project,local","--tools","default"];const t=O(),s=R(W(e),e.adapterPolicy.workDir,{internalUnixSockets:t.transport==="unix_socket"?[t.endpoint]:[]}),r=await N(s);this.managedConfigDir=r.directory;const o=v({agentType:"workbuddy",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return Object.assign(o,s.runtimeEnv),delete o.CODEBUDDY_IS_SANDBOX,this.managedProcessEnv=o,["--permission-mode","dontAsk","--subagent-permission-mode","dontAsk","--settings",r.settingsPath,"--setting-sources","none","--strict-mcp-config","--mcp-config","{}","--tools",s.toolNames.join(",")]}buildProcessEnv(){return this.managedProcessEnv??_(this.extraEnv)}async disposeManagedConfig(){const e=this.managedConfigDir;this.managedConfigDir=null,this.managedProcessEnv=null,e&&await b(e).catch(()=>{})}}function g(n){if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return"[unserializable WorkBuddy tool result]"}}function u(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)}S("workbuddy",()=>new Y);export{Y as WorkBuddyAdapter,U as normalizeWorkBuddyModelId,$ as normalizeWorkBuddyReasoningEffort,u as sanitizeWorkBuddyError,p as terminateWorkBuddyProcess,j as workBuddyRoomActivationText,z as workBuddySystemPromptArgs};
3
+ `:"";this.emitEvent({state:"delta",text:s+e,thinking:t||void 0}),t||(this.hasEmittedText=!0)}handleProcessClose(e,t){if(e!==0&&e!==null){this.emitErrorIfOpen({state:"error",message:u(t)||`WorkBuddy exited with code ${e}.`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}emitFinalIfOpen(e){this.terminalState==="open"&&(this.terminalState="final",this.runPhase=null,this.emitEvent(e))}emitErrorIfOpen(e){this.terminalState==="open"&&(this.terminalState="error",this.runPhase=null,this.emitEvent(e))}emitEvent(e){this.emit("agentEvent",{...e,runId:this.runId,seq:this.seq++})}resetRunState(){this.seq=0,this.terminalState="open",this.hasEmittedText=!1,this.runPhase=null}async killProcess(){const e=this.process;if(!e){await this.disposeManagedConfig();return}this.process=null,this.runPhase=null,p(e),await new Promise(t=>{let s=!1;const r=()=>{s||(s=!0,t())};e.once("close",r),setTimeout(()=>{p(e),r()},3e3).unref?.()}),await this.disposeManagedConfig()}async prepareManagedLaunch(e=!1){const t=this.managedAgentPolicy;if(!t||M(t))return this.managedProcessEnv=null,t&&(this.managedProcessEnv=B(this.extraEnv)),["--permission-mode","bypassPermissions","--setting-sources","user,project,local","--tools","default"];const s=e?null:R(),r=b(D(t),t.adapterPolicy.workDir,{internalUnixSockets:s?.transport==="unix_socket"?[s.endpoint]:[],publicTurn:e}),o=await N(r);this.managedConfigDir=o.directory;const i=v({agentType:"workbuddy",providerEnv:this.extraEnv,sessionEnv:this.extraEnv,runtimeEnv:{NO_COLOR:"1"}});return Object.assign(i,r.runtimeEnv),delete i.CODEBUDDY_IS_SANDBOX,this.managedProcessEnv=i,["--permission-mode","dontAsk","--subagent-permission-mode","dontAsk","--settings",o.settingsPath,"--setting-sources","none","--strict-mcp-config","--mcp-config","{}","--tools",r.toolNames.join(",")]}buildProcessEnv(){return this.managedProcessEnv??x(this.extraEnv)}async disposeManagedConfig(){const e=this.managedConfigDir;this.managedConfigDir=null,this.managedProcessEnv=null,e&&await q(e).catch(()=>{})}}function g(n){if(typeof n=="string")return n;try{return JSON.stringify(n)}catch{return"[unserializable WorkBuddy tool result]"}}function u(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)}S("workbuddy",()=>new G);export{G as WorkBuddyAdapter,$ as normalizeWorkBuddyModelId,z as normalizeWorkBuddyReasoningEffort,u as sanitizeWorkBuddyError,p as terminateWorkBuddyProcess,L as workBuddyRoomActivationText,j as workBuddySystemPromptArgs};
@@ -1,2 +1,2 @@
1
- import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";const d={method_not_found:"Update the existing Shennian installation: Desktop through Desktop updates, standalone CLI through its own update flow. Do not install a second Client.",daemon_unavailable:"Check shennian status --json on the target computer. Do not resume an intentional pause without the user requesting it.",daemon_paused:"Shennian is paused. Ask the user to resume it before checking.",ipc_timeout:"The daemon did not answer in time, so the result is unknown; the machine is usually overloaded. Check its load and memory (uptime, free -m) and wait for the running check to end; do not repeatedly send tests. If the AI's own processes are still running afterwards, stop only those exact processes.",agent_check_in_progress:"Wait for the running check to finish before trying again.",agent_validation_runtime_unavailable:"Refresh discovery. If the tool is still missing, guide the user through its official installation on this computer.",agent_validation_runtime_auth_required:"Guide the user through the provider\u2019s own sign-in on this computer, then retry the selected AI and model.",agent_validation_model_unavailable:"Choose a model returned by discovery and check the provider account\u2019s model access.",agent_validation_quota_exceeded:"Check the provider account\u2019s credits, subscription or rate limit before another test.",agent_validation_network_failed:"Check the target computer\u2019s connection to the provider, then retry when it is available.",agent_validation_workdir_invalid:"Use an existing absolute project directory on this computer that the user allows Shennian to use.",agent_validation_workdir_not_found:"The working directory does not exist on this computer. Ask the user for an existing absolute project directory (it may have been moved, deleted or is on an unmounted drive); do not create it for them.",agent_validation_workdir_not_directory:"The working directory path is a file, not a folder. Ask the user for the project folder.",agent_validation_workdir_unavailable:"The working directory exists but this user cannot access it. Check its permissions on this computer or choose another directory.",agent_validation_start_failed:"The AI could not start, or did not finish starting within 60 seconds. Run the tool once on this computer to see its own error, fix its installation or configuration, then retry once.",agent_validation_response_timeout:"The AI started but gave no hello reply within 60 seconds and its processes were stopped. Check the computer's load and free memory first, then its provider sign-in, selected model and network; retry once and do not repeatedly send tests.",agent_validation_response_mismatch:"The AI replied but not with hello. Check the selected model; a reply that ignores simple instructions usually means a wrong or misconfigured model.",agent_validation_response_failed:"The AI started but failed while replying. Check its provider sign-in, selected model, quota and network, then retry once."},l={agent_validation_runtime_unavailable:"start",agent_validation_workdir_invalid:"start",agent_validation_workdir_not_found:"start",agent_validation_workdir_not_directory:"start",agent_validation_workdir_unavailable:"start",agent_validation_start_failed:"start",agent_validation_runtime_auth_required:"auth",agent_validation_model_unavailable:"model",agent_validation_quota_exceeded:"quota",agent_validation_network_failed:"network",agent_validation_response_timeout:"reply",agent_validation_response_mismatch:"reply",agent_validation_response_failed:"reply"},c=new Set(["claude","codex","cursor","workbuddy","opencode","pi-coding-agent"]);function u(t,o){const n=d[t]??"Return to the AI preparation guide on this machine. Check the selected directory, account, model and network; do not reinstall by default.",e=o&&typeof o=="object"&&!Array.isArray(o)&&typeof o.agentType=="string"?o.agentType:null;return!e||!c.has(e)||!["agent_validation_start_failed","agent_validation_response_failed","agent_validation_response_mismatch"].includes(t)?n:`${n} If the provider's own machine interface works but the official Adapter still fails, read shennian adapter show ${e}, create a local V1 file with shennian adapter init, and follow adapter check's structured stage/error/nextAction. Stop after three failed edit/check iterations.`}const h=9e4,_="A successful check only proves one standard-mode hello reply; it does not prove the AI can send messages in a Room.",p={authState:'unknown means Shennian has no login probe for this AI; it is not "not logged in".',defaultModelId:"null means no model is marked default; it does not mean no model is available. The AI picks its own model when none is passed."};async function g(t,o,n=s){try{const e=await n({method:t,params:o,timeoutMs:h});if(t==="agent.check"&&(!e||typeof e!="object"||Array.isArray(e)||e.ready!==!0))throw new r("agent_check_invalid_response","The daemon did not confirm a successful hello reply.");return t==="agent.discover"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,notes:p}}:t==="agent.check"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,note:_}}:{ok:!0,command:t,result:e}}catch(e){const i=e instanceof r?e.code:"agent_check_failed",a=l[i]??(t==="agent.check"&&i==="ipc_timeout"?"reply":void 0);return{ok:!1,command:t,error:i,...a?{stage:a}:{},nextAction:u(i,o)}}}function y(t){const o=async(n,e)=>{const i=await g(n,e);process.stdout.write(`${JSON.stringify(i)}
2
- `),i.ok||(process.exitCode=1)};t.command("discover").description("Read the daemon AI snapshot; use --refresh only to rerun local provider and model discovery").option("--agent <type>","Return only one discovered Agent type").option("--compact","Return capability summary without the full model list").option("--refresh","Rerun discovery; with --agent, probe only that Agent").option("--json","Print JSON (default)").action(n=>o("agent.discover",{...n.agent?{agentType:n.agent}:{},compact:n.compact===!0,refresh:n.refresh===!0})),t.command("check <agentType>").description("Send one hello test using the selected AI and model; uses provider quota, creates no managed Agent or room").requiredOption("--workdir <path>","Absolute project directory on this machine").option("--model <id>","Model returned by discovery; otherwise use the provider default").option("--json","Print JSON (default)").action((n,e)=>o("agent.check",{agentType:n,workDir:e.workdir,modelId:e.model??null}))}export{h as AGENT_READINESS_IPC_TIMEOUT_MS,g as executeAgentReadinessCommand,y as registerAgentReadinessCommands};
1
+ import{callDaemonIpc as s}from"../daemon-ipc/client.js";import{DaemonIpcError as r}from"../daemon-ipc/protocol.js";const d={method_not_found:"Update the existing Shennian installation: Desktop through Desktop updates, standalone CLI through its own update flow. Do not install a second Client.",daemon_unavailable:"Check shennian status --json on the target computer, including ipc.state. A running PID and paired account do not prove local IPC is available. Check the Client version, service account and logs; do not force-stop or resume an intentional pause.",ipc_access_denied:"The daemon IPC endpoint denied this user. Check whether the command and Windows scheduled task run under the same account and inspect the task/logs; do not force-stop the daemon.",ipc_connection_failed:"The daemon IPC connection failed. Check shennian status --json (ipc.state), the Client version and daemon logs before retrying.",daemon_paused:"Shennian is paused. Ask the user to resume it before checking.",ipc_timeout:"The daemon did not answer in time, so the result is unknown; the machine is usually overloaded. Check its load and memory (uptime, free -m) and wait for the running check to end; do not repeatedly send tests. If the AI's own processes are still running afterwards, stop only those exact processes.",agent_check_in_progress:"Wait for the running check to finish before trying again.",agent_validation_runtime_unavailable:"Refresh discovery. If the tool is still missing, guide the user through its official installation on this computer.",agent_validation_runtime_auth_required:"Guide the user through the provider\u2019s own sign-in on this computer, then retry the selected AI and model.",agent_validation_model_unavailable:"Choose a model returned by discovery and check the provider account\u2019s model access.",agent_validation_quota_exceeded:"Check the provider account\u2019s credits, subscription or rate limit before another test.",agent_validation_network_failed:"Check the target computer\u2019s connection to the provider, then retry when it is available.",agent_validation_workdir_invalid:"Use an existing absolute project directory on this computer that the user allows Shennian to use.",agent_validation_workdir_not_found:"The working directory does not exist on this computer. Ask the user for an existing absolute project directory (it may have been moved, deleted or is on an unmounted drive); do not create it for them.",agent_validation_workdir_not_directory:"The working directory path is a file, not a folder. Ask the user for the project folder.",agent_validation_workdir_unavailable:"The working directory exists but this user cannot access it. Check its permissions on this computer or choose another directory.",agent_validation_start_failed:"The AI could not start, or did not finish starting within 60 seconds. Run the tool once on this computer to see its own error, fix its installation or configuration, then retry once.",agent_validation_response_timeout:"The AI started but gave no hello reply within 60 seconds and its processes were stopped. Check the computer's load and free memory first, then its provider sign-in, selected model and network; retry once and do not repeatedly send tests.",agent_validation_response_mismatch:"The AI replied but not with hello. Check the selected model; a reply that ignores simple instructions usually means a wrong or misconfigured model.",agent_validation_response_failed:"The AI started but failed while replying. Check its provider sign-in, selected model, quota and network, then retry once."},c={agent_validation_runtime_unavailable:"start",agent_validation_workdir_invalid:"start",agent_validation_workdir_not_found:"start",agent_validation_workdir_not_directory:"start",agent_validation_workdir_unavailable:"start",agent_validation_start_failed:"start",agent_validation_runtime_auth_required:"auth",agent_validation_model_unavailable:"model",agent_validation_quota_exceeded:"quota",agent_validation_network_failed:"network",agent_validation_response_timeout:"reply",agent_validation_response_mismatch:"reply",agent_validation_response_failed:"reply"},l=new Set(["claude","codex","cursor","workbuddy","opencode","pi-coding-agent"]);function u(t,o){const n=d[t]??"Return to the AI preparation guide on this machine. Check the selected directory, account, model and network; do not reinstall by default.",e=o&&typeof o=="object"&&!Array.isArray(o)&&typeof o.agentType=="string"?o.agentType:null;return!e||!l.has(e)||!["agent_validation_start_failed","agent_validation_response_failed","agent_validation_response_mismatch"].includes(t)?n:`${n} If the provider's own machine interface works but the official Adapter still fails, read shennian adapter show ${e}, create a local V1 file with shennian adapter init, and follow adapter check's structured stage/error/nextAction. Stop after three failed edit/check iterations.`}const h=9e4,_="A successful check only proves one standard-mode hello reply; it does not prove the AI can send messages in a Room.",p={authState:'unknown means Shennian has no login probe for this AI; it is not "not logged in".',defaultModelId:"null means no model is marked default; it does not mean no model is available. The AI picks its own model when none is passed."};async function m(t,o,n=s){try{const e=await n({method:t,params:o,timeoutMs:h});if(t==="agent.check"&&(!e||typeof e!="object"||Array.isArray(e)||e.ready!==!0))throw new r("agent_check_invalid_response","The daemon did not confirm a successful hello reply.");return t==="agent.discover"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,notes:p}}:t==="agent.check"&&e&&typeof e=="object"&&!Array.isArray(e)?{ok:!0,command:t,result:{...e,note:_}}:{ok:!0,command:t,result:e}}catch(e){const a=e instanceof r?e.code:"agent_check_failed",i=c[a]??(t==="agent.check"&&a==="ipc_timeout"?"reply":void 0);return{ok:!1,command:t,error:a,...i?{stage:i}:{},nextAction:u(a,o)}}}function y(t){const o=async(n,e)=>{const a=await m(n,e);process.stdout.write(`${JSON.stringify(a)}
2
+ `),a.ok||(process.exitCode=1)};t.command("discover").description("Read the daemon AI snapshot; use --refresh only to rerun local provider and model discovery").option("--agent <type>","Return only one discovered Agent type").option("--compact","Return capability summary without the full model list").option("--refresh","Rerun discovery; with --agent, probe only that Agent").option("--json","Print JSON (default)").action(n=>o("agent.discover",{...n.agent?{agentType:n.agent}:{},compact:n.compact===!0,refresh:n.refresh===!0})),t.command("check <agentType>").description("Send one hello test using the selected AI and model; uses provider quota, creates no managed Agent or room").requiredOption("--workdir <path>","Absolute project directory on this machine").option("--model <id>","Model returned by discovery; otherwise use the provider default").option("--json","Print JSON (default)").action((n,e)=>o("agent.check",{agentType:n,workDir:e.workdir,modelId:e.model??null}))}export{h as AGENT_READINESS_IPC_TIMEOUT_MS,m as executeAgentReadinessCommand,y as registerAgentReadinessCommands};
@@ -1,3 +1,3 @@
1
- import O from"node:crypto";import E from"node:path";import{callDaemonIpc as M}from"../daemon-ipc/client.js";import{DaemonIpcError as k}from"../daemon-ipc/protocol.js";import{PublicAgentClient as q,PublicAgentClientError as b}from"../agents/public-agent-client.js";const C={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function W(e,n={}){const i=n.client??new q,t=n.io??C,a=n.callDaemon??M;e.command("list").description("List Shennian Agents owned by the paired user on this computer").action(()=>c("agent.list",t,()=>i.list())),e.command("get <agent-id>").description("Read one exact Shennian Agent").action(o=>c("agent.get",t,()=>i.get(l(o,"agent_id_invalid")))),e.command("create").description("Check one local AI configuration, then create a standard full-access Shennian Agent").requiredOption("--name <name>","Agent display name").requiredOption("--description <description>","Public Room introduction").requiredOption("--agent <type>","Exact type returned by agent discover").requiredOption("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--system-prompt <prompt>","Private system prompt","").action(o=>c("agent.create",t,async()=>{const s=L(o);return await R(s,a),i.create(s)})),e.command("update <agent-id>").description("Update one exact Shennian Agent; runtime changes are checked before saving").option("--name <name>","Agent display name").option("--description <description>","Public Room introduction").option("--agent <type>","Exact type returned by agent discover").option("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--clear-model","Use the runtime default model").option("--system-prompt <prompt>","Private system prompt").action((o,s)=>c("agent.update",t,async()=>{const _=l(o,"agent_id_invalid"),m=F(s);Object.keys(m).length||d("agent_changes_required","Provide at least one Agent field to update.");const p=N(await i.get(_)),u={...p,...m};return I(p)!==I(u)&&await R(u,a),i.update(_,m)})),e.command("delete <agent-id>").description("Delete one exact Shennian Agent and its dedicated Room conversations").action(o=>c("agent.delete",t,()=>i.delete(l(o,"agent_id_invalid"))));const g=e.command("room").description("List, add, or remove this Agent in explicit Rooms");g.command("list <agent-id>").description("List Rooms currently linked to one exact Agent").action(o=>c("agent.room.list",t,()=>i.listRooms(l(o,"agent_id_invalid")))),g.command("add <agent-id> <room-url>").description("Create a dedicated conversation and add this Agent to one explicit Room").option("--mode <mode>","auto_follow, mention_only, or paused","auto_follow").action((o,s,_)=>c("agent.room.add",t,async()=>{const m=l(o,"agent_id_invalid"),p=x(s),u=$(_.mode),A=`cli-agent-room:${O.randomUUID()}`,f=await i.createRoomSession(m,{roomId:p,assignmentIntentId:A,activationMode:u});if(f.existing===!0)return f;const h=v(f.session,"invalid_server_response"),P=y(h.id,"invalid_server_response"),U=y(h.agentType,"invalid_server_response"),D=await a({method:"room.agent.bind-managed",params:{roomId:p,agentIdentityId:m,nianSessionId:P,assignmentIntentId:A,agentType:U,activationMode:u},timeoutMs:12e4});return{session:h,binding:D}})),g.command("remove <agent-id> <room-url>").description("Remove this Agent from one explicit Room and delete its dedicated conversation").action((o,s)=>c("agent.room.remove",t,()=>i.removeFromRoom(l(o,"agent_id_invalid"),x(s))))}function L(e){return{displayName:r(e.name,191,"agent_name_invalid",!0),description:r(e.description,1e3,"agent_description_invalid",!0),runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0),workDir:S(e.workdir),modelId:e.model?r(e.model,191,"agent_model_invalid",!0):null,systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}function F(e){return e.clearModel&&e.model!==void 0&&d("agent_model_ambiguous","Use either --model or --clear-model, not both."),{...e.name===void 0?{}:{displayName:r(e.name,191,"agent_name_invalid",!0)},...e.description===void 0?{}:{description:r(e.description,1e3,"agent_description_invalid",!0)},...e.agent===void 0?{}:{runtimeAdapter:r(e.agent,64,"agent_runtime_invalid",!0)},...e.workdir===void 0?{}:{workDir:S(e.workdir)},...e.model===void 0&&!e.clearModel?{}:{modelId:e.clearModel?null:r(e.model,191,"agent_model_invalid",!0)},...e.systemPrompt===void 0?{}:{systemPrompt:r(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}}async function R(e,n){const i=y(e.runtimeAdapter,"agent_runtime_invalid"),t=y(e.workDir,"agent_workdir_invalid"),a=typeof e.modelId=="string"?e.modelId:null;if(v(await n({method:"agent.check",params:{agentType:i,workDir:t,modelId:a},timeoutMs:9e4}),"agent_check_invalid_response").ready!==!0)throw new k("agent_check_invalid_response","The daemon did not confirm a successful hello reply.")}function I(e){return JSON.stringify([e.runtimeAdapter??null,e.workDir??null,e.modelId??null,typeof e.systemPrompt=="string"?e.systemPrompt.trim():""])}function N(e){return v(e.agent,"invalid_server_response")}async function c(e,n,i){try{const t=await i();n.write(`${JSON.stringify({ok:!0,command:e,result:t})}
2
- `)}catch(t){if(t instanceof b){w(n,t.code,t.message,t.nextAction);return}if(t instanceof k){w(n,t.code,t.message,null);return}w(n,"agent_command_failed","Shennian Agent command failed.",null)}}function l(e,n){return/^[A-Za-z0-9_.:@/-]{1,191}$/.test(e)||d(n,"Use the exact Agent id returned by Shennian."),e}function x(e){let n;try{n=new URL(e)}catch{d("invalid_room_ref","Use an official Room URL returned by Shennian.")}const i=n.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(n.protocol!=="https:"||n.hostname!=="app.shennian.net"||n.port||n.username||n.password||n.hash||n.search||!i)&&d("invalid_room_ref","Use an official Room URL returned by Shennian."),i[1]}function $(e){if(e==="auto_follow"||e==="mention_only"||e==="paused")return e;d("invalid_activation_mode","--mode must be auto_follow, mention_only, or paused.")}function S(e){const n=r(e,1024,"agent_workdir_invalid",!0);return E.isAbsolute(n)||d("agent_workdir_invalid","Use an absolute working directory."),n}function r(e,n,i,t=!1){const a=typeof e=="string"?e.trim():"";return(t&&!a||a.length>n||a.includes("\0"))&&d(i,"Agent field is invalid."),a}function y(e,n){return(typeof e!="string"||!e)&&d(n,"Shennian returned an invalid Agent response."),e}function v(e,n){return(!e||typeof e!="object"||Array.isArray(e))&&d(n,"Shennian returned an invalid Agent response."),e}function d(e,n){throw new b(e,n)}function w(e,n,i,t){e.write(`${JSON.stringify({ok:!1,error:n,message:i,nextAction:t})}
3
- `),e.fail()}export{W as registerAgentWorkspaceCommands};
1
+ import M from"node:crypto";import C from"node:path";import{callDaemonIpc as N}from"../daemon-ipc/client.js";import{DaemonIpcError as k}from"../daemon-ipc/protocol.js";import{PublicAgentClient as $,PublicAgentClientError as P}from"../agents/public-agent-client.js";const V={write:e=>process.stdout.write(e),fail:()=>{process.exitCode=1}};function K(e,i={}){const o=i.client??new $,t=i.io??V,p=i.callDaemon??N;e.command("list").description("List Shennian Agents owned by the paired user on this computer").action(()=>r("agent.list",t,()=>o.list())),e.command("get <agent-id>").description("Read one exact Shennian Agent").action(n=>r("agent.get",t,()=>o.get(s(n,"agent_id_invalid")))),e.command("create").description("Check one local AI configuration, then create a standard full-access Shennian Agent").requiredOption("--name <name>","Agent display name").requiredOption("--description <description>","Public Room introduction").requiredOption("--agent <type>","Exact type returned by agent discover").requiredOption("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--system-prompt <prompt>","Private system prompt","").action(n=>r("agent.create",t,async()=>{const a=F(n);return await q(a,p),o.create(a)})),e.command("update <agent-id>").description("Update one exact Shennian Agent; runtime changes are checked before saving").option("--name <name>","Agent display name").option("--description <description>","Public Room introduction").option("--agent <type>","Exact type returned by agent discover").option("--workdir <path>","Absolute working directory on this computer").option("--model <id>","Exact model id returned by agent discover").option("--clear-model","Use the runtime default model").option("--system-prompt <prompt>","Private system prompt").action((n,a)=>r("agent.update",t,async()=>{const _=s(n,"agent_id_invalid"),m=T(a);Object.keys(m).length||c("agent_changes_required","Provide at least one Agent field to update.");const l=J(await o.get(_)),b={...l,...m};return O(l)!==O(b)&&await q(b,p),o.update(_,m)})),e.command("delete <agent-id>").description("Delete one exact Shennian Agent and its dedicated Room conversations").action(n=>r("agent.delete",t,()=>o.delete(s(n,"agent_id_invalid"))));const y=e.command("room").description("List, add, or remove this Agent in explicit Rooms");y.command("list <agent-id>").description("List Rooms currently linked to one exact Agent").action(n=>r("agent.room.list",t,()=>o.listRooms(s(n,"agent_id_invalid")))),y.command("add <agent-id> <room-url>").description("Create a dedicated conversation and add this Agent to one explicit Room").option("--mode <mode>","auto_follow, mention_only, or paused","auto_follow").action((n,a,_)=>r("agent.room.add",t,async()=>{const m=s(n,"agent_id_invalid"),l=U(a),b=Z(_.mode),x=`cli-agent-room:${M.randomUUID()}`,h=await o.createRoomSession(m,{roomId:l,assignmentIntentId:x,activationMode:b});if(h.existing===!0)return h;const A=w(h.session,"invalid_server_response"),D=f(A.id,"invalid_server_response"),L=f(A.agentType,"invalid_server_response"),E=await p({method:"room.agent.bind-managed",params:{roomId:l,agentIdentityId:m,nianSessionId:D,assignmentIntentId:x,agentType:L,activationMode:b},timeoutMs:12e4});return{session:A,binding:E}})),y.command("remove <agent-id> <room-url>").description("Remove this Agent from one explicit Room and delete its dedicated conversation").action((n,a)=>r("agent.room.remove",t,()=>o.removeFromRoom(s(n,"agent_id_invalid"),U(a))));const u=e.command("publication").description("Manage one Agent publication by exact Agent id");u.command("get <agent-id>").description("Read publication status and versions").action(n=>r("agent.publication.get",t,()=>o.getPublication(s(n,"agent_id_invalid")))),u.command("readiness <agent-id>").description("Read the current machine and runtime checks for one exact Agent publication").action(n=>r("agent.publication.readiness",t,()=>o.getPublicationReadiness(s(n,"agent_id_invalid")))),u.command("draft <agent-id>").description("Create an inactive publication draft; this does not expose a share URL").requiredOption("--name <name>","Public display name").requiredOption("--description <description>","Public introduction").option("--daily-limit <count>","Visitor messages per day","100").action((n,a)=>r("agent.publication.draft",t,()=>o.createPublicationDraft(s(n,"agent_id_invalid"),{displayName:d(a.name,80,"publication_name_invalid",!0),description:d(a.description,1e3,"publication_description_invalid"),dailyMessageLimit:I(a.dailyLimit)}))),u.command("update <agent-id>").description("Update publication details with versions from publication get").requiredOption("--generation <number>","Expected generation").requiredOption("--policy-version <number>","Expected policy version").requiredOption("--name <name>","Public display name").requiredOption("--description <description>","Public introduction").requiredOption("--daily-limit <count>","Visitor messages per day").action((n,a)=>r("agent.publication.update",t,()=>o.updatePublication(s(n,"agent_id_invalid"),{expectedGeneration:g(a.generation,"publication_generation_invalid"),expectedPolicyVersion:g(a.policyVersion,"publication_policy_version_invalid"),displayName:d(a.name,80,"publication_name_invalid",!0),description:d(a.description,1e3,"publication_description_invalid"),dailyMessageLimit:I(a.dailyLimit)}))),u.command("pause <agent-id>").description("Pause an active publication using exact versions").requiredOption("--generation <number>","Expected generation").requiredOption("--policy-version <number>","Expected policy version").action((n,a)=>r("agent.publication.pause",t,()=>o.pausePublication(s(n,"agent_id_invalid"),g(a.generation,"publication_generation_invalid"),g(a.policyVersion,"publication_policy_version_invalid")))),u.command("activate <agent-id>").description("Activate a draft or resume a paused publication only after server release and runtime checks").requiredOption("--generation <number>","Expected generation").requiredOption("--policy-version <number>","Expected policy version").action((n,a)=>r("agent.publication.activate",t,()=>o.activatePublication(s(n,"agent_id_invalid"),g(a.generation,"publication_generation_invalid"),g(a.policyVersion,"publication_policy_version_invalid")))),u.command("revoke <agent-id>").description("Revoke a publication and invalidate its old share URL").requiredOption("--generation <number>","Expected generation").action((n,a)=>r("agent.publication.revoke",t,()=>o.revokePublication(s(n,"agent_id_invalid"),g(a.generation,"publication_generation_invalid")))),u.command("conversations <agent-id>").description("List this publication\u2019s conversations, one page at a time").option("--cursor <cursor>","Opaque next cursor from the previous page").action((n,a)=>r("agent.publication.conversations",t,()=>o.listPublicationConversations(s(n,"agent_id_invalid"),a.cursor)));const v=e.command("public").description("Use the same published Agent URL as a human visitor");v.command("list").description("List your own existing conversations, including history after a link is revoked").option("--cursor <cursor>","Opaque next cursor from the previous page").action(n=>r("agent.public.list",t,()=>o.listMyDirectConversations(n.cursor))),v.command("open <url>").description("Read public Agent details from an exact Shennian share URL").action(n=>r("agent.public.open",t,()=>o.openPublishedAgent(n))),v.command("read <url>").description("Read your latest messages, or page older / follow newer messages").option("--after <cursor>","Read messages after this sequence").option("--before <cursor>","Read older messages before this sequence (default: latest)").action((n,a)=>r("agent.public.read",t,()=>o.readPublishedAgent(n,a))),v.command("history <conversation-id>").description("Read your latest conversation history by stable ID after URL revocation").option("--after <cursor>","Read messages after this sequence").option("--before <cursor>","Read older messages before this sequence (default: latest)").action((n,a)=>r("agent.public.history",t,()=>o.readDirectConversation(s(n,"conversation_id_invalid"),a))),v.command("send <url> <message>").description("Send one message as the paired human user; requires an active publication").requiredOption("--client-message-id <id>","Stable ID for idempotent retries (letters, digits, _ or -)").action((n,a,_)=>r("agent.public.send",t,async()=>{const m=z(_.clientMessageId),l=await o.sendPublishedAgent(n,{text:d(a,2e4,"publication_message_invalid",!0),clientMessageId:m});return{...l,...typeof l.conversationId=="string"?{conversationRef:l.conversationId}:{},clientMessageId:m}}))}function g(e,i){/^[1-9][0-9]*$/.test(e)||c(i,"Use a positive integer.");const o=Number(e);return Number.isSafeInteger(o)||c(i,"Use a positive integer."),o}function I(e){const i=g(e,"publication_daily_limit_invalid");return i>1e3&&c("publication_daily_limit_invalid","Daily limit must be at most 1000."),i}function z(e){return/^[A-Za-z0-9_-]{1,128}$/.test(e)||c("publication_message_id_invalid","Use 1\u2013128 letters, digits, _ or - for --client-message-id."),e}function F(e){return{displayName:d(e.name,191,"agent_name_invalid",!0),description:d(e.description,1e3,"agent_description_invalid",!0),runtimeAdapter:d(e.agent,64,"agent_runtime_invalid",!0),workDir:S(e.workdir),modelId:e.model?d(e.model,191,"agent_model_invalid",!0):null,systemPrompt:d(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}function T(e){return e.clearModel&&e.model!==void 0&&c("agent_model_ambiguous","Use either --model or --clear-model, not both."),{...e.name===void 0?{}:{displayName:d(e.name,191,"agent_name_invalid",!0)},...e.description===void 0?{}:{description:d(e.description,1e3,"agent_description_invalid",!0)},...e.agent===void 0?{}:{runtimeAdapter:d(e.agent,64,"agent_runtime_invalid",!0)},...e.workdir===void 0?{}:{workDir:S(e.workdir)},...e.model===void 0&&!e.clearModel?{}:{modelId:e.clearModel?null:d(e.model,191,"agent_model_invalid",!0)},...e.systemPrompt===void 0?{}:{systemPrompt:d(e.systemPrompt,2e4,"agent_system_prompt_invalid")}}}async function q(e,i){const o=f(e.runtimeAdapter,"agent_runtime_invalid"),t=f(e.workDir,"agent_workdir_invalid"),p=typeof e.modelId=="string"?e.modelId:null;if(w(await i({method:"agent.check",params:{agentType:o,workDir:t,modelId:p},timeoutMs:9e4}),"agent_check_invalid_response").ready!==!0)throw new k("agent_check_invalid_response","The daemon did not confirm a successful hello reply.")}function O(e){return JSON.stringify([e.runtimeAdapter??null,e.workDir??null,e.modelId??null,typeof e.systemPrompt=="string"?e.systemPrompt.trim():""])}function J(e){return w(e.agent,"invalid_server_response")}async function r(e,i,o){try{const t=await o();i.write(`${JSON.stringify({ok:!0,command:e,result:t})}
2
+ `)}catch(t){if(t instanceof P){R(i,t.code,t.message,t.nextAction);return}if(t instanceof k){R(i,t.code,t.message,null);return}R(i,"agent_command_failed","Shennian Agent command failed.",null)}}function s(e,i){return/^[A-Za-z0-9_.:@/-]{1,191}$/.test(e)||c(i,"Use the exact Agent id returned by Shennian."),e}function U(e){let i;try{i=new URL(e)}catch{c("invalid_room_ref","Use an official Room URL returned by Shennian.")}const o=i.pathname.match(/^\/spaces\/([A-Za-z0-9_-]{1,191})$/);return(i.protocol!=="https:"||i.hostname!=="app.shennian.net"||i.port||i.username||i.password||i.hash||i.search||!o)&&c("invalid_room_ref","Use an official Room URL returned by Shennian."),o[1]}function Z(e){if(e==="auto_follow"||e==="mention_only"||e==="paused")return e;c("invalid_activation_mode","--mode must be auto_follow, mention_only, or paused.")}function S(e){const i=d(e,1024,"agent_workdir_invalid",!0);return C.isAbsolute(i)||c("agent_workdir_invalid","Use an absolute working directory."),i}function d(e,i,o,t=!1){const p=typeof e=="string"?e.trim():"";return(t&&!p||p.length>i||p.includes("\0"))&&c(o,"Agent field is invalid."),p}function f(e,i){return(typeof e!="string"||!e)&&c(i,"Shennian returned an invalid Agent response."),e}function w(e,i){return(!e||typeof e!="object"||Array.isArray(e))&&c(i,"Shennian returned an invalid Agent response."),e}function c(e,i){throw new P(e,i)}function R(e,i,o,t){e.write(`${JSON.stringify({ok:!1,error:i,message:o,nextAction:t})}
3
+ `),e.fail()}export{K as registerAgentWorkspaceCommands};
@@ -0,0 +1 @@
1
+ import{callDaemonIpc as r}from"../daemon-ipc/client.js";import{DaemonIpcError as n}from"../daemon-ipc/protocol.js";async function d(t,c=r){if(!t)return{state:"not_running"};try{return await c({method:"protocol.info",timeoutMs:2e3}),{state:"ready"}}catch(e){const o=e instanceof n?e.code:e&&typeof e=="object"&&"code"in e&&(e.code==="ENOENT"||e.code==="EACCES"||e.code==="EPERM")?e.code==="ENOENT"?"ipc_secret_missing":"ipc_access_denied":"ipc_check_failed";return{state:o==="method_not_found"?"unsupported":"unavailable",error:o}}}export{d as checkDaemonIpcHealth};
@@ -0,0 +1 @@
1
+ import{callDaemonIpc as n}from"../daemon-ipc/client.js";async function c(r,t=n){if(!Number.isSafeInteger(r)||r<=0)return{accepted:!1,error:"daemon_instance_mismatch"};try{const e=await t({method:"daemon.shutdown",params:{expectedPid:r},timeoutMs:3e3});return e?.accepted!==!0||e.pid!==r?{accepted:!1,error:"daemon_instance_mismatch"}:{accepted:!0}}catch(e){const a=e&&typeof e=="object"&&"code"in e?e.code:null;return a==="ipc_access_denied"||a==="method_not_found"?{accepted:!1,error:"legacy_s4u_migration_required"}:a==="unauthorized"?{accepted:!1,error:"daemon_shutdown_unauthorized"}:a==="daemon_unavailable"?{accepted:!1,error:"daemon_shutdown_ipc_unavailable"}:{accepted:!1,error:"daemon_shutdown_ipc_failed"}}}export{c as requestWindowsDaemonSelfStop};
@@ -1,20 +1,20 @@
1
- import a from"chalk";import{readCliDistribution as ae}from"../runtime-distribution.js";import s from"node:fs";import{renameSyncWithRetry as ce}from"../fs/atomic-rename.js";import l from"node:path";import g from"node:os";import{execSync as c,spawn as ue}from"node:child_process";import{randomUUID as le}from"node:crypto";import{fileURLToPath as de}from"node:url";import{loadConfig as pe,resolveShennianPath as v,saveConfig as me}from"../config/index.js";import{buildWindowsLauncherCommand as fe,buildWindowsScheduledTaskXml as Se,buildWindowsStartupVbs as ge,escapeXml as I,isWindowsCmdScript as he,quoteCmdArg as W,quoteSystemdArg as ye,registerWindowsScheduledTask as we,suspendWindowsScheduledTask as ve}from"./daemon-windows.js";import{isDaemonReadyStatus as ke,waitForDaemonReadyStatus as A}from"./daemon-start-wait.js";import{getCurrentVersion as O}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as yn,buildWindowsScheduledTaskCommands as wn,buildWindowsScheduledTaskSuspendCommands as vn,buildWindowsScheduledTaskXml as kn,buildWindowsStartupVbs as Dn,suspendWindowsScheduledTask as bn}from"./daemon-windows.js";import{inferDaemonLauncherFromCommand as $n,isShennianRunServiceCommand as In}from"./daemon-process.js";import{SHENNIAN_DIR as U,PID_FILE as E,LOG_FILE as d,REMOTE_ACCESS_DISABLED_FILE as B,readPidRecord as R,clearDaemonLauncher as De,shouldDiscoverGlobalDaemonProcesses as be,getPlatform as f,isRunning as k,waitForPidExitSync as F,isRemoteAccessDisabled as Pe,clearRemoteAccessDisabled as $e,recordStartedDaemon as Ie,findRunningDaemonProcessIds as Ae,shouldInstallPlatformService as x,getDaemonStatus as u}from"./daemon-state.js";import{isEphemeralCliPath as Ee,resolveServiceLaunchSpec as Re,captureEnvForService as H,saveEnvSnapshot as xe}from"./daemon-service-spec.js";import{isRemoteAccessDisabled as En,clearRemoteAccessDisabled as Rn,getCurrentProcessDaemonLauncher as xn,createDaemonInstanceId as Ln,writeDaemonPid as _n,clearDaemonPidIfOwner as jn,writeDaemonLauncher as Nn,clearDaemonLauncher as Tn,recordStartedDaemon as Cn,findRunningDaemonProcessIds as Wn,isIsolatedDaemonE2eInstance as On,shouldDiscoverGlobalDaemonProcesses as Un,shouldInstallPlatformService as Bn,getDaemonStatus as Fn}from"./daemon-state.js";import{isSafeSnapshotEnvKey as Mn,isEphemeralCliPath as Gn,resolveServiceLaunchSpec as Vn,captureEnvForService as Jn,saveEnvSnapshot as Kn}from"./daemon-service-spec.js";const Le=l.dirname(de(import.meta.url)),M=l.resolve(Le,"../../bin/shennian.js"),G=process.execPath;function V(e,n){const t=l.dirname(e),r=`${e}.${process.pid}.${le()}.tmp`;s.mkdirSync(t,{recursive:!0});let o=null;try{o=s.openSync(r,"w",384),s.writeSync(o,n,void 0,"utf8"),s.fsyncSync(o),s.closeSync(o),o=null,ce(r,e);try{const i=s.openSync(t,"r");try{s.fsyncSync(i)}finally{s.closeSync(i)}}catch{}}finally{o!==null&&s.closeSync(o);try{s.unlinkSync(r)}catch{}}}function _e(e={}){const n=e.disabledAt??new Date().toISOString(),t=e.markerFile??B,r=e.platform??f(),o=e.servicePresent??(x()&&N(r)),i=e.execCommand??(m=>{c(m,{stdio:"pipe",windowsHide:!0})});if(V(t,n),!o)return{disabledAt:n,autoStartDisabled:!0};try{return i(r==="darwin"?`launchctl disable "${_()}"`:r==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${w}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(m){return{disabledAt:n,autoStartDisabled:!1,autoStartError:m instanceof Error?m.message:String(m)}}}function je(e={}){const n=e.platform??f();if(!(e.servicePresent??(x()&&N(n))))return{serviceStopped:!0};const r=e.execCommand??(o=>{c(o,{stdio:"pipe",windowsHide:!0})});try{return r(n==="darwin"?`launchctl bootout "${_()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${w}"`),{serviceStopped:!0}}catch(o){return{serviceStopped:!1,error:o instanceof Error?o.message:String(o)}}}function p(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
- `)}function J(e){const n=e?.trim();if(!n)return;const t=pe();t.serverUrl=n,me(t)}function K(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const L="com.shennian.agent",S=l.join(g.homedir(),"Library/LaunchAgents",`${L}.plist`),q=v("Shennian.app"),Ne=l.join(g.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),D=v("autostart.cmd"),b=v("autostart.vbs"),P=v("autostart.xml"),Te=l.join(Ne,"ShennianAgent.vbs");function X(e){const n=f()==="win32"?`where ${e}`:`command -v ${e}`;try{return c(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(o=>o.trim()).find(Boolean)??null}catch{return null}}function Ce(){const e=f()==="win32"?"npx.cmd":"npx",n=l.join(l.dirname(G),e);return s.existsSync(n)?n:X("npx")}function h(){return Re({nodeExec:G,scriptPath:M,shennianCommandPath:X("shennian"),npxPath:Ce()})}function Y(){try{c(`schtasks /delete /tn "${w}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function z(){try{s.unlinkSync(Te)}catch{}}function We(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||g.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function Oe(e){if(f()==="win32"&&he(e.command)){const n=[e.command,...e.args].map(W).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Ue(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Be(){const e=h();s.writeFileSync(D,fe(e,d)),s.writeFileSync(b,ge(D)),z(),Y();const n=Se({userId:We(),command:l.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[W(b)]});return s.writeFileSync(P,`\uFEFF${n}`,"utf16le"),we({taskName:w,xmlPath:P},t=>{c(t,{stdio:"pipe",windowsHide:!0})})}function Fe(){ve(w,e=>{c(e,{stdio:"pipe",windowsHide:!0})})}function He(e={}){const n=H(),t=e.launch??h(),r=Object.entries(n).map(([i,m])=>` <key>${I(i)}</key>
3
- <string>${I(m)}</string>`).join(`
4
- `),o=[t.command,...t.args].map(i=>` <string>${I(i)}</string>`).join(`
1
+ import a from"chalk";import{readCliDistribution as ae}from"../runtime-distribution.js";import i from"node:fs";import{renameSyncWithRetry as ce}from"../fs/atomic-rename.js";import d from"node:path";import g 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{loadConfig as pe,resolveShennianPath as v,saveConfig as me}from"../config/index.js";import{buildWindowsLauncherCommand as fe,buildWindowsScheduledTaskXml as Se,buildWindowsStartupVbs as ge,escapeXml as E,isWindowsCmdScript as he,quoteCmdArg as W,quoteSystemdArg as ye,registerWindowsScheduledTask as we,suspendWindowsScheduledTask as ve}from"./daemon-windows.js";import{isDaemonReadyStatus as ke,waitForDaemonReadyStatus as I}from"./daemon-start-wait.js";import{checkDaemonIpcHealth as De}from"./daemon-ipc-health.js";import{requestWindowsDaemonSelfStop as Pe}from"./daemon-stop-ipc.js";import{compareVersions as be,getCurrentVersion as O}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as Pn,buildWindowsScheduledTaskCommands as bn,buildWindowsScheduledTaskSuspendCommands as $n,buildWindowsScheduledTaskXml as En,buildWindowsStartupVbs as In,suspendWindowsScheduledTask as An}from"./daemon-windows.js";import{inferDaemonLauncherFromCommand as _n,isShennianRunServiceCommand as xn}from"./daemon-process.js";import{SHENNIAN_DIR as U,PID_FILE as A,LOG_FILE as l,REMOTE_ACCESS_DISABLED_FILE as B,readPidRecord as R,clearDaemonLauncher as $e,shouldDiscoverGlobalDaemonProcesses as Ee,getPlatform as p,isRunning as k,waitForPidExitSync as F,isRemoteAccessDisabled as Ie,clearRemoteAccessDisabled as Ae,recordStartedDaemon as Re,findRunningDaemonProcessIds as _e,shouldInstallPlatformService as _,getDaemonStatus as u}from"./daemon-state.js";import{isEphemeralCliPath as xe,resolveServiceLaunchSpec as Le,captureEnvForService as H,saveEnvSnapshot as Ce}from"./daemon-service-spec.js";import{isRemoteAccessDisabled as Cn,clearRemoteAccessDisabled as jn,getCurrentProcessDaemonLauncher as Nn,createDaemonInstanceId as Tn,writeDaemonPid as Wn,clearDaemonPidIfOwner as On,writeDaemonLauncher as Un,clearDaemonLauncher as Bn,recordStartedDaemon as Fn,findRunningDaemonProcessIds as Hn,isIsolatedDaemonE2eInstance as Mn,shouldDiscoverGlobalDaemonProcesses as Vn,shouldInstallPlatformService as Gn,getDaemonStatus as qn}from"./daemon-state.js";import{isSafeSnapshotEnvKey as Kn,isEphemeralCliPath as Xn,resolveServiceLaunchSpec as Yn,captureEnvForService as zn,saveEnvSnapshot as Qn}from"./daemon-service-spec.js";const je=d.dirname(le(import.meta.url)),M=d.resolve(je,"../../bin/shennian.js"),V=process.execPath;function G(e,n){const r=d.dirname(e),t=`${e}.${process.pid}.${de()}.tmp`;i.mkdirSync(r,{recursive:!0});let o=null;try{o=i.openSync(t,"w",384),i.writeSync(o,n,void 0,"utf8"),i.fsyncSync(o),i.closeSync(o),o=null,ce(t,e);try{const s=i.openSync(r,"r");try{i.fsyncSync(s)}finally{i.closeSync(s)}}catch{}}finally{o!==null&&i.closeSync(o);try{i.unlinkSync(t)}catch{}}}function Ne(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??B,t=e.platform??p(),o=e.servicePresent??(_()&&j(t)),s=e.execCommand??(f=>{c(f,{stdio:"pipe",windowsHide:!0})});if(G(r,n),!o)return{disabledAt:n,autoStartDisabled:!0};try{return s(t==="darwin"?`launchctl disable "${L()}"`:t==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${w}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(f){return{disabledAt:n,autoStartDisabled:!1,autoStartError:f instanceof Error?f.message:String(f)}}}function Te(e={}){const n=e.platform??p();if(!(e.servicePresent??(_()&&j(n))))return{serviceStopped:!0};const t=e.execCommand??(o=>{c(o,{stdio:"pipe",windowsHide:!0})});try{return t(n==="darwin"?`launchctl bootout "${L()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${w}"`),{serviceStopped:!0}}catch(o){return{serviceStopped:!1,error:o instanceof Error?o.message:String(o)}}}function m(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ `)}function q(e){const n=e?.trim();if(!n)return;const r=pe();r.serverUrl=n,me(r)}function J(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const x="com.shennian.agent",S=d.join(g.homedir(),"Library/LaunchAgents",`${x}.plist`),K=v("Shennian.app"),We=d.join(g.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),D=v("autostart.cmd"),P=v("autostart.vbs"),b=v("autostart.xml"),Oe=d.join(We,"ShennianAgent.vbs");function X(e){const n=p()==="win32"?`where ${e}`:`command -v ${e}`;try{return c(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(o=>o.trim()).find(Boolean)??null}catch{return null}}function Ue(){const e=p()==="win32"?"npx.cmd":"npx",n=d.join(d.dirname(V),e);return i.existsSync(n)?n:X("npx")}function h(){return Le({nodeExec:V,scriptPath:M,shennianCommandPath:X("shennian"),npxPath:Ue()})}function Y(){try{c(`schtasks /delete /tn "${w}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function z(){try{i.unlinkSync(Oe)}catch{}}function Be(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),r=process.env.USERNAME?.trim()||g.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${r}`:n?`${n}\\${r}`:r}function Fe(e){if(p()==="win32"&&he(e.command)){const n=[e.command,...e.args].map(W).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function He(e,n,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Me(){const e=h();i.writeFileSync(D,fe(e,l)),i.writeFileSync(P,ge(D)),z(),Y();const n=Se({userId:Be(),command:d.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[W(P)]});return i.writeFileSync(b,`\uFEFF${n}`,"utf16le"),we({taskName:w,xmlPath:b},r=>{c(r,{stdio:"pipe",windowsHide:!0})})}function Ve(){ve(w,e=>{c(e,{stdio:"pipe",windowsHide:!0})})}function Ge(e={}){const n=H(),r=e.launch??h(),t=Object.entries(n).map(([s,f])=>` <key>${E(s)}</key>
3
+ <string>${E(f)}</string>`).join(`
4
+ `),o=[r.command,...r.args].map(s=>` <string>${E(s)}</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">
8
8
  <dict>
9
9
  <key>Label</key>
10
- <string>${L}</string>
10
+ <string>${x}</string>
11
11
  <key>ProgramArguments</key>
12
12
  <array>
13
13
  ${o}
14
14
  </array>
15
15
  <key>EnvironmentVariables</key>
16
16
  <dict>
17
- ${r}
17
+ ${t}
18
18
  </dict>
19
19
  <key>RunAtLoad</key>
20
20
  <true/>
@@ -23,24 +23,24 @@ ${r}
23
23
  <key>ThrottleInterval</key>
24
24
  <integer>5</integer>
25
25
  <key>StandardOutPath</key>
26
- <string>${d}</string>
26
+ <string>${l}</string>
27
27
  <key>StandardErrorPath</key>
28
- <string>${d}</string>
28
+ <string>${l}</string>
29
29
  </dict>
30
- </plist>`}function Q(e=q){s.rmSync(e,{recursive:!0,force:!0})}function Me(e=f()){return e!=="darwin"?null:"macOS may show \u201CNode.js Foundation\u201D under Allow in the Background. This is the Node.js runtime used by Shennian Client."}const y=l.join(g.homedir(),".config/systemd/user/shennian.service");function Ge(){const e=H(),n=h(),t=Object.entries(e).map(([o,i])=>`Environment=${o}=${i}`).join(`
31
- `),r=[n.command,...n.args].map(ye).join(" ");return`[Unit]
30
+ </plist>`}function Q(e=K){i.rmSync(e,{recursive:!0,force:!0})}function qe(e=p()){return e!=="darwin"?null:"macOS may show \u201CNode.js Foundation\u201D under Allow in the Background. This is the Node.js runtime used by Shennian Client."}const y=d.join(g.homedir(),".config/systemd/user/shennian.service");function Je(){const e=H(),n=h(),r=Object.entries(e).map(([o,s])=>`Environment=${o}=${s}`).join(`
31
+ `),t=[n.command,...n.args].map(ye).join(" ");return`[Unit]
32
32
  Description=Shennian Agent Daemon
33
33
  After=network.target
34
34
 
35
35
  [Service]
36
- ${t}
37
- ExecStart=${r}
36
+ ${r}
37
+ ExecStart=${t}
38
38
  Restart=on-failure
39
39
  RestartSec=10
40
- StandardOutput=append:${d}
41
- StandardError=append:${d}
40
+ StandardOutput=append:${l}
41
+ StandardError=append:${l}
42
42
 
43
43
  [Install]
44
- WantedBy=default.target`}const w="ShennianAgent";function Ve(e){c(e,{stdio:"pipe",windowsHide:!0})}function Z(){const e=process.getuid?.();if(typeof e=="number")return e;const n=g.userInfo();return typeof n.uid=="number"?n.uid:n.username}function _(e=Z()){return`gui/${e}/${L}`}function Je(e=Ve){const n=`gui/${Z()}`,t=_();try{return e(`launchctl enable "${t}"`),e(`launchctl bootout "${t}" 2>/dev/null || true`),e(`launchctl bootstrap "${n}" "${S}"`),e(`launchctl kickstart -k "${t}"`),e(`launchctl print "${t}"`),!0}catch{try{return e(`launchctl enable "${t}"`),e(`launchctl unload "${S}" 2>/dev/null || true`),e(`launchctl load -w "${S}"`),e(`launchctl kickstart -k "${t}"`),e(`launchctl print "${t}"`),!0}catch{return!1}}}function ee(e={}){s.mkdirSync(U,{recursive:!0});const n=u({cleanupStale:!0}),t=$(n);if(t)throw new Error(t);if(n.pid!==null&&n.running&&!j(n)){e.quiet||console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}ne(n);const r=s.openSync(d,"a"),o=Oe(h()),i=ue(o.command,o.args,Ue(o,r));i.unref(),s.closeSync(r),Ie(i.pid),e.quiet||(console.log(a.green(`\u2713 Background service started (PID ${i.pid})`)),console.log(a.gray(` Logs: ${d}`)))}function j(e,n=R(),t=O()){return e.pid===null||!e.running||e.launcher!=="global-cli"?!1:e.adopted===!0||n?.version!==t}function $(e){return e.pid===null||!e.running||e.launcher==="global-cli"?null:`unsupported_daemon_source:${e.launcher}`}function N(e=f()){switch(e){case"darwin":return s.existsSync(S)&&!s.existsSync(q);case"linux":return s.existsSync(y);case"win32":return s.existsSync(D)&&s.existsSync(b)&&s.existsSync(P)}}function Ke(e,n,t,r=R(),o=O()){return!n.api?.trim()&&e.pid!==null&&e.running&&e.launcher==="global-cli"&&!e.stale&&!e.remoteAccessDisabled&&t&&!j(e,r,o)}function ne(e=u({cleanupStale:!0})){if(!j(e)||e.pid===null)return;let n;try{process.kill(e.pid,"SIGTERM"),F(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),F(e.pid,2e3))}catch(t){n=t}if(k(e.pid)){const t=n instanceof Error?n.message:"daemon_stop_timeout";throw new Error(`daemon_replacement_failed: ${t}`)}try{R()?.pid===e.pid&&s.unlinkSync(E)}catch{}}function te(e={}){if(s.mkdirSync(U,{recursive:!0}),!x())return!1;const n=f();switch(h().mode==="direct"&&Ee(M)&&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.")),n){case"darwin":{const r=l.dirname(S);s.mkdirSync(r,{recursive:!0}),Q(),s.writeFileSync(S,He());const o=Je(),i=Me(n);return!e.quiet&&i&&console.log(a.gray(`\u2139 ${i}`)),o}case"linux":{const r=l.dirname(y);s.mkdirSync(r,{recursive:!0}),s.writeFileSync(y,Ge());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${g.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 Be()}}function qe(){const n=u({cleanupStale:!0}).pid;if(n===null)return{};if(!k(n)){try{s.unlinkSync(E)}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 re(e=5e3){const n=qe();if(!n.stoppedPid)return n;if(!await C(n.stoppedPid,e))try{if(process.kill(n.stoppedPid,"SIGKILL"),!await C(n.stoppedPid,2e3))return{...n,error:"daemon_stop_timeout"}}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{s.unlinkSync(E)}catch{}if(De(),be()){for(const r of Ae())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await C(r,2e3)}catch{}}return n}function Xe(e={}){const n=Pe();try{const t=u({cleanupStale:!0}),r=$(t);if(r)throw new Error(r);J(K(e.api)),$e(),ne(t),te({quiet:e.json})||ee({quiet:e.json});const i=t.running?t.instanceId:void 0,m=A(()=>u({cleanupStale:!0}),{excludedInstanceId:i});if(!ke(m,i))throw new Error("daemon_start_failed");e.json?p(m):console.log(a.green("\u2713 Background service started"))}catch(t){n&&V(B,new Date().toISOString()),process.exitCode=1;const r=t instanceof Error?t.message:String(t);e.json?p({...u(),error:r}):console.error(a.red(`Background service did not start: ${r}`))}}async function Ye(e={}){const n=_e(),t=je(),r=await re(),o=u({cleanupStale:!0}),i=r.error??n.autoStartError??t.error??(o.running?"daemon_stop_failed":o.remoteAccessDisabled?void 0:"remote_access_pause_failed");if(i&&(process.exitCode=1),e.json){p(i?{...o,error:i}:o);return}i?console.error(a.red(`\u2717 Failed to stop completely: ${i}`)):console.log(a.green("\u2713 Background service stopped"))}function oe(e){const n=u({cleanupStale:!0});if(Ke(n,e,N())){e.json?p(n):console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}xe(),Xe(e)}async function T(e={}){const n=u({cleanupStale:!0}),t=$(n);if(t){process.exitCode=1,e.json?p({...n,error:t}):console.error(a.red(`Background service was not stopped: ${t}`));return}await Ye(e)}async function C(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!k(e))return!0;await new Promise(r=>setTimeout(r,100))}return!k(e)}async function ze(e={}){const n=u({cleanupStale:!0}),t=$(n);if(t){process.exitCode=1,e.json?p({...n,error:t}):console.error(a.red(`Background service was not restarted: ${t}`));return}if(n.remoteAccessDisabled){e.json?p({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(a.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}J(K(e.api));const r=f();r==="win32"&&Fe();const o=await re();if(o.error){e.json?p({...u(),error:o.error}):console.error(a.red(`\u2717 Failed to stop: ${o.error}`));return}if(o.stoppedPid!==void 0?e.json||console.log(a.green(`\u2713 Background service stopped (PID ${o.stoppedPid})`)):o.stalePid!==void 0?e.json||console.log(a.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${o.stalePid}`)):e.json||console.log(a.gray("\u25CF Background service not running, starting it now")),(r==="linux"||r==="win32")&&te()){e.json&&p(A(()=>u({cleanupStale:!0})));return}ee({quiet:e.json}),e.json&&p(A(()=>u({cleanupStale:!0})))}function se(e={}){const n={...u(),client:ae()};if(e.json){p(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 ie(e){if(!s.existsSync(d)){console.log(a.gray("No logs yet"));return}try{const n=c(g.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=s.readFileSync(d,"utf-8").split(`
44
+ WantedBy=default.target`}const w="ShennianAgent";function Ke(e){c(e,{stdio:"pipe",windowsHide:!0})}function Z(){const e=process.getuid?.();if(typeof e=="number")return e;const n=g.userInfo();return typeof n.uid=="number"?n.uid:n.username}function L(e=Z()){return`gui/${e}/${x}`}function Xe(e=Ke){const n=`gui/${Z()}`,r=L();try{return e(`launchctl enable "${r}"`),e(`launchctl bootout "${r}" 2>/dev/null || true`),e(`launchctl bootstrap "${n}" "${S}"`),e(`launchctl kickstart -k "${r}"`),e(`launchctl print "${r}"`),!0}catch{try{return e(`launchctl enable "${r}"`),e(`launchctl unload "${S}" 2>/dev/null || true`),e(`launchctl load -w "${S}"`),e(`launchctl kickstart -k "${r}"`),e(`launchctl print "${r}"`),!0}catch{return!1}}}function ee(e={}){i.mkdirSync(U,{recursive:!0});const n=u({cleanupStale:!0}),r=$(n);if(r)throw new Error(r);if(n.pid!==null&&n.running&&!C(n)){e.quiet||console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}ne(n);const t=i.openSync(l,"a"),o=Fe(h()),s=ue(o.command,o.args,He(o,t));s.unref(),i.closeSync(t),Re(s.pid),e.quiet||(console.log(a.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(a.gray(` Logs: ${l}`)))}function C(e,n=R(),r=O()){return e.pid===null||!e.running||e.launcher!=="global-cli"?!1:e.adopted===!0||n?.version!==r}function $(e){return e.pid===null||!e.running||e.launcher==="global-cli"?null:`unsupported_daemon_source:${e.launcher}`}function j(e=p()){switch(e){case"darwin":return i.existsSync(S)&&!i.existsSync(K);case"linux":return i.existsSync(y);case"win32":return i.existsSync(D)&&i.existsSync(P)&&i.existsSync(b)}}function Ye(e,n,r,t=R(),o=O()){return!n.api?.trim()&&e.pid!==null&&e.running&&e.launcher==="global-cli"&&!e.stale&&!e.remoteAccessDisabled&&r&&!C(e,t,o)}function ne(e=u({cleanupStale:!0})){if(!C(e)||e.pid===null)return;let n;try{process.kill(e.pid,"SIGTERM"),F(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),F(e.pid,2e3))}catch(r){n=r}if(k(e.pid)){const r=n instanceof Error?n.message:"daemon_stop_timeout",t=n&&typeof n=="object"&&"code"in n?n.code:null;throw process.platform==="win32"&&(t==="EPERM"||t==="EACCES")&&e.daemonVersion&&be(e.daemonVersion,"0.5.12")!=="none"?new Error("legacy_s4u_migration_required"):new Error(`daemon_replacement_failed: ${r}`)}try{R()?.pid===e.pid&&i.unlinkSync(A)}catch{}}function re(e={}){if(i.mkdirSync(U,{recursive:!0}),!_())return!1;const n=p();switch(h().mode==="direct"&&xe(M)&&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.")),n){case"darwin":{const t=d.dirname(S);i.mkdirSync(t,{recursive:!0}),Q(),i.writeFileSync(S,Ge());const o=Xe(),s=qe(n);return!e.quiet&&s&&console.log(a.gray(`\u2139 ${s}`)),o}case"linux":{const t=d.dirname(y);i.mkdirSync(t,{recursive:!0}),i.writeFileSync(y,Je());try{c("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{c(`loginctl enable-linger ${g.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 Me()}}function ze(){const n=u({cleanupStale:!0}).pid;if(n===null)return{};if(!k(n)){try{i.unlinkSync(A)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(r){const t=r&&typeof r=="object"&&"code"in r&&typeof r.code=="string"?r.code:void 0;return{refusedPid:n,error:r instanceof Error?r.message:String(r),errorCode:t}}}async function te(e=5e3){let n=ze(),r=!1;if(n.refusedPid&&(n.errorCode==="EPERM"||n.errorCode==="EACCES")&&p()==="win32"){const o=await Pe(n.refusedPid);if(!o.accepted)return{error:o.error};n={stoppedPid:n.refusedPid},r=!0}if(!n.stoppedPid)return n;const t=await T(n.stoppedPid,e);if(!t&&r)return{...n,error:"daemon_stop_timeout"};if(!t)try{if(process.kill(n.stoppedPid,"SIGKILL"),!await T(n.stoppedPid,2e3))return{...n,error:"daemon_stop_timeout"}}catch(o){return{...n,error:o instanceof Error?o.message:String(o)}}try{i.unlinkSync(A)}catch{}if($e(),Ee()){for(const o of _e())if(o!==n.stoppedPid)try{process.kill(o,"SIGTERM"),await T(o,2e3)}catch{}}return n}function Qe(e={}){const n=Ie();try{const r=u({cleanupStale:!0}),t=$(r);if(t)throw new Error(t);q(J(e.api)),Ae(),ne(r),re({quiet:e.json})||ee({quiet:e.json});const s=r.running?r.instanceId:void 0,f=I(()=>u({cleanupStale:!0}),{excludedInstanceId:s});if(!ke(f,s))throw new Error("daemon_start_failed");e.json?m(f):console.log(a.green("\u2713 Background service started"))}catch(r){n&&G(B,new Date().toISOString()),process.exitCode=1;const t=r instanceof Error?r.message:String(r);e.json?m({...u(),error:t}):console.error(a.red(`Background service did not start: ${t}`))}}async function Ze(e={}){const n=Ne(),r=Te(),t=await te(),o=u({cleanupStale:!0}),s=t.error??n.autoStartError??r.error??(o.running?"daemon_stop_failed":o.remoteAccessDisabled?void 0:"remote_access_pause_failed");if(s&&(process.exitCode=1),e.json){m(s?{...o,error:s}:o);return}s?console.error(a.red(`\u2717 Failed to stop completely: ${s}`)):console.log(a.green("\u2713 Background service stopped"))}function oe(e){const n=u({cleanupStale:!0});if(Ye(n,e,j())){e.json?m(n):console.log(a.green(`\u2713 Background service already running (PID ${n.pid})`));return}Ce(),Qe(e)}async function N(e={}){const n=u({cleanupStale:!0}),r=$(n);if(r){process.exitCode=1,e.json?m({...n,error:r}):console.error(a.red(`Background service was not stopped: ${r}`));return}await Ze(e)}async function T(e,n=5e3){const r=Date.now();for(;Date.now()-r<n;){if(!k(e))return!0;await new Promise(t=>setTimeout(t,100))}return!k(e)}async function en(e={}){const n=u({cleanupStale:!0}),r=$(n);if(r){process.exitCode=1,e.json?m({...n,error:r}):console.error(a.red(`Background service was not restarted: ${r}`));return}if(n.remoteAccessDisabled){e.json?m({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(a.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}q(J(e.api));const t=p();t==="win32"&&Ve();const o=await te();if(o.error){e.json?m({...u(),error:o.error}):console.error(a.red(`\u2717 Failed to stop: ${o.error}`));return}if(o.stoppedPid!==void 0?e.json||console.log(a.green(`\u2713 Background service stopped (PID ${o.stoppedPid})`)):o.stalePid!==void 0?e.json||console.log(a.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${o.stalePid}`)):e.json||console.log(a.gray("\u25CF Background service not running, starting it now")),(t==="linux"||t==="win32")&&re()){e.json&&m(I(()=>u({cleanupStale:!0})));return}ee({quiet:e.json}),e.json&&m(I(()=>u({cleanupStale:!0})))}async function ie(e={}){const n=u(),r={...n,client:ae(),ipc:await De(n.running)};if(e.json){m(r);return}if(r.pid===null){console.log(a.gray("\u25CF Background service not running"));return}r.running?(console.log(a.green(`\u25CF Background service running (PID ${r.pid})`)),(r.ipc.state==="unavailable"||r.ipc.state==="unsupported")&&(console.log(a.yellow(` Local control IPC: ${r.ipc.state} (${r.ipc.error})`)),console.log(a.gray(" PID and pairing alone do not prove local commands work; check the Client version, service account, and logs."))),console.log(a.gray(` Logs: ${l}`))):console.log(a.yellow(`\u25CF Background service stopped (PID ${r.pid} is stale)`))}function se(e){if(!i.existsSync(l)){console.log(a.gray("No logs yet"));return}try{const n=c(g.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${l}"`:`tail -n ${e.lines} "${l}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=i.readFileSync(l,"utf-8").split(`
45
45
  `).slice(-e.lines);console.log(n.join(`
46
- `))}}async function Qe(){switch(await T(),f()){case"darwin":{if(s.existsSync(S)){try{c(`launchctl unload -w "${S}"`,{stdio:"pipe",windowsHide:!0})}catch{}s.unlinkSync(S)}Q(),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(s.existsSync(y)){s.unlinkSync(y);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(a.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Y();try{s.unlinkSync(D)}catch{}try{s.unlinkSync(b)}catch{}try{s.unlinkSync(P)}catch{}z(),console.log(a.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function Sn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>oe(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>T(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>se(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>ie({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,o)=>{o||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),oe(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await T(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),ze(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),se(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),ie({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(Qe)}export{Ue as buildDaemonSpawnOptions,He as buildMacLaunchdPlist,Ge as buildSystemdUnit,yn as buildWindowsLauncherCommand,wn as buildWindowsScheduledTaskCommands,vn as buildWindowsScheduledTaskSuspendCommands,kn as buildWindowsScheduledTaskXml,Dn as buildWindowsStartupVbs,Ke as canReuseRunningDaemonForStart,Jn as captureEnvForService,Tn as clearDaemonLauncher,jn as clearDaemonPidIfOwner,Rn as clearRemoteAccessDisabled,Ln as createDaemonInstanceId,Wn as findRunningDaemonProcessIds,xn as getCurrentProcessDaemonLauncher,Fn as getDaemonStatus,_ as getLaunchdServiceTarget,Me as getMacBackgroundItemNotice,$ as getUnsupportedDaemonSourceError,$n as inferDaemonLauncherFromCommand,te as installService,N as isAutoStartServicePresent,Gn as isEphemeralCliPath,On as isIsolatedDaemonE2eInstance,En as isRemoteAccessDisabled,Mn as isSafeSnapshotEnvKey,In as isShennianRunServiceCommand,_e as prepareRemoteAccessPause,Cn as recordStartedDaemon,Sn as registerDaemonCommand,Je as reloadLaunchdAgent,Q as removeLegacyMacBackgroundApp,Vn as resolveServiceLaunchSpec,Kn as saveEnvSnapshot,Un as shouldDiscoverGlobalDaemonProcesses,Bn as shouldInstallPlatformService,j as shouldReplaceRunningDaemon,ee as startDaemonProcess,je as stopPreparedRemoteAccessService,bn as suspendWindowsScheduledTask,Nn as writeDaemonLauncher,_n as writeDaemonPid};
46
+ `))}}async function nn(){switch(await N(),p()){case"darwin":{if(i.existsSync(S)){try{c(`launchctl unload -w "${S}"`,{stdio:"pipe",windowsHide:!0})}catch{}i.unlinkSync(S)}Q(),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(i.existsSync(y)){i.unlinkSync(y);try{c("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(a.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{Y();try{i.unlinkSync(D)}catch{}try{i.unlinkSync(P)}catch{}try{i.unlinkSync(b)}catch{}z(),console.log(a.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function vn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>oe(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>N(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>ie(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>se({lines:parseInt(t.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),r=(t,o)=>{o||console.error(a.yellow(`\u26A0 Deprecated command. Use \`shennian ${t}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>{r("start",t.json),oe(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await N(t)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>{r("stop && shennian start",t.json),en(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),ie(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),se({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(nn)}export{He as buildDaemonSpawnOptions,Ge as buildMacLaunchdPlist,Je as buildSystemdUnit,Pn as buildWindowsLauncherCommand,bn as buildWindowsScheduledTaskCommands,$n as buildWindowsScheduledTaskSuspendCommands,En as buildWindowsScheduledTaskXml,In as buildWindowsStartupVbs,Ye as canReuseRunningDaemonForStart,zn as captureEnvForService,Bn as clearDaemonLauncher,On as clearDaemonPidIfOwner,jn as clearRemoteAccessDisabled,Tn as createDaemonInstanceId,Hn as findRunningDaemonProcessIds,Nn as getCurrentProcessDaemonLauncher,qn as getDaemonStatus,L as getLaunchdServiceTarget,qe as getMacBackgroundItemNotice,$ as getUnsupportedDaemonSourceError,_n as inferDaemonLauncherFromCommand,re as installService,j as isAutoStartServicePresent,Xn as isEphemeralCliPath,Mn as isIsolatedDaemonE2eInstance,Cn as isRemoteAccessDisabled,Kn as isSafeSnapshotEnvKey,xn as isShennianRunServiceCommand,Ne as prepareRemoteAccessPause,Fn as recordStartedDaemon,vn as registerDaemonCommand,Xe as reloadLaunchdAgent,Q as removeLegacyMacBackgroundApp,Yn as resolveServiceLaunchSpec,Qn as saveEnvSnapshot,Vn as shouldDiscoverGlobalDaemonProcesses,Gn as shouldInstallPlatformService,C as shouldReplaceRunningDaemon,ee as startDaemonProcess,Te as stopPreparedRemoteAccessService,An as suspendWindowsScheduledTask,Un as writeDaemonLauncher,Wn as writeDaemonPid};
@@ -1 +1 @@
1
- import c from"chalk";import{execSync as P,spawn as V}from"node:child_process";import{randomUUID as w}from"node:crypto";import $ from"node:os";import{createInterface as A}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as k,compareVersions as v,checkForUpdate as M,acquireUpgradeLock as F,recordUpgradeFailure as O}from"../upgrade/engine.js";import{callDaemonIpc as _}from"../daemon-ipc/client.js";import{getDaemonStatus as D}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as R,waitForUpdaterReady as H,confirmUpdaterHandoff as L}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as j}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as q}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as C}from"../upgrade/updater-worker.js";import{getUpgradeChannel as x,setUpgradeChannel as G}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as T,getCliUpdateSourceOrder as b}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(t,r){return t!=="major"||r}function ie(t={}){const r=t.platform??$.platform(),i=t.kill??process.kill,e=t.pid??process.pid,l=t.env??process.env,n=t.spawn??V,o=t.execSync??P;if(r==="win32"){const a=l.ComSpec||"cmd.exe";try{n(a,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{o('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{n("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:l}).unref()}catch{}i(e,"SIGTERM")}function ce(t){t.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(G(r.channel),console.log(`Upgrade channel: ${x()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${x()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const l=setInterval(()=>{process.stdout.write(`\r ${I[e%I.length]} Checking for updates...`),e++},80);let n;try{n=r.to??await k(),clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const o=v(i,n);if(o==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(n)} ${o==="major"?c.red("(major)"):o==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(o==="major"&&(console.log(c.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const a=A({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${n} to confirm this major upgrade: `);if(a.close(),p.trim()!==n){console.log(c.gray(" Upgrade cancelled."));return}}try{await _({method:"upgrade.start",params:{targetVersion:n,confirmedMajor:o==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(D({cleanupStale:!0}).running)throw a;const m=U(),u={fromVersion:i,targetVersion:n,oldDaemonPid:null,jobId:w()},g=R(u);await C(g,{fetchOwnedArtifact:T,sourceOrder:b()}),j(g,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${n}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function de(t,r,i,e={}){const l=s=>{t.sendEvent({type:"event",event:"upgrade",payload:s})},n=e.currentVersion??y();let o;try{l({state:"checking",machineId:"self"}),o=i??await k()}catch(s){const d=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const a=v(n,o);if(a==="none")return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:n}}),{ok:!0,alreadyLatest:!0,version:n};if(!Y(a,e.confirmedMajor===!0))return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const p=w();let m;try{m=F();try{N(q("runtime","updater"))}catch(s){throw m.release(),s}}catch(s){const d=s instanceof Error?s.message:String(s);return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!1,error:d}),{ok:!1,error:d}}const u={from:n,to:o,jobId:p},g=async()=>{let s=!1;try{const d=U(),f={fromVersion:n,targetVersion:o,oldDaemonPid:process.pid,jobId:p,requireHandoffAck:!0},h=R(f);if(l({state:"downloading",machineId:"self",version:o,jobId:p}),await C(h,{fetchOwnedArtifact:T,sourceOrder:b()}),j(h,d.workerPath),S(f,{readiness:d}),await H(h),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const E=e.prepareForUpgrade?.(p)??{activeRunCount:0};return s=!0,L(h),l({state:"restarting",machineId:"self",...u,activeRunCount:E.activeRunCount}),{ok:!0,...u,resultPath:h.resultPath}}catch(d){s&&e.cancelUpgradePreparation?.();const f=d instanceof Error?d.message:String(d);return f!=="upgrade_waiting_for_idle"&&O(o,f),l({state:"failed",machineId:"self",error:f,jobId:p}),{ok:!1,error:f}}finally{m.release()}};return e.suppressResponse||t.sendRes({type:"res",id:r,ok:!0,payload:u}),e.waitForCompletion?g():(g(),{ok:!0,...u})}async function le(t,r,i={}){const e=await M(i.currentVersion);t.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{de as handleUpgradeStart,le as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,ce as registerUpgradeCommand,ie as restartCurrentDaemonAfterUpgrade};
1
+ import c from"chalk";import{execSync as V,spawn as P}from"node:child_process";import{randomUUID as v}from"node:crypto";import _ from"node:os";import{createInterface as $}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as k,compareVersions as w,checkForUpdate as A,acquireUpgradeLock as M,recordUpgradeFailure as F}from"../upgrade/engine.js";import{callDaemonIpc as D}from"../daemon-ipc/client.js";import{getDaemonStatus as O}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as j,waitForUpdaterReady as H,confirmUpdaterHandoff as q}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as C}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as L}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as R}from"../upgrade/updater-worker.js";import{getUpgradeChannel as T,setUpgradeChannel as G}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as b,getCliUpdateSourceOrder as x}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(n,r){return n!=="major"||r}function ie(n={}){const r=n.platform??_.platform(),i=n.kill??process.kill,e=n.pid??process.pid,p=n.env??process.env,t=n.spawn??P,s=n.execSync??V;if(r==="win32"){const o=p.ComSpec||"cmd.exe";try{t(o,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{s('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{t("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:p}).unref()}catch{}i(e,"SIGTERM")}function ce(n){n.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(G(r.channel),console.log(`Upgrade channel: ${T()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${T()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const p=setInterval(()=>{process.stdout.write(`\r ${I[e%I.length]} Checking for updates...`),e++},80);let t;try{t=r.to??await k(),clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(o){clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${o instanceof Error?o.message:String(o)}`)),process.exit(1)}const s=w(i,t);if(s==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(t)} ${s==="major"?c.red("(major)"):s==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(s==="major"&&(console.log(c.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const o=$({input:process.stdin,output:process.stdout}),d=await o.question(` Type ${t} to confirm this major upgrade: `);if(o.close(),d.trim()!==t){console.log(c.gray(" Upgrade cancelled."));return}}try{await D({method:"upgrade.start",params:{targetVersion:t,confirmedMajor:s==="major"||r.yes===!0},timeoutMs:1e4})}catch(o){const d=O({cleanupStale:!0});if(d.running){const a=o&&typeof o=="object"&&"code"in o?o.code:null;throw process.platform==="win32"&&a==="ipc_access_denied"&&d.daemonVersion&&w(d.daemonVersion,"0.5.12")!=="none"?new Error("legacy_s4u_migration_required: The older Windows daemon is running in an inaccessible S4U session. The upgrade was not scheduled; inspect shennian status --json and request assisted migration. Do not force-stop the daemon.",{cause:o}):new Error("A Shennian daemon process is running, but its local control IPC did not accept the upgrade. Run shennian status --json and inspect ipc.state, the Client version, service account and logs. The upgrade was not scheduled; do not force-stop the daemon.",{cause:o})}const m=U(),u={fromVersion:i,targetVersion:t,oldDaemonPid:null,jobId:v()},g=j(u);await R(g,{fetchOwnedArtifact:b,sourceOrder:x()}),C(g,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${t}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function de(n,r,i,e={}){const p=a=>{n.sendEvent({type:"event",event:"upgrade",payload:a})},t=e.currentVersion??y();let s;try{p({state:"checking",machineId:"self"}),s=i??await k()}catch(a){const l=`Version check failed: ${a instanceof Error?a.message:String(a)}`;return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const o=w(t,s);if(o==="none")return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:t}}),{ok:!0,alreadyLatest:!0,version:t};if(!Y(o,e.confirmedMajor===!0))return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const d=v();let m;try{m=M();try{N(L("runtime","updater"))}catch(a){throw m.release(),a}}catch(a){const l=a instanceof Error?a.message:String(a);return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const u={from:t,to:s,jobId:d},g=async()=>{let a=!1;try{const l=U(),f={fromVersion:t,targetVersion:s,oldDaemonPid:process.pid,jobId:d,requireHandoffAck:!0},h=j(f);if(p({state:"downloading",machineId:"self",version:s,jobId:d}),await R(h,{fetchOwnedArtifact:b,sourceOrder:x()}),C(h,l.workerPath),S(f,{readiness:l}),await H(h),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const E=e.prepareForUpgrade?.(d)??{activeRunCount:0};return a=!0,q(h),p({state:"restarting",machineId:"self",...u,activeRunCount:E.activeRunCount}),{ok:!0,...u,resultPath:h.resultPath}}catch(l){a&&e.cancelUpgradePreparation?.();const f=l instanceof Error?l.message:String(l);return f!=="upgrade_waiting_for_idle"&&F(s,f),p({state:"failed",machineId:"self",error:f,jobId:d}),{ok:!1,error:f}}finally{m.release()}};return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:u}),e.waitForCompletion?g():(g(),{ok:!0,...u})}async function le(n,r,i={}){const e=await A(i.currentVersion);n.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{de as handleUpgradeStart,le as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,ce as registerUpgradeCommand,ie as restartCurrentDaemonAfterUpgrade};
@@ -1,3 +1,3 @@
1
- import l from"node:crypto";import I from"node:net";import{resolveDaemonIpcPaths as w}from"./paths.js";import{DAEMON_IPC_MAX_REQUEST_BYTES as D,DAEMON_IPC_MAX_RESPONSE_BYTES as E,DAEMON_IPC_VERSION as f,DaemonIpcError as o}from"./protocol.js";import{readDaemonIpcSecret as S}from"./secret.js";async function v(t){const m=t.paths??w(),u=S(m.secretFile),c=l.randomUUID(),d=`${JSON.stringify({version:f,requestId:c,auth:u,method:t.method,params:t.params})}
2
- `;if(Buffer.byteLength(d,"utf8")>D)throw new o("request_too_large","Daemon IPC request is too large.");const n=await new Promise((_,r)=>{const e=I.createConnection(m.endpoint);let s="";const h=setTimeout(()=>{e.destroy(),r(new o("ipc_timeout","Shennian daemon did not respond."))},t.timeoutMs??5e3),a=i=>{clearTimeout(h),e.destroy(),i()};e.setEncoding("utf8"),e.once("connect",()=>e.write(d)),e.on("data",i=>{if(s+=i,Buffer.byteLength(s,"utf8")>E){a(()=>r(new o("response_too_large","Daemon IPC response is too large.")));return}const p=s.indexOf(`
3
- `);p<0||a(()=>{try{_(JSON.parse(s.slice(0,p)))}catch{r(new o("invalid_response","Daemon IPC response is invalid."))}})}),e.once("error",()=>a(()=>r(new o("daemon_unavailable","Shennian daemon is not running."))))});if(n.requestId!==c||n.version!==f)throw new o("response_mismatch","Daemon IPC response did not match the request.");if(!n.ok)throw new o(n.error.code,n.error.message);return n.result}export{v as callDaemonIpc};
1
+ import _ from"node:crypto";import E from"node:net";import{resolveDaemonIpcPaths as I}from"./paths.js";import{DAEMON_IPC_MAX_REQUEST_BYTES as w,DAEMON_IPC_MAX_RESPONSE_BYTES as C,DAEMON_IPC_VERSION as f,DaemonIpcError as n}from"./protocol.js";import{readDaemonIpcSecret as S}from"./secret.js";async function v(e){const c=e.paths??I(),u=S(c.secretFile),m=_.randomUUID(),d=`${JSON.stringify({version:f,requestId:m,auth:u,method:e.method,params:e.params})}
2
+ `;if(Buffer.byteLength(d,"utf8")>w)throw new n("request_too_large","Daemon IPC request is too large.");const t=await new Promise((h,s)=>{const o=E.createConnection(c.endpoint);let i="";const l=setTimeout(()=>{o.destroy(),s(new n("ipc_timeout","Shennian daemon did not respond."))},e.timeoutMs??5e3),a=r=>{clearTimeout(l),o.destroy(),r()};o.setEncoding("utf8"),o.once("connect",()=>o.write(d)),o.on("data",r=>{if(i+=r,Buffer.byteLength(i,"utf8")>C){a(()=>s(new n("response_too_large","Daemon IPC response is too large.")));return}const p=i.indexOf(`
3
+ `);p<0||a(()=>{try{h(JSON.parse(i.slice(0,p)))}catch{s(new n("invalid_response","Daemon IPC response is invalid."))}})}),o.once("error",r=>a(()=>s(D(r))))});if(t.requestId!==m||t.version!==f)throw new n("response_mismatch","Daemon IPC response did not match the request.");if(!t.ok)throw new n(t.error.code,t.error.message);return t.result}function D(e){return e.code==="EACCES"||e.code==="EPERM"?new n("ipc_access_denied","This user cannot access the Shennian daemon IPC endpoint."):e.code==="ENOENT"||e.code==="ECONNREFUSED"?new n("daemon_unavailable","Shennian daemon IPC endpoint is unavailable."):new n("ipc_connection_failed","Could not connect to the Shennian daemon IPC endpoint.")}export{v as callDaemonIpc,D as classifyDaemonIpcConnectError};
@@ -1 +1 @@
1
- import o from"node:fs";import{getAcpLocalMetrics as g}from"../agents/acp/metrics.js";const u=["protocol.info","daemon.status","agent.list","agent.discover","agent.check","room.agent.bind-managed","agent.auth.completed","log.read","upgrade.start","upgrade.prepare-external","upgrade.cancel-external"],m=32*1024,y=64*1024;function _(t){return{"protocol.info":()=>{const e=t.getStatus();return{daemonVersion:t.daemonVersion,protocolVersion:t.protocolVersion,launcher:e.launcher,capabilities:[...u]}},"daemon.status":()=>t.getStatus(),"agent.list":()=>({agents:t.getAgents().map(e=>({type:e.type,...e.version?{version:e.version}:{},...e.runtimePlatform?{runtimePlatform:e.runtimePlatform}:{},...e.runtimeIsWsl!==void 0?{runtimeIsWsl:e.runtimeIsWsl}:{}})),acpMetrics:g()}),"agent.auth.completed":async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:"";if(!/^acp:[a-z0-9][a-z0-9._-]{0,63}$/.test(n))throw new Error("invalid_acp_agent");return{resumed:await t.completeAgentAuthentication?.(n)??!1}},"room.agent.bind-managed":async e=>{if(!e||typeof e!="object"||Array.isArray(e))throw new Error("invalid_managed_room_binding");if(!t.bindManagedRoom)throw new Error("managed_room_binding_unavailable");return t.bindManagedRoom(e)},"log.read":e=>p(t.getStatus().logFile,e),"upgrade.start":async e=>{if(!t.startUpgrade)throw new Error("upgrade_unavailable");return t.startUpgrade(e)},"upgrade.prepare-external":async e=>{if(!t.prepareExternalUpgrade)throw new Error("external_upgrade_unavailable");return t.prepareExternalUpgrade(e)},"upgrade.cancel-external":async()=>{if(!t.cancelExternalUpgrade)throw new Error("external_upgrade_unavailable");return t.cancelExternalUpgrade()}}}function p(t,e){const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},c=typeof n.limitBytes=="number"&&Number.isFinite(n.limitBytes)?n.limitBytes:m,d=Math.max(1,Math.min(Math.floor(c),y));let l=0;try{l=o.statSync(t).size}catch{return{text:"",start:0,end:0,nextBefore:null,eof:!0}}const f=typeof n.before=="number"&&Number.isFinite(n.before)?Math.floor(n.before):l,a=Math.max(0,Math.min(f,l)),r=Math.max(0,a-d);if(a===r)return{text:"",start:r,end:a,nextBefore:r>0?r:null,eof:r===0};const i=Buffer.allocUnsafe(a-r),s=o.openSync(t,"r");try{o.readSync(s,i,0,i.length,r)}finally{o.closeSync(s)}return{text:i.toString("utf8"),start:r,end:a,nextBefore:r>0?r:null,eof:r===0}}export{u as DAEMON_CONTROL_CAPABILITIES,_ as createDaemonControlHandlers,p as readDaemonLogPage};
1
+ import s from"node:fs";import{getAcpLocalMetrics as f}from"../agents/acp/metrics.js";const g=["protocol.info","daemon.status","daemon.shutdown","agent.list","agent.discover","agent.check","room.agent.bind-managed","agent.auth.completed","log.read","upgrade.start","upgrade.prepare-external","upgrade.cancel-external"],m=32*1024,y=64*1024;function _(r){return{"protocol.info":()=>{const e=r.getStatus();return{daemonVersion:r.daemonVersion,protocolVersion:r.protocolVersion,launcher:e.launcher,capabilities:[...g]}},"daemon.status":()=>r.getStatus(),"daemon.shutdown":(e,t)=>{const a=e&&typeof e=="object"&&!Array.isArray(e)?e.expectedPid:void 0;if(!Number.isSafeInteger(a)||a!==process.pid)throw new Error("daemon_instance_mismatch");if(!r.requestShutdown||!t.afterResponse)throw new Error("daemon_shutdown_unavailable");return t.afterResponse(r.requestShutdown),{accepted:!0,pid:process.pid}},"agent.list":()=>({agents:r.getAgents().map(e=>({type:e.type,...e.version?{version:e.version}:{},...e.runtimePlatform?{runtimePlatform:e.runtimePlatform}:{},...e.runtimeIsWsl!==void 0?{runtimeIsWsl:e.runtimeIsWsl}:{}})),acpMetrics:f()}),"agent.auth.completed":async e=>{const t=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:"";if(!/^acp:[a-z0-9][a-z0-9._-]{0,63}$/.test(t))throw new Error("invalid_acp_agent");return{resumed:await r.completeAgentAuthentication?.(t)??!1}},"room.agent.bind-managed":async e=>{if(!e||typeof e!="object"||Array.isArray(e))throw new Error("invalid_managed_room_binding");if(!r.bindManagedRoom)throw new Error("managed_room_binding_unavailable");return r.bindManagedRoom(e)},"log.read":e=>p(r.getStatus().logFile,e),"upgrade.start":async e=>{if(!r.startUpgrade)throw new Error("upgrade_unavailable");return r.startUpgrade(e)},"upgrade.prepare-external":async e=>{if(!r.prepareExternalUpgrade)throw new Error("external_upgrade_unavailable");return r.prepareExternalUpgrade(e)},"upgrade.cancel-external":async()=>{if(!r.cancelExternalUpgrade)throw new Error("external_upgrade_unavailable");return r.cancelExternalUpgrade()}}}function p(r,e){const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},a=typeof t.limitBytes=="number"&&Number.isFinite(t.limitBytes)?t.limitBytes:m,d=Math.max(1,Math.min(Math.floor(a),y));let i=0;try{i=s.statSync(r).size}catch{return{text:"",start:0,end:0,nextBefore:null,eof:!0}}const u=typeof t.before=="number"&&Number.isFinite(t.before)?Math.floor(t.before):i,o=Math.max(0,Math.min(u,i)),n=Math.max(0,o-d);if(o===n)return{text:"",start:n,end:o,nextBefore:n>0?n:null,eof:n===0};const c=Buffer.allocUnsafe(o-n),l=s.openSync(r,"r");try{s.readSync(l,c,0,c.length,n)}finally{s.closeSync(l)}return{text:c.toString("utf8"),start:n,end:o,nextBefore:n>0?n:null,eof:n===0}}export{g as DAEMON_CONTROL_CAPABILITIES,_ as createDaemonControlHandlers,p as readDaemonLogPage};
@@ -1,4 +1,4 @@
1
- import y from"node:crypto";import u from"node:fs";import h from"node:net";import{resolveDaemonIpcPaths as S}from"./paths.js";import{DAEMON_IPC_MAX_REQUEST_BYTES as w,DAEMON_IPC_MAX_RESPONSE_BYTES as I,DAEMON_IPC_VERSION as l,DaemonIpcError as f}from"./protocol.js";import{ensurePrivateRuntimeDirectory as g,loadOrCreateDaemonIpcSecret as P}from"./secret.js";function k(n,e=process.platform){const r=e==="darwin"?104:e==="linux"?107:null;if(r!==null&&Buffer.byteLength(n,"utf8")>r)throw new f("ipc_endpoint_too_long",`Daemon IPC endpoint exceeds the ${e} Unix socket path limit.`)}async function R(n){const e=n.paths??S();g(e.runtimeDirectory);const r=P(e.runtimeDirectory,e.secretFile);e.transport==="unix_socket"&&(k(e.endpoint),await v(e.endpoint));const o=h.createServer(t=>{t.setEncoding("utf8");let s="",c=!1;t.on("data",d=>{if(c)return;if(s+=d,Buffer.byteLength(s,"utf8")>w){c=!0,p(t,a("unknown","request_too_large","Daemon IPC request is too large."));return}const m=s.indexOf(`
2
- `);m<0||(c=!0,D(s.slice(0,m),r,n.handlers).then(_=>p(t,_)).catch(()=>p(t,a("unknown","internal_error","Daemon IPC request failed."))))}),t.on("error",()=>{})});await new Promise((t,s)=>{const c=d=>s(d);o.once("error",c),o.listen(x(e),()=>{o.off("error",c),t()})});let i=null;if(e.transport==="unix_socket")try{u.chmodSync(e.endpoint,384);const t=u.lstatSync(e.endpoint);if(!t.isSocket()||t.isSymbolicLink())throw new f("unsafe_ipc_endpoint","Daemon IPC endpoint was not created as a Unix socket.");i={dev:t.dev,ino:t.ino}}catch(t){throw await new Promise(s=>o.close(()=>s())),t}return{paths:e,close:async()=>{if(await new Promise(t=>o.close(()=>t())),e.transport==="unix_socket"&&i)try{const t=u.lstatSync(e.endpoint);t.isSocket()&&t.dev===i.dev&&t.ino===i.ino&&u.unlinkSync(e.endpoint)}catch{}}}}async function v(n){try{const e=u.lstatSync(n);if(!e.isSocket()||e.isSymbolicLink())throw new f("unsafe_ipc_endpoint","Refusing to replace a non-socket IPC endpoint.");if(await C(n))throw new f("daemon_ipc_in_use","Another Shennian daemon IPC server is already running.");u.unlinkSync(n)}catch(e){if(q(e))return;throw e}}function C(n){return new Promise(e=>{const r=h.createConnection(n),o=i=>{r.destroy(),e(i)};r.once("connect",()=>o(!0)),r.once("error",()=>o(!1)),r.setTimeout(300,()=>o(!1))})}async function D(n,e,r){let o;try{o=JSON.parse(n)}catch{return a("unknown","invalid_json","Daemon IPC request must be JSON.")}const i=typeof o.requestId=="string"?o.requestId.slice(0,128):"unknown";if(o.version!==l)return a(i,"unsupported_version","Unsupported daemon IPC version.");if(!E(o.auth,e))return a(i,"unauthorized","Daemon IPC authentication failed.");if(typeof o.method!="string"||!/^[a-z][a-z0-9_.-]{0,127}$/.test(o.method))return a(i,"invalid_method","Invalid daemon IPC method.");const t=r[o.method];if(!t)return a(i,"method_not_found","Daemon IPC method is not supported.");try{const s=await t(o.params,{requestId:i});return{version:l,requestId:i,ok:!0,result:s}}catch(s){return s instanceof f?a(i,s.code,s.message):a(i,"method_failed","Daemon IPC method failed.")}}function E(n,e){if(typeof n!="string")return!1;const r=Buffer.from(n),o=Buffer.from(e),i=Buffer.alloc(o.length);return r.copy(i,0,0,o.length),y.timingSafeEqual(i,o)&&r.length===o.length}function a(n,e,r){return{version:l,requestId:n,ok:!1,error:{code:e,message:r}}}function p(n,e){let r=JSON.stringify(e);Buffer.byteLength(`${r}
3
- `,"utf8")>I&&(r=JSON.stringify(a(e.requestId,"response_too_large","Daemon IPC response is too large."))),n.end(`${r}
4
- `)}function x(n){return n.transport==="unix_socket"?n.endpoint:{path:n.endpoint,readableAll:!1,writableAll:!1}}function q(n){return typeof n=="object"&&n!==null&&"code"in n&&n.code==="ENOENT"}export{k as assertUnixSocketEndpointLength,x as daemonIpcListenOptions,R as startDaemonIpcServer,E as timingSafeSecretEqual};
1
+ import w from"node:crypto";import u from"node:fs";import y from"node:net";import{resolveDaemonIpcPaths as S}from"./paths.js";import{DAEMON_IPC_MAX_REQUEST_BYTES as I,DAEMON_IPC_MAX_RESPONSE_BYTES as P,DAEMON_IPC_VERSION as p,DaemonIpcError as f}from"./protocol.js";import{ensurePrivateRuntimeDirectory as g,loadOrCreateDaemonIpcSecret as D}from"./secret.js";import{secureWindowsDaemonPipe as k}from"./windows-acl.js";function v(t,e=process.platform){const i=e==="darwin"?104:e==="linux"?107:null;if(i!==null&&Buffer.byteLength(t,"utf8")>i)throw new f("ipc_endpoint_too_long",`Daemon IPC endpoint exceeds the ${e} Unix socket path limit.`)}async function T(t){const e=t.paths??S();g(e.runtimeDirectory);const i=D(e.runtimeDirectory,e.secretFile);e.transport==="unix_socket"&&(v(e.endpoint),await C(e.endpoint));const o=y.createServer(n=>{n.setEncoding("utf8");let s="",a=!1;n.on("data",d=>{if(a)return;if(s+=d,Buffer.byteLength(s,"utf8")>I){a=!0,m(n,c("unknown","request_too_large","Daemon IPC request is too large."));return}const h=s.indexOf(`
2
+ `);if(h<0)return;a=!0;let _;x(s.slice(0,h),i,t.handlers,{afterResponse:l=>{_=l}}).then(l=>m(n,l,_)).catch(()=>m(n,c("unknown","internal_error","Daemon IPC request failed.")))}),n.on("error",()=>{})});await new Promise((n,s)=>{const a=d=>s(d);o.once("error",a),o.listen(O(e),()=>{o.off("error",a),n()})});let r=null;if(e.transport==="windows_named_pipe")try{k(e.endpoint)}catch(n){throw await new Promise(s=>o.close(()=>s())),n}if(e.transport==="unix_socket")try{u.chmodSync(e.endpoint,384);const n=u.lstatSync(e.endpoint);if(!n.isSocket()||n.isSymbolicLink())throw new f("unsafe_ipc_endpoint","Daemon IPC endpoint was not created as a Unix socket.");r={dev:n.dev,ino:n.ino}}catch(n){throw await new Promise(s=>o.close(()=>s())),n}return{paths:e,close:async()=>{if(await new Promise(n=>o.close(()=>n())),e.transport==="unix_socket"&&r)try{const n=u.lstatSync(e.endpoint);n.isSocket()&&n.dev===r.dev&&n.ino===r.ino&&u.unlinkSync(e.endpoint)}catch{}}}}async function C(t){try{const e=u.lstatSync(t);if(!e.isSocket()||e.isSymbolicLink())throw new f("unsafe_ipc_endpoint","Refusing to replace a non-socket IPC endpoint.");if(await E(t))throw new f("daemon_ipc_in_use","Another Shennian daemon IPC server is already running.");u.unlinkSync(t)}catch(e){if(N(e))return;throw e}}function E(t){return new Promise(e=>{const i=y.createConnection(t),o=r=>{i.destroy(),e(r)};i.once("connect",()=>o(!0)),i.once("error",()=>o(!1)),i.setTimeout(300,()=>o(!1))})}async function x(t,e,i,o={}){let r;try{r=JSON.parse(t)}catch{return c("unknown","invalid_json","Daemon IPC request must be JSON.")}const n=typeof r.requestId=="string"?r.requestId.slice(0,128):"unknown";if(r.version!==p)return c(n,"unsupported_version","Unsupported daemon IPC version.");if(!q(r.auth,e))return c(n,"unauthorized","Daemon IPC authentication failed.");if(typeof r.method!="string"||!/^[a-z][a-z0-9_.-]{0,127}$/.test(r.method))return c(n,"invalid_method","Invalid daemon IPC method.");const s=i[r.method];if(!s)return c(n,"method_not_found","Daemon IPC method is not supported.");try{const a=await s(r.params,{requestId:n,...o});return{version:p,requestId:n,ok:!0,result:a}}catch(a){return a instanceof f?c(n,a.code,a.message):c(n,"method_failed","Daemon IPC method failed.")}}function q(t,e){if(typeof t!="string")return!1;const i=Buffer.from(t),o=Buffer.from(e),r=Buffer.alloc(o.length);return i.copy(r,0,0,o.length),w.timingSafeEqual(r,o)&&i.length===o.length}function c(t,e,i){return{version:p,requestId:t,ok:!1,error:{code:e,message:i}}}function m(t,e,i){let o=JSON.stringify(e);Buffer.byteLength(`${o}
3
+ `,"utf8")>P&&(o=JSON.stringify(c(e.requestId,"response_too_large","Daemon IPC response is too large."))),t.end(`${o}
4
+ `,i)}function O(t){return t.transport==="unix_socket"?t.endpoint:{path:t.endpoint,readableAll:!1,writableAll:!1}}function N(t){return typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT"}export{v as assertUnixSocketEndpointLength,O as daemonIpcListenOptions,T as startDaemonIpcServer,q as timingSafeSecretEqual};
@@ -0,0 +1,43 @@
1
+ import{execFileSync as c}from"node:child_process";import{DaemonIpcError as t}from"./protocol.js";const r=/^\\\\\.\\pipe\\shennian-daemon-[a-f0-9]{20}$/;function p(e,n=c,i=process.platform){if(i!=="win32"||!r.test(e))throw new t("ipc_acl_setup_failed","Invalid Windows daemon IPC endpoint.");const o=a(e);try{const l=n("powershell.exe",["-NoProfile","-NonInteractive","-EncodedCommand",Buffer.from(o,"utf16le").toString("base64")],{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:15e3,windowsHide:!0});if(String(l).trim()!=="ACL_OK")throw new Error("unexpected_acl_result")}catch{throw new t("ipc_acl_setup_failed","Could not secure the Windows daemon IPC endpoint.")}}function a(e){if(!r.test(e))throw new t("ipc_acl_setup_failed","Invalid Windows daemon IPC endpoint.");return`$ErrorActionPreference = 'Stop'
2
+ $endpoint = '${e}'
3
+ $code = @'
4
+ using System;
5
+ using System.Runtime.InteropServices;
6
+ public static class ShennianPipeAcl {
7
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
8
+ public static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string sddl, uint revision, out IntPtr descriptor, out uint size);
9
+ [DllImport("advapi32.dll", SetLastError = true)]
10
+ public static extern bool GetSecurityDescriptorDacl(IntPtr descriptor, out bool present, out IntPtr dacl, out bool defaulted);
11
+ [DllImport("advapi32.dll", SetLastError = true)]
12
+ public static extern uint SetSecurityInfo(IntPtr handle, int objectType, uint securityInfo, IntPtr owner, IntPtr group, IntPtr dacl, IntPtr sacl);
13
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
14
+ public static extern IntPtr CreateFile(string name, uint access, uint shareMode, IntPtr security, uint creation, uint flags, IntPtr template);
15
+ [DllImport("kernel32.dll", SetLastError = true)]
16
+ public static extern bool CloseHandle(IntPtr handle);
17
+ [DllImport("kernel32.dll")]
18
+ public static extern IntPtr LocalFree(IntPtr pointer);
19
+ }
20
+ '@
21
+ Add-Type -TypeDefinition $code -ErrorAction Stop
22
+ $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
23
+ $sddl = 'D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;' + $sid + ')'
24
+ $descriptor = [IntPtr]::Zero
25
+ $size = [uint32]0
26
+ if (-not [ShennianPipeAcl]::ConvertStringSecurityDescriptorToSecurityDescriptor($sddl, 1, [ref]$descriptor, [ref]$size)) { exit 2 }
27
+ try {
28
+ $present = $false
29
+ $defaulted = $false
30
+ $dacl = [IntPtr]::Zero
31
+ if (-not [ShennianPipeAcl]::GetSecurityDescriptorDacl($descriptor, [ref]$present, [ref]$dacl, [ref]$defaulted) -or -not $present -or $dacl -eq [IntPtr]::Zero) { exit 3 }
32
+ $handle = [ShennianPipeAcl]::CreateFile($endpoint, 0x00060000, 3, [IntPtr]::Zero, 3, 0, [IntPtr]::Zero)
33
+ if ($handle -eq [IntPtr](-1)) { exit 4 }
34
+ try {
35
+ if ([ShennianPipeAcl]::SetSecurityInfo($handle, 6, 4, [IntPtr]::Zero, [IntPtr]::Zero, $dacl, [IntPtr]::Zero) -ne 0) { exit 5 }
36
+ } finally {
37
+ [void][ShennianPipeAcl]::CloseHandle($handle)
38
+ }
39
+ } finally {
40
+ [void][ShennianPipeAcl]::LocalFree($descriptor)
41
+ }
42
+ 'ACL_OK'
43
+ `}export{a as buildWindowsPipeAclScript,p as secureWindowsDaemonPipe};