shennian 0.4.31 → 0.4.32

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 (44) hide show
  1. package/dist/publish-build-manifest.json +50 -35
  2. package/dist/src/agents/acp/adapter.d.ts +4 -0
  3. package/dist/src/agents/acp/adapter.js +2 -2
  4. package/dist/src/agents/acp/client-services.d.ts +2 -1
  5. package/dist/src/agents/acp/client-services.js +2 -2
  6. package/dist/src/agents/acp/discovery.js +1 -1
  7. package/dist/src/agents/acp/process-client.js +1 -1
  8. package/dist/src/agents/claude.d.ts +1 -0
  9. package/dist/src/agents/claude.js +5 -5
  10. package/dist/src/agents/codex.js +3 -3
  11. package/dist/src/agents/command-spec.d.ts +1 -0
  12. package/dist/src/agents/command-spec.js +3 -3
  13. package/dist/src/commands/daemon-instance-lock.d.ts +21 -0
  14. package/dist/src/commands/daemon-instance-lock.js +1 -0
  15. package/dist/src/commands/daemon.d.ts +2 -2
  16. package/dist/src/commands/daemon.js +6 -6
  17. package/dist/src/index.js +3 -3
  18. package/dist/src/relay/client.d.ts +4 -0
  19. package/dist/src/relay/client.js +1 -1
  20. package/dist/src/room/activation-service.d.ts +2 -0
  21. package/dist/src/room/activation-service.js +4 -4
  22. package/dist/src/room/managed-room-binding.d.ts +2 -1
  23. package/dist/src/room/managed-room-binding.js +1 -1
  24. package/dist/src/room/room-binding-client.js +1 -1
  25. package/dist/src/session/handlers/chat.js +2 -2
  26. package/dist/src/session/handlers/project-fs.d.ts +11 -0
  27. package/dist/src/session/handlers/project-fs.js +1 -0
  28. package/dist/src/session/handlers/room-managed.js +1 -1
  29. package/dist/src/session/manager.d.ts +6 -1
  30. package/dist/src/session/manager.js +2 -2
  31. package/dist/src/session/project-fs-store.d.ts +43 -0
  32. package/dist/src/session/project-fs-store.js +1 -0
  33. package/dist/src/session/queue.js +4 -4
  34. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  35. package/node_modules/@shennian/wire/dist/index.js +1 -0
  36. package/node_modules/@shennian/wire/dist/methods.d.ts +3 -1
  37. package/node_modules/@shennian/wire/dist/methods.js +2 -0
  38. package/node_modules/@shennian/wire/dist/project-fs.d.ts +146 -0
  39. package/node_modules/@shennian/wire/dist/project-fs.js +462 -0
  40. package/node_modules/@shennian/wire/dist/project.d.ts +2 -0
  41. package/node_modules/@shennian/wire/dist/project.js +2 -0
  42. package/node_modules/@shennian/wire/dist/session-sync.d.ts +8 -1
  43. package/node_modules/@shennian/wire/dist/session-sync.js +1 -0
  44. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
- import{spawn as k,spawnSync as b}from"node:child_process";import p from"node:fs";import j from"node:crypto";import F from"node:os";import s from"node:path";const N={claude:[{command:"claude",args:[],display:"claude"}],codex:[{command:"codex",args:[],display:"codex"}],workbuddy:[{command:"codebuddy",args:[],display:"WorkBuddy"}],gemini:[{command:"gemini",args:[],display:"gemini"}],cursor:[{command:"agent",args:[],display:"agent"},{command:"cursor",args:["agent"],display:"cursor agent"}],openclaw:[],opencode:[{command:"opencode",args:[],display:"opencode"}],pi:[{command:"shennian",args:[],display:"shennian"}],"pi-coding-agent":[{command:"pi",args:[],display:"pi"}],manager:[{command:"shennian",args:["manager"],display:"shennian manager"}]},m=new Map,f=new Map,h=new Map,u=new Map;function X(){m.clear(),f.clear(),h.clear(),u.clear()}function c(){return process.platform}function R(n){return n.split(/\r?\n/).map(e=>e.trim()).filter(Boolean)}function C(n){if(s.basename(n)!==n)return[n];const e=F.homedir();if(c()==="win32"){const o=s.win32,r=s.extname(n)?[n]:[n,`${n}.cmd`,`${n}.exe`,`${n}.bat`],a=process.env.APPDATA||o.join(e,"AppData","Roaming"),l=process.env.LOCALAPPDATA||o.join(e,"AppData","Local"),v=[o.join(e,".shennian","node"),o.join("C:\\","nvm4w","nodejs"),o.join(l,"npm-global"),o.join(a,"npm"),o.join(l,"pnpm"),o.join(e,"scoop","shims"),o.join("C:\\","Program Files","nodejs")].flatMap(w=>r.map(g=>o.join(w,g)));if(n==="codebuddy"){const w=[o.join(l,"Programs","WorkBuddy"),o.join(l,"WorkBuddy"),o.join("C:\\","Program Files","WorkBuddy")];for(const g of w)for(const E of["codebuddy.exe","codebuddy.cmd","codebuddy"])v.push(o.join(g,"resources","app.asar.unpacked","cli","bin",E))}return v}const i=[s.join(e,".npm-global","bin"),s.join(e,".local","bin"),s.join(e,".bun","bin"),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin"].map(o=>s.join(o,n));return n==="claude"&&c()==="darwin"&&i.push(...V(e)),n==="codebuddy"&&c()==="darwin"&&i.push("/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy",s.join(e,"Applications","WorkBuddy.app","Contents","Resources","app.asar.unpacked","cli","bin","codebuddy")),i}function V(n){const e=[s.join(n,"Library","Application Support","Claude","claude-code")],t=[];for(const i of e){let o;try{o=p.readdirSync(i,{withFileTypes:!0}).filter(r=>r.isDirectory()).map(r=>r.name).sort((r,a)=>a.localeCompare(r,void 0,{numeric:!0}))}catch{continue}for(const r of o)t.push(s.join(i,r,"claude.app","Contents","MacOS","claude"))}return t}function P(n,e){const t=n?.split(s.delimiter).filter(Boolean)??[];if(c()==="win32"){if(e&&s.basename(e)!==e){const i=s.win32.dirname(e);t.includes(i)||t.unshift(i)}for(const i of C("shennian")){const o=s.win32.dirname(i);t.includes(o)||t.push(o)}return t.join(s.delimiter)}if(e&&s.basename(e)!==e){const i=s.dirname(e);t.includes(i)||t.unshift(i)}for(const i of C("shennian")){const o=s.dirname(i);t.includes(o)||t.push(o)}return t.join(s.delimiter)}function d(n){if(h.has(n))return h.get(n)??[];const e=c()==="win32",i=b(e?"where":"which",[n],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,windowsHide:!0,env:{...process.env,PATH:e?process.env.PATH:P(process.env.PATH,n)}}),r=[...i.status===0?R(i.stdout??""):[]];for(const a of C(n))p.existsSync(a)&&!r.includes(a)&&r.push(a);return h.set(n,r),r}function A(){return process.env.ComSpec||"cmd.exe"}function $(n){const e=s.extname(n).toLowerCase();return e===".exe"||e===".com"}function x(n){const e=s.extname(n).toLowerCase();return e===".cmd"||e===".bat"}function S(n){const e=s.extname(n).toLowerCase();return e===".js"||e===".mjs"||e===".cjs"}function W(){return d("node.exe")[0]??d("node")[0]??"node"}function B(n){return`"${n.replace(/"/g,'""')}"`}function y(n){return`'${n.replace(/'/g,"'\\''")}'`}function z(n){if(!n)return;const e=n.replace(/\//g,"\\"),t=e.match(/^([A-Za-z]):\\(.*)$/);if(t){const[,i,o]=t,r=o.replace(/\\/g,"/");return`/mnt/${i.toLowerCase()}/${r}`}if(e.startsWith("\\\\wsl$\\"))return`/${e.replace(/^\\\\wsl\$\\[^\\]+\\?/,"").replace(/\\/g,"/")}`;if(n.startsWith("/"))return n}function H(n){const e=n.trim();if(!e)throw new Error("Command string is empty");if(/[\\/]/.test(e)||/^[A-Za-z]:/.test(e))return x(e)?{kind:"cmd-shim",path:e}:{kind:"direct",path:e};const i=d(e),o=i.find($);if(o)return{kind:"direct",path:o};const r=i.find(x);return r?{kind:"cmd-shim",path:r}:{kind:"direct",path:e}}function L(n){if(u.has(n))return u.get(n)??!1;const e=d("wsl.exe")[0]??d("wsl")[0];if(!e)return u.set(n,!1),!1;const i=b(e,["-e","sh","-lc",`command -v ${y(n)} >/dev/null 2>&1`],{stdio:"ignore",timeout:5e3,windowsHide:!0}).status===0;return u.set(n,i),i}function Y(n){if(m.has(n))return m.get(n)??!1;const e=d(n).length>0||c()==="win32"&&L(n);return m.set(n,e),e}function nn(n){const e=d(n)[0];if(!e)return null;try{const t=p.realpathSync(e),i=p.statSync(t,{bigint:!0}),o=j.createHash("sha256").update(t).digest("hex"),r=j.createHash("sha256").update(`${i.dev}:${i.ino}:${i.size}:${i.mtimeNs}`).digest("hex");return{pathHash:o,fingerprint:r}}catch{return null}}function I(n){const e=d(n.command),t=e.find($);if(t)return{...n,kind:"native",path:t};const i=e.find(x);if(i)return{...n,kind:"cmd-shim",path:i};if(L(n.command)){const o=d("wsl.exe")[0]??d("wsl")[0];if(o)return{...n,kind:"wsl-bridge",path:o}}return null}function en(n){if(f.has(n))return f.get(n)??null;const e=N[n].map(t=>{if(c()==="win32")return I(t);const i=d(t.command)[0];return i?{...t,kind:"native",path:i}:null}).find(Boolean)??null;return f.set(n,e),e}function D(n,e,t){if(n.kind==="native")return c()==="win32"&&S(n.path)?{command:W(),args:[n.path,...n.args,...e],cwd:t}:{command:n.path,args:[...n.args,...e],cwd:t};if(n.kind==="cmd-shim"){const r=[n.path,...n.args,...e].map(B).join(" ");return{command:A(),args:["/d","/s","/c",`"${r}"`],cwd:t}}const i=t?z(t):void 0;if(t&&!i)throw new Error(`Cannot map Windows path to WSL workDir: ${t}`);const o=[i?`cd ${y(i)}`:"",`exec ${[n.command,...n.args,...e].map(y).join(" ")}`].filter(Boolean).join(" && ");return{command:n.path,args:["-e","sh","-lc",o],cwd:void 0}}function O(n,e,t={}){const i=D(n,e,t.cwd);return k(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function tn(n,e,t={}){return O(n,e,t)}function T(n,e,t={}){const i=D(n,e,t.cwd);return b(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function M(n){const e=[];let t="",i=!1,o=!1;for(const r of n){if(r==="'"&&!o){i=!i;continue}if(r==='"'&&!i){o=!o;continue}if(r===" "&&!i&&!o){t&&(e.push(t),t="");continue}t+=r}return t&&e.push(t),e}function q(n,e,t){const i=M(n),o=i[0];if(!o)throw new Error("Command string is empty");const r=i.slice(1);if(c()!=="win32")return{command:o,args:[...r,...e],cwd:t};const a=H(o);return a.kind==="cmd-shim"?{command:A(),args:["/d","/s","/c",`"${[a.path,...r,...e].map(B).join(" ")}"`],cwd:t}:S(a.path)?{command:W(),args:[a.path,...r,...e],cwd:t}:{command:a.path,args:[...r,...e],cwd:t}}function on(n,e,t={}){const i=q(n,e,t.cwd),o=M(n),r=c()==="win32"&&o[0]?H(o[0]):null;return k(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,env:{...t.env,PATH:P(t.env?.PATH??process.env.PATH,o[0])},...c()==="win32"&&r?.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function U(n,e="--version"){const t=T(n,[e],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5e3});return t.status!==0?void 0:`${t.stdout??""}
2
- ${t.stderr??""}`.trim().match(/\d+\.\d+[\w.-]*/)?.[0]}function rn(n,e){if(n!=="cursor")return U(e);const t=T(e,["about"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5e3});return t.status!==0?void 0:`${t.stdout??""}
3
- ${t.stderr??""}`.match(/^\s*CLI Version\s+(\d+\.\d+(?:\.\d+)?[\w.-]*)\s*$/im)?.[1]}export{q as buildCommandStringLaunchSpec,D as buildLaunchSpec,Y as commandExists,rn as getBuiltinAgentVersion,U as getCommandVersion,c as getProcessPlatform,M as parseCommandString,X as resetCommandSpecCache,en as resolveBuiltinCommand,nn as resolveCommandFileIdentity,tn as spawnAgentCommand,on as spawnCommandString,O as spawnResolvedCommand,T as spawnResolvedCommandSync,z as windowsPathToWsl};
1
+ import{spawn as k,spawnSync as C}from"node:child_process";import p from"node:fs";import j from"node:crypto";import E from"node:os";import s from"node:path";const N={claude:[{command:"claude",args:[],display:"claude"}],codex:[{command:"codex",args:[],display:"codex"}],workbuddy:[{command:"codebuddy",args:[],display:"WorkBuddy"}],gemini:[{command:"gemini",args:[],display:"gemini"}],cursor:[{command:"agent",args:[],display:"agent"},{command:"cursor",args:["agent"],display:"cursor agent"}],openclaw:[],opencode:[{command:"opencode",args:[],display:"opencode"}],pi:[{command:"shennian",args:[],display:"shennian"}],"pi-coding-agent":[{command:"pi",args:[],display:"pi"}],manager:[{command:"shennian",args:["manager"],display:"shennian manager"}]},m=new Map,f=new Map,h=new Map,u=new Map;function X(){m.clear(),f.clear(),h.clear(),u.clear()}function c(){return process.platform}function R(n){return n.split(/\r?\n/).map(e=>e.trim()).filter(Boolean)}function b(n){if(s.basename(n)!==n)return[n];const e=E.homedir();if(c()==="win32"){const o=s.win32,r=s.extname(n)?[n]:[n,`${n}.cmd`,`${n}.exe`,`${n}.bat`],a=process.env.APPDATA||o.join(e,"AppData","Roaming"),l=process.env.LOCALAPPDATA||o.join(e,"AppData","Local"),y=[o.join(e,".shennian","node"),o.join("C:\\","nvm4w","nodejs"),o.join(l,"npm-global"),o.join(a,"npm"),o.join(l,"pnpm"),o.join(e,"scoop","shims"),o.join("C:\\","Program Files","nodejs")].flatMap(w=>r.map(g=>o.join(w,g)));if(n==="codebuddy"){const w=[o.join(l,"Programs","WorkBuddy"),o.join(l,"WorkBuddy"),o.join("C:\\","Program Files","WorkBuddy")];for(const g of w)for(const F of["codebuddy.exe","codebuddy.cmd","codebuddy"])y.push(o.join(g,"resources","app.asar.unpacked","cli","bin",F))}return y}const i=[s.join(e,".npm-global","bin"),s.join(e,".local","bin"),s.join(e,".bun","bin"),"/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin"].map(o=>s.join(o,n));return n==="claude"&&c()==="darwin"&&i.push(...V(e)),n==="codebuddy"&&c()==="darwin"&&i.push("/Applications/WorkBuddy.app/Contents/Resources/app.asar.unpacked/cli/bin/codebuddy",s.join(e,"Applications","WorkBuddy.app","Contents","Resources","app.asar.unpacked","cli","bin","codebuddy")),i}function V(n){const e=[s.join(n,"Library","Application Support","Claude","claude-code")],t=[];for(const i of e){let o;try{o=p.readdirSync(i,{withFileTypes:!0}).filter(r=>r.isDirectory()).map(r=>r.name).sort((r,a)=>a.localeCompare(r,void 0,{numeric:!0}))}catch{continue}for(const r of o)t.push(s.join(i,r,"claude.app","Contents","MacOS","claude"))}return t}function P(n,e){const t=n?.split(s.delimiter).filter(Boolean)??[];if(c()==="win32"){if(e&&s.basename(e)!==e){const i=s.win32.dirname(e);t.includes(i)||t.unshift(i)}for(const i of b("shennian")){const o=s.win32.dirname(i);t.includes(o)||t.push(o)}return t.join(s.delimiter)}if(e&&s.basename(e)!==e){const i=s.dirname(e);t.includes(i)||t.unshift(i)}for(const i of b("shennian")){const o=s.dirname(i);t.includes(o)||t.push(o)}return t.join(s.delimiter)}function d(n){if(h.has(n))return h.get(n)??[];const e=c()==="win32",i=C(e?"where":"which",[n],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:3e3,windowsHide:!0,env:{...process.env,PATH:e?process.env.PATH:P(process.env.PATH,n)}}),r=[...i.status===0?R(i.stdout??""):[]];for(const a of b(n))p.existsSync(a)&&!r.includes(a)&&r.push(a);return h.set(n,r),r}function A(){return process.env.ComSpec||"cmd.exe"}function $(n){const e=s.extname(n).toLowerCase();return e===".exe"||e===".com"}function x(n){const e=s.extname(n).toLowerCase();return e===".cmd"||e===".bat"}function S(n){const e=s.extname(n).toLowerCase();return e===".js"||e===".mjs"||e===".cjs"}function W(){return d("node.exe")[0]??d("node")[0]??"node"}function B(n){return`"${n.replace(/"/g,'""')}"`}function v(n){return`'${n.replace(/'/g,"'\\''")}'`}function z(n){if(!n)return;const e=n.replace(/\//g,"\\"),t=e.match(/^([A-Za-z]):\\(.*)$/);if(t){const[,i,o]=t,r=o.replace(/\\/g,"/");return`/mnt/${i.toLowerCase()}/${r}`}if(e.startsWith("\\\\wsl$\\"))return`/${e.replace(/^\\\\wsl\$\\[^\\]+\\?/,"").replace(/\\/g,"/")}`;if(n.startsWith("/"))return n}function H(n){const e=n.trim();if(!e)throw new Error("Command string is empty");if(/[\\/]/.test(e)||/^[A-Za-z]:/.test(e))return x(e)?{kind:"cmd-shim",path:e}:{kind:"direct",path:e};const i=d(e),o=i.find($);if(o)return{kind:"direct",path:o};const r=i.find(x);return r?{kind:"cmd-shim",path:r}:{kind:"direct",path:e}}function L(n){if(u.has(n))return u.get(n)??!1;const e=d("wsl.exe")[0]??d("wsl")[0];if(!e)return u.set(n,!1),!1;const i=C(e,["-e","sh","-lc",`command -v ${v(n)} >/dev/null 2>&1`],{stdio:"ignore",timeout:5e3,windowsHide:!0}).status===0;return u.set(n,i),i}function Y(n){if(m.has(n))return m.get(n)??!1;const e=d(n).length>0||c()==="win32"&&L(n);return m.set(n,e),e}function nn(n){const e=d(n)[0];if(!e)return null;try{const t=p.realpathSync(e),i=p.statSync(t,{bigint:!0}),o=j.createHash("sha256").update(t).digest("hex"),r=j.createHash("sha256").update(`${i.dev}:${i.ino}:${i.size}:${i.mtimeNs}`).digest("hex");return{pathHash:o,fingerprint:r}}catch{return null}}function en(n){return d(n)[0]??null}function I(n){const e=d(n.command),t=e.find($);if(t)return{...n,kind:"native",path:t};const i=e.find(x);if(i)return{...n,kind:"cmd-shim",path:i};if(L(n.command)){const o=d("wsl.exe")[0]??d("wsl")[0];if(o)return{...n,kind:"wsl-bridge",path:o}}return null}function tn(n){if(f.has(n))return f.get(n)??null;const e=N[n].map(t=>{if(c()==="win32")return I(t);const i=d(t.command)[0];return i?{...t,kind:"native",path:i}:null}).find(Boolean)??null;return f.set(n,e),e}function D(n,e,t){if(n.kind==="native")return c()==="win32"&&S(n.path)?{command:W(),args:[n.path,...n.args,...e],cwd:t}:{command:n.path,args:[...n.args,...e],cwd:t};if(n.kind==="cmd-shim"){const r=[n.path,...n.args,...e].map(B).join(" ");return{command:A(),args:["/d","/s","/c",`"${r}"`],cwd:t}}const i=t?z(t):void 0;if(t&&!i)throw new Error(`Cannot map Windows path to WSL workDir: ${t}`);const o=[i?`cd ${v(i)}`:"",`exec ${[n.command,...n.args,...e].map(v).join(" ")}`].filter(Boolean).join(" && ");return{command:n.path,args:["-e","sh","-lc",o],cwd:void 0}}function O(n,e,t={}){const i=D(n,e,t.cwd);return k(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function on(n,e,t={}){return O(n,e,t)}function T(n,e,t={}){const i=D(n,e,t.cwd);return C(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,...c()==="win32"&&n.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function M(n){const e=[];let t="",i=!1,o=!1;for(const r of n){if(r==="'"&&!o){i=!i;continue}if(r==='"'&&!i){o=!o;continue}if(r===" "&&!i&&!o){t&&(e.push(t),t="");continue}t+=r}return t&&e.push(t),e}function q(n,e,t){const i=M(n),o=i[0];if(!o)throw new Error("Command string is empty");const r=i.slice(1);if(c()!=="win32")return{command:o,args:[...r,...e],cwd:t};const a=H(o);return a.kind==="cmd-shim"?{command:A(),args:["/d","/s","/c",`"${[a.path,...r,...e].map(B).join(" ")}"`],cwd:t}:S(a.path)?{command:W(),args:[a.path,...r,...e],cwd:t}:{command:a.path,args:[...r,...e],cwd:t}}function rn(n,e,t={}){const i=q(n,e,t.cwd),o=M(n),r=c()==="win32"&&o[0]?H(o[0]):null;return k(i.command,i.args,{...t,cwd:i.cwd,windowsHide:t.windowsHide??!0,env:{...t.env,PATH:P(t.env?.PATH??process.env.PATH,o[0])},...c()==="win32"&&r?.kind==="cmd-shim"?{windowsVerbatimArguments:!0}:{}})}function U(n,e="--version"){const t=T(n,[e],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5e3});return t.status!==0?void 0:`${t.stdout??""}
2
+ ${t.stderr??""}`.trim().match(/\d+\.\d+[\w.-]*/)?.[0]}function sn(n,e){if(n!=="cursor")return U(e);const t=T(e,["about"],{encoding:"utf-8",stdio:["ignore","pipe","pipe"],timeout:5e3});return t.status!==0?void 0:`${t.stdout??""}
3
+ ${t.stderr??""}`.match(/^\s*CLI Version\s+(\d+\.\d+(?:\.\d+)?[\w.-]*)\s*$/im)?.[1]}export{q as buildCommandStringLaunchSpec,D as buildLaunchSpec,Y as commandExists,sn as getBuiltinAgentVersion,U as getCommandVersion,c as getProcessPlatform,M as parseCommandString,X as resetCommandSpecCache,tn as resolveBuiltinCommand,nn as resolveCommandFileIdentity,en as resolveCommandFilePath,on as spawnAgentCommand,rn as spawnCommandString,O as spawnResolvedCommand,T as spawnResolvedCommandSync,z as windowsPathToWsl};
@@ -0,0 +1,21 @@
1
+ export type DaemonInstanceLock = {
2
+ acquired: true;
3
+ release: () => void;
4
+ } | {
5
+ acquired: false;
6
+ ownerPid: number | null;
7
+ };
8
+ /**
9
+ * Hold an atomic, cross-process lock for the entire daemon lifetime. The pid
10
+ * file remains the public status record; this private lock closes the
11
+ * read-before-write race when Desktop, a terminal, and an OS service start at
12
+ * the same time.
13
+ */
14
+ export declare function acquireDaemonInstanceLock(options: {
15
+ lockPath: string;
16
+ instanceId: string;
17
+ pid?: number;
18
+ isProcessAlive?: (pid: number) => boolean;
19
+ now?: () => number;
20
+ malformedStaleAfterMs?: number;
21
+ }): DaemonInstanceLock;
@@ -0,0 +1 @@
1
+ import t from"node:fs";import u from"node:path";function i(e){try{const r=JSON.parse(t.readFileSync(e,"utf8"));return Number.isInteger(r.pid)&&Number(r.pid)>0&&typeof r.instanceId=="string"?{pid:Number(r.pid),instanceId:r.instanceId}:null}catch{return null}}function f(e){try{return process.kill(e,0),!0}catch(r){return!!(r&&typeof r=="object"&&"code"in r&&r.code==="EPERM")}}function S(e){const r=e.pid??process.pid,l=e.isProcessAlive??f,o=e.now??Date.now,s=e.malformedStaleAfterMs??5e3;t.mkdirSync(u.dirname(e.lockPath),{recursive:!0});for(let d=0;d<3;d+=1)try{const c=t.openSync(e.lockPath,"wx",384);t.writeFileSync(c,JSON.stringify({pid:r,instanceId:e.instanceId})),t.fsyncSync(c);let a=!1;return{acquired:!0,release:()=>{if(a)return;a=!0,t.closeSync(c);const n=i(e.lockPath);if(!(n?.pid!==r||n.instanceId!==e.instanceId))try{t.unlinkSync(e.lockPath)}catch{}}}}catch(c){if((c&&typeof c=="object"&&"code"in c?c.code:null)!=="EEXIST")throw c;const n=i(e.lockPath);if(n&&l(n.pid))return{acquired:!1,ownerPid:n.pid};if(!n)try{if(Math.max(0,o()-t.statSync(e.lockPath).mtimeMs)<s)return{acquired:!1,ownerPid:null}}catch{}try{t.unlinkSync(e.lockPath)}catch{}}return{acquired:!1,ownerPid:i(e.lockPath)?.pid??null}}export{S as acquireDaemonInstanceLock};
@@ -107,11 +107,11 @@ export declare function saveEnvSnapshot(): void;
107
107
  export declare function startDaemonProcess(opts?: {
108
108
  quiet?: boolean;
109
109
  }): void;
110
- export declare function shouldReplaceRunningDaemon(status: Pick<DaemonStatus, 'pid' | 'running' | 'adopted'>, record?: {
110
+ export declare function shouldReplaceRunningDaemon(status: Pick<DaemonStatus, 'pid' | 'running' | 'adopted' | 'launcher'>, record?: {
111
111
  version?: string;
112
112
  } | null, currentVersion?: string): boolean;
113
113
  export declare function isAutoStartServicePresent(platform?: Platform): boolean;
114
- export declare function canReuseRunningDaemonForStart(status: Pick<DaemonStatus, 'pid' | 'running' | 'stale' | 'remoteAccessDisabled' | 'adopted'>, opts: {
114
+ export declare function canReuseRunningDaemonForStart(status: Pick<DaemonStatus, 'pid' | 'running' | 'stale' | 'remoteAccessDisabled' | 'adopted' | 'launcher'>, opts: {
115
115
  api?: string;
116
116
  }, servicePresent: boolean, record?: {
117
117
  version?: string;
@@ -1,7 +1,7 @@
1
- import c from"chalk";import o from"node:fs";import a from"node:path";import d from"node:os";import{execSync as u,spawn as ge}from"node:child_process";import{randomUUID as B}from"node:crypto";import{fileURLToPath as ye}from"node:url";import{getShennianDir as we,loadConfig as V,resolveShennianPath as S,saveConfig as Ee}from"../config/index.js";import{buildAugmentedPath as ve}from"../env-path.js";import{buildWindowsLauncherCommand as Pe,buildWindowsScheduledTaskCommands as Ae,buildWindowsScheduledTaskXml as Ne,buildWindowsStartupVbs as Ie,escapeXml as x,isWindowsCmdScript as De,quoteCmdArg as G,quoteSystemdArg as ke,suspendWindowsScheduledTask as _e}from"./daemon-windows.js";import{waitForDaemonReadyStatus as T}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as be,isShennianRunServiceCommand as Re}from"./daemon-process.js";import{getCurrentVersion as K}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as Rn,buildWindowsScheduledTaskCommands as xn,buildWindowsScheduledTaskSuspendCommands as Tn,buildWindowsScheduledTaskXml as Ln,buildWindowsStartupVbs as On,suspendWindowsScheduledTask as Cn}from"./daemon-windows.js";import{isShennianRunServiceCommand as $n}from"./daemon-process.js";const xe=a.dirname(ye(import.meta.url)),h=we(),y=S("daemon.pid"),m=S("daemon.log"),L=S("remote-access.disabled"),k=S("daemon-launcher.json"),J=a.resolve(xe,"../../bin/shennian.js"),q=process.execPath,X=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","ELECTRON_RUN_AS_NODE","SHENNIAN_DESKTOP_CLI_SCRIPT","SHENNIAN_DESKTOP_CLI_BRIDGE","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_ACCOUNT_NAMESPACE_ID","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function Te(e){return X.has(e)}function p(){const e=d.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function O(e){const n=e.replace(/\\/g,"/").toLowerCase(),r=d.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(r.endsWith("/")?r:`${r}/`)}function Le(e){return process.env.ELECTRON_RUN_AS_NODE==="1"&&e.desktopBridgePath&&o.existsSync(e.scriptPath)&&o.existsSync(e.desktopBridgePath)?{command:e.nodeExec,args:[e.desktopBridgePath,e.scriptPath,"run-service"],mode:"direct"}:o.existsSync(e.scriptPath)&&(process.env.SHENNIAN_E2E_ISOLATED_DAEMON==="1"||!O(e.scriptPath))?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!O(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function Oe(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const t=JSON.parse(n),s=Number(t.pid);return!Number.isInteger(s)||s<=0?null:{pid:s,...typeof t.instanceId=="string"?{instanceId:t.instanceId}:{},...typeof t.version=="string"?{version:t.version}:{},...t.launcher==="desktop-managed"||t.launcher==="global-cli"||t.launcher==="unknown"?{launcher:t.launcher}:{},...typeof t.startedAt=="string"?{startedAt:t.startedAt}:{},...t.adopted===!0?{adopted:!0}:{}}}catch{return null}const r=parseInt(n,10);return isNaN(r)?null:{pid:r}}function A(){try{return Oe(o.readFileSync(y,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function Y(e,n=3e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!v(e)}function Ce(){return o.existsSync(L)}function He(){try{o.unlinkSync(L)}catch{}}function $e(e,n){const r=a.dirname(e),t=`${e}.${process.pid}.${B()}.tmp`;o.mkdirSync(r,{recursive:!0});let s=null;try{s=o.openSync(t,"w",384),o.writeSync(s,n,void 0,"utf8"),o.fsyncSync(s),o.closeSync(s),s=null,o.renameSync(t,e);try{const i=o.openSync(r,"r");try{o.fsyncSync(i)}finally{o.closeSync(i)}}catch{}}finally{s!==null&&o.closeSync(s);try{o.unlinkSync(t)}catch{}}}function Me(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??L,t=e.platform??p(),s=e.servicePresent??(H()&&U(t)),i=e.execCommand??(l=>{u(l,{stdio:"pipe",windowsHide:!0})});if($e(r,n),!s)return{disabledAt:n,autoStartDisabled:!0};try{return i(t==="darwin"?`launchctl disable "${P()}"`:t==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${D}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(l){return{disabledAt:n,autoStartDisabled:!1,autoStartError:l instanceof Error?l.message:String(l)}}}function Ue(e={}){const n=e.platform??p();if(!(e.servicePresent??(H()&&U(n))))return{serviceStopped:!0};const t=e.execCommand??(s=>{u(s,{stdio:"pipe",windowsHide:!0})});try{return t(n==="darwin"?`launchctl bootout "${P()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${D}"`),{serviceStopped:!0}}catch(s){return{serviceStopped:!1,error:s instanceof Error?s.message:String(s)}}}function z(){return process.env.SHENNIAN_DESKTOP_CLI_SCRIPT?"desktop-managed":"global-cli"}function je(){return`${process.pid}-${Date.now()}-${B()}`}function We(e,n,r={}){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(y,JSON.stringify({pid:e,instanceId:n,...r.version?{version:r.version}:{},launcher:r.launcher??z(),...r.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function Dn(e,n){const r=A();if(!(r?.pid!==e||r.instanceId!==n))try{o.unlinkSync(y)}catch{}}function Q(e,n=z(),r){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...r?{instanceId:r}:{},updatedAt:new Date().toISOString()},null,2))}function Fe(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function Be(e){e&&Q(e)}function Ve(e,n){if(!e||!n)return"unknown";try{const r=JSON.parse(o.readFileSync(k,"utf-8"));if(r.pid!==e)return"unknown";if(r.launcher==="desktop-managed"||r.launcher==="global-cli")return r.launcher}catch{}return C(e)}function C(e){if(p()==="win32")return"unknown";try{const n=u(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/cli-bridge.js")||n.includes("/Resources/daemon/")||n.includes("\\resources\\daemon\\"))return"desktop-managed";if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function Z(e=process.pid){if(p()==="win32")return be(e);try{return u("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(r=>{const t=r.trim().match(/^(\d+)\s+(.+)$/);if(!t)return null;const s=Number(t[1]),i=t[2];return!Number.isInteger(s)||s===e?null:Re(i)?s:null}).filter(r=>typeof r=="number")}catch{return[]}}function ee(e=process.env,n=h,r=d.tmpdir(),t=a.join(d.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||a.resolve(n)===a.resolve(t))return!1;const s=a.relative(a.resolve(r),a.resolve(n));return s!==""&&s!==".."&&!s.startsWith(`..${a.sep}`)&&!a.isAbsolute(s)||a.resolve(n)===a.resolve(e.SHENNIAN_HOME)}function ne(e=process.env,n=h){return!ee(e,n)}function H(e=process.env,n=h){return!ee(e,n)}function f(e={}){let n=A(),r=n?.pid??null,t=r!==null&&v(r),s=r!==null&&!t,i=n?.adopted===!0;const l=V();if(s&&e.cleanupStale){try{o.unlinkSync(y)}catch{}n=null,r=null,t=!1,s=!1}if(!t&&ne()){const E=Z()[0];if(E&&(r=E,t=!0,s=!1,i=!0,e.cleanupStale)){const F=je();We(E,F,{launcher:C(E),adopted:!0}),Q(E,C(E)),n={pid:E,instanceId:F,adopted:!0}}}return{running:t,pid:r,stale:s,remoteAccessDisabled:Ce(),launcher:Ve(r,t),platform:p(),shennianDir:h,pidFile:y,logFile:m,...n?.instanceId?{instanceId:n.instanceId}:{},...i?{adopted:!0}:{},...l.machineId?{machineId:l.machineId}:{},paired:!!(l.machineToken&&l.machineId),...l.serverUrl?{serverUrl:l.serverUrl}:{}}}function w(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
- `)}function te(e){const n=e?.trim();if(!n)return;const r=V();r.serverUrl=n,Ee(r)}function re(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const $="com.shennian.agent",g=a.join(d.homedir(),"Library/LaunchAgents",`${$}.plist`),oe=S("Shennian.app"),Ge=a.join(d.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),_=S("autostart.cmd"),b=S("autostart.vbs"),R=S("autostart.xml"),Ke=a.join(Ge,"ShennianAgent.vbs");function se(e){const n=p()==="win32"?`where ${e}`:`command -v ${e}`;try{return u(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(s=>s.trim()).find(Boolean)??null}catch{return null}}function Je(){const e=p()==="win32"?"npx.cmd":"npx",n=a.join(a.dirname(q),e);return o.existsSync(n)?n:se("npx")}function N(){return Le({nodeExec:q,scriptPath:J,shennianCommandPath:se("shennian"),npxPath:Je(),desktopBridgePath:process.env.SHENNIAN_DESKTOP_CLI_BRIDGE})}function ie(){try{u(`schtasks /delete /tn "${D}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ce(){try{o.unlinkSync(Ke)}catch{}}function qe(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),r=process.env.USERNAME?.trim()||d.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${r}`:n?`${n}\\${r}`:r}function Xe(e){if(p()==="win32"&&De(e.command)){const n=[e.command,...e.args].map(G).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Ye(e,n,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function ze(){const e=N();o.writeFileSync(_,Pe(e,m)),o.writeFileSync(b,Ie(_)),ce(),ie();const n=Ne({userId:qe(),command:a.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[G(b)]});o.writeFileSync(R,n,"utf8");const r=Ae({taskName:D,xmlPath:R});try{return u(r.create,{stdio:"pipe",windowsHide:!0}),u(r.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Qe(){_e(D,e=>{u(e,{stdio:"pipe",windowsHide:!0})})}function ae(){const e={};for(const n of X)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=d.homedir(),e.PATH=ve({pathValue:e.PATH,env:e}),e.USER??=d.userInfo().username,p()==="win32"){const n=process.env.TEMP||process.env.TMP||d.tmpdir(),r=d.homedir();e.USERPROFILE??=r;const t=a.win32.parse(r);e.HOMEDRIVE??=t.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=r.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||d.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ze(e={}){const n=ae(),r=e.launch??N(),t=Object.entries(n).map(([i,l])=>` <key>${x(i)}</key>
3
- <string>${x(l)}</string>`).join(`
4
- `),s=[r.command,...r.args].map(i=>` <string>${x(i)}</string>`).join(`
1
+ import c from"chalk";import o from"node:fs";import a from"node:path";import d from"node:os";import{execSync as u,spawn as ge}from"node:child_process";import{randomUUID as B}from"node:crypto";import{fileURLToPath as ye}from"node:url";import{getShennianDir as we,loadConfig as V,resolveShennianPath as S,saveConfig as Ee}from"../config/index.js";import{buildAugmentedPath as ve}from"../env-path.js";import{buildWindowsLauncherCommand as Pe,buildWindowsScheduledTaskCommands as Ae,buildWindowsScheduledTaskXml as Ne,buildWindowsStartupVbs as Ie,escapeXml as T,isWindowsCmdScript as De,quoteCmdArg as G,quoteSystemdArg as ke,suspendWindowsScheduledTask as _e}from"./daemon-windows.js";import{waitForDaemonReadyStatus as x}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as be,isShennianRunServiceCommand as Re}from"./daemon-process.js";import{getCurrentVersion as K}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as Rn,buildWindowsScheduledTaskCommands as Tn,buildWindowsScheduledTaskSuspendCommands as xn,buildWindowsScheduledTaskXml as On,buildWindowsStartupVbs as Ln,suspendWindowsScheduledTask as Cn}from"./daemon-windows.js";import{isShennianRunServiceCommand as $n}from"./daemon-process.js";const Te=a.dirname(ye(import.meta.url)),h=we(),y=S("daemon.pid"),m=S("daemon.log"),O=S("remote-access.disabled"),k=S("daemon-launcher.json"),J=a.resolve(Te,"../../bin/shennian.js"),q=process.execPath,X=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","ELECTRON_RUN_AS_NODE","SHENNIAN_DESKTOP_CLI_SCRIPT","SHENNIAN_DESKTOP_CLI_BRIDGE","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_ACCOUNT_NAMESPACE_ID","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function xe(e){return X.has(e)}function p(){const e=d.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function L(e){const n=e.replace(/\\/g,"/").toLowerCase(),r=d.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(r.endsWith("/")?r:`${r}/`)}function Oe(e){return process.env.ELECTRON_RUN_AS_NODE==="1"&&e.desktopBridgePath&&o.existsSync(e.scriptPath)&&o.existsSync(e.desktopBridgePath)?{command:e.nodeExec,args:[e.desktopBridgePath,e.scriptPath,"run-service"],mode:"direct"}:o.existsSync(e.scriptPath)&&(process.env.SHENNIAN_E2E_ISOLATED_DAEMON==="1"||!L(e.scriptPath))?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!L(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function Le(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const t=JSON.parse(n),s=Number(t.pid);return!Number.isInteger(s)||s<=0?null:{pid:s,...typeof t.instanceId=="string"?{instanceId:t.instanceId}:{},...typeof t.version=="string"?{version:t.version}:{},...t.launcher==="desktop-managed"||t.launcher==="global-cli"||t.launcher==="unknown"?{launcher:t.launcher}:{},...typeof t.startedAt=="string"?{startedAt:t.startedAt}:{},...t.adopted===!0?{adopted:!0}:{}}}catch{return null}const r=parseInt(n,10);return isNaN(r)?null:{pid:r}}function A(){try{return Le(o.readFileSync(y,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function Y(e,n=3e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!v(e)}function Ce(){try{return o.lstatSync(O),!0}catch(e){return!(e&&typeof e=="object"&&"code"in e&&e.code==="ENOENT")}}function He(){try{o.unlinkSync(O)}catch(e){if(e&&typeof e=="object"&&"code"in e&&e.code==="ENOENT")return;throw e}}function $e(e,n){const r=a.dirname(e),t=`${e}.${process.pid}.${B()}.tmp`;o.mkdirSync(r,{recursive:!0});let s=null;try{s=o.openSync(t,"w",384),o.writeSync(s,n,void 0,"utf8"),o.fsyncSync(s),o.closeSync(s),s=null,o.renameSync(t,e);try{const i=o.openSync(r,"r");try{o.fsyncSync(i)}finally{o.closeSync(i)}}catch{}}finally{s!==null&&o.closeSync(s);try{o.unlinkSync(t)}catch{}}}function Me(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??O,t=e.platform??p(),s=e.servicePresent??(H()&&U(t)),i=e.execCommand??(l=>{u(l,{stdio:"pipe",windowsHide:!0})});if($e(r,n),!s)return{disabledAt:n,autoStartDisabled:!0};try{return i(t==="darwin"?`launchctl disable "${P()}"`:t==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${D}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(l){return{disabledAt:n,autoStartDisabled:!1,autoStartError:l instanceof Error?l.message:String(l)}}}function Ue(e={}){const n=e.platform??p();if(!(e.servicePresent??(H()&&U(n))))return{serviceStopped:!0};const t=e.execCommand??(s=>{u(s,{stdio:"pipe",windowsHide:!0})});try{return t(n==="darwin"?`launchctl bootout "${P()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${D}"`),{serviceStopped:!0}}catch(s){return{serviceStopped:!1,error:s instanceof Error?s.message:String(s)}}}function z(){return process.env.SHENNIAN_DESKTOP_CLI_SCRIPT?"desktop-managed":"global-cli"}function je(){return`${process.pid}-${Date.now()}-${B()}`}function We(e,n,r={}){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(y,JSON.stringify({pid:e,instanceId:n,...r.version?{version:r.version}:{},launcher:r.launcher??z(),...r.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function Dn(e,n){const r=A();if(!(r?.pid!==e||r.instanceId!==n))try{o.unlinkSync(y)}catch{}}function Q(e,n=z(),r){o.mkdirSync(h,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...r?{instanceId:r}:{},updatedAt:new Date().toISOString()},null,2))}function Fe(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function Be(e){e&&Q(e)}function Ve(e,n){if(!e||!n)return"unknown";try{const r=JSON.parse(o.readFileSync(k,"utf-8"));if(r.pid!==e)return"unknown";if(r.launcher==="desktop-managed"||r.launcher==="global-cli")return r.launcher}catch{}return C(e)}function C(e){if(p()==="win32")return"unknown";try{const n=u(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/cli-bridge.js")||n.includes("/Resources/daemon/")||n.includes("\\resources\\daemon\\"))return"desktop-managed";if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function Z(e=process.pid){if(p()==="win32")return be(e);try{return u("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(r=>{const t=r.trim().match(/^(\d+)\s+(.+)$/);if(!t)return null;const s=Number(t[1]),i=t[2];return!Number.isInteger(s)||s===e?null:Re(i)?s:null}).filter(r=>typeof r=="number")}catch{return[]}}function ee(e=process.env,n=h,r=d.tmpdir(),t=a.join(d.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||a.resolve(n)===a.resolve(t))return!1;const s=a.relative(a.resolve(r),a.resolve(n));return s!==""&&s!==".."&&!s.startsWith(`..${a.sep}`)&&!a.isAbsolute(s)||a.resolve(n)===a.resolve(e.SHENNIAN_HOME)}function ne(e=process.env,n=h){return!ee(e,n)}function H(e=process.env,n=h){return!ee(e,n)}function f(e={}){let n=A(),r=n?.pid??null,t=r!==null&&v(r),s=r!==null&&!t,i=n?.adopted===!0;const l=V();if(s&&e.cleanupStale){try{o.unlinkSync(y)}catch{}n=null,r=null,t=!1,s=!1}if(!t&&ne()){const E=Z()[0];if(E&&(r=E,t=!0,s=!1,i=!0,e.cleanupStale)){const F=je();We(E,F,{launcher:C(E),adopted:!0}),Q(E,C(E)),n={pid:E,instanceId:F,adopted:!0}}}return{running:t,pid:r,stale:s,remoteAccessDisabled:Ce(),launcher:Ve(r,t),platform:p(),shennianDir:h,pidFile:y,logFile:m,...n?.instanceId?{instanceId:n.instanceId}:{},...i?{adopted:!0}:{},...l.machineId?{machineId:l.machineId}:{},paired:!!(l.machineToken&&l.machineId),...l.serverUrl?{serverUrl:l.serverUrl}:{}}}function w(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ `)}function te(e){const n=e?.trim();if(!n)return;const r=V();r.serverUrl=n,Ee(r)}function re(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const $="com.shennian.agent",g=a.join(d.homedir(),"Library/LaunchAgents",`${$}.plist`),oe=S("Shennian.app"),Ge=a.join(d.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),_=S("autostart.cmd"),b=S("autostart.vbs"),R=S("autostart.xml"),Ke=a.join(Ge,"ShennianAgent.vbs");function se(e){const n=p()==="win32"?`where ${e}`:`command -v ${e}`;try{return u(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(s=>s.trim()).find(Boolean)??null}catch{return null}}function Je(){const e=p()==="win32"?"npx.cmd":"npx",n=a.join(a.dirname(q),e);return o.existsSync(n)?n:se("npx")}function N(){return Oe({nodeExec:q,scriptPath:J,shennianCommandPath:se("shennian"),npxPath:Je(),desktopBridgePath:process.env.SHENNIAN_DESKTOP_CLI_BRIDGE})}function ie(){try{u(`schtasks /delete /tn "${D}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ce(){try{o.unlinkSync(Ke)}catch{}}function qe(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),r=process.env.USERNAME?.trim()||d.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${r}`:n?`${n}\\${r}`:r}function Xe(e){if(p()==="win32"&&De(e.command)){const n=[e.command,...e.args].map(G).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Ye(e,n,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function ze(){const e=N();o.writeFileSync(_,Pe(e,m)),o.writeFileSync(b,Ie(_)),ce(),ie();const n=Ne({userId:qe(),command:a.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[G(b)]});o.writeFileSync(R,n,"utf8");const r=Ae({taskName:D,xmlPath:R});try{return u(r.create,{stdio:"pipe",windowsHide:!0}),u(r.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function Qe(){_e(D,e=>{u(e,{stdio:"pipe",windowsHide:!0})})}function ae(){const e={};for(const n of X)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=d.homedir(),e.PATH=ve({pathValue:e.PATH,env:e}),e.USER??=d.userInfo().username,p()==="win32"){const n=process.env.TEMP||process.env.TMP||d.tmpdir(),r=d.homedir();e.USERPROFILE??=r;const t=a.win32.parse(r);e.HOMEDRIVE??=t.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=r.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||d.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Ze(e={}){const n=ae(),r=e.launch??N(),t=Object.entries(n).map(([i,l])=>` <key>${T(i)}</key>
3
+ <string>${T(l)}</string>`).join(`
4
+ `),s=[r.command,...r.args].map(i=>` <string>${T(i)}</string>`).join(`
5
5
  `);return`<?xml version="1.0" encoding="UTF-8"?>
6
6
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7
7
  <plist version="1.0">
@@ -41,6 +41,6 @@ StandardOutput=append:${m}
41
41
  StandardError=append:${m}
42
42
 
43
43
  [Install]
44
- WantedBy=default.target`}const D="ShennianAgent";function tn(e){u(e,{stdio:"pipe",windowsHide:!0})}function rn(){const e=process.getuid?.();if(typeof e=="number")return e;const n=d.userInfo();return typeof n.uid=="number"?n.uid:n.username}function P(e=rn()){return`gui/${e}/${$}`}function on(e=tn){try{return e(`launchctl enable "${P()}"; launchctl unload "${g}" 2>/dev/null; launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{try{return e(`launchctl enable "${P()}"`),e(`launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{return!1}}}function sn(){o.mkdirSync(h,{recursive:!0});const e={};for(const[n,r]of Object.entries(process.env))r!==void 0&&Te(n)&&(e[n]=r);o.writeFileSync(S("env.json"),JSON.stringify(e,null,2))}function de(e={}){o.mkdirSync(h,{recursive:!0});const n=f({cleanupStale:!0});if(n.pid!==null&&n.running&&!M(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}le(n);const r=o.openSync(m,"a"),t=Xe(N()),s=ge(t.command,t.args,Ye(t,r));s.unref(),o.closeSync(r),Be(s.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(c.gray(` Logs: ${m}`)))}function M(e,n=A(),r=K()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==r}function U(e=p()){switch(e){case"darwin":return o.existsSync(g)&&!o.existsSync(oe);case"linux":return o.existsSync(I);case"win32":return o.existsSync(_)&&o.existsSync(b)&&o.existsSync(R)}}function cn(e,n,r,t=A(),s=K()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&r&&!M(e,t,s)}function le(e=f({cleanupStale:!0})){if(!(!M(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),Y(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),Y(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(y)}catch{}}catch{}}function pe(e={}){if(o.mkdirSync(h,{recursive:!0}),!H())return!1;const n=p();switch(N().mode==="direct"&&O(J)&&console.warn(c.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=a.dirname(g);o.mkdirSync(t,{recursive:!0}),ue(),o.writeFileSync(g,Ze());const s=on(),i=en(n);return!e.quiet&&i&&console.log(c.gray(`\u2139 ${i}`)),s}case"linux":{const t=a.dirname(I);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(I,nn());try{u("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{u(`loginctl enable-linger ${d.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return u("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return ze()}}function an(){const n=f({cleanupStale:!0}).pid;if(n===null)return{};if(!v(n)){try{o.unlinkSync(y)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(r){return{error:r instanceof Error?r.message:String(r)}}}async function me(e=5e3){const n=an();if(!n.stoppedPid)return n;if(!await W(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await W(n.stoppedPid,2e3)}catch(t){return{...n,error:t instanceof Error?t.message:String(t)}}try{o.unlinkSync(y)}catch{}if(Fe(),ne()){for(const t of Z())if(t!==n.stoppedPid)try{process.kill(t,"SIGTERM"),await W(t,2e3)}catch{}}return n}function un(e={}){te(re(e.api)),He(),le(),pe({quiet:e.json})||de({quiet:e.json}),e.json?w(T(()=>f({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function dn(e={}){const n=Me(),r=Ue(),t=await me(),s=f({cleanupStale:!0}),i=t.error??n.autoStartError??r.error;if(e.json){w(i?{...s,error:i}:s);return}i?console.error(c.red(`\u2717 Failed to stop completely: ${i}`)):console.log(c.green("\u2713 Background service stopped"))}function fe(e){const n=f({cleanupStale:!0});if(cn(n,e,U())){e.json?w(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}sn(),un(e)}async function j(e={}){await dn(e)}async function W(e,n=5e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;await new Promise(t=>setTimeout(t,100))}return!v(e)}async function ln(e={}){const n=f({cleanupStale:!0});if(n.remoteAccessDisabled){e.json?w({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(c.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}te(re(e.api));const r=p();r==="win32"&&Qe();const t=await me();if(t.error){e.json?w({...f(),error:t.error}):console.error(c.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(r==="linux"||r==="win32")&&pe()){e.json&&w(T(()=>f({cleanupStale:!0})));return}de({quiet:e.json}),e.json&&w(T(()=>f({cleanupStale:!0})))}function Se(e={}){const n=f({cleanupStale:!0});if(e.json){w(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${m}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function he(e){if(!o.existsSync(m)){console.log(c.gray("No logs yet"));return}try{const n=u(d.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${m}"`:`tail -n ${e.lines} "${m}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(m,"utf-8").split(`
44
+ WantedBy=default.target`}const D="ShennianAgent";function tn(e){u(e,{stdio:"pipe",windowsHide:!0})}function rn(){const e=process.getuid?.();if(typeof e=="number")return e;const n=d.userInfo();return typeof n.uid=="number"?n.uid:n.username}function P(e=rn()){return`gui/${e}/${$}`}function on(e=tn){try{return e(`launchctl enable "${P()}"; launchctl unload "${g}" 2>/dev/null; launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{try{return e(`launchctl enable "${P()}"`),e(`launchctl load -w "${g}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{return!1}}}function sn(){o.mkdirSync(h,{recursive:!0});const e={};for(const[n,r]of Object.entries(process.env))r!==void 0&&xe(n)&&(e[n]=r);o.writeFileSync(S("env.json"),JSON.stringify(e,null,2))}function de(e={}){o.mkdirSync(h,{recursive:!0});const n=f({cleanupStale:!0});if(n.pid!==null&&n.running&&!M(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}le(n);const r=o.openSync(m,"a"),t=Xe(N()),s=ge(t.command,t.args,Ye(t,r));s.unref(),o.closeSync(r),Be(s.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(c.gray(` Logs: ${m}`)))}function M(e,n=A(),r=K()){return e.pid===null||!e.running||e.launcher==="desktop-managed"?!1:e.adopted===!0||n?.version!==r}function U(e=p()){switch(e){case"darwin":return o.existsSync(g)&&!o.existsSync(oe);case"linux":return o.existsSync(I);case"win32":return o.existsSync(_)&&o.existsSync(b)&&o.existsSync(R)}}function cn(e,n,r,t=A(),s=K()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&(e.launcher==="desktop-managed"||r)&&!M(e,t,s)}function le(e=f({cleanupStale:!0})){if(!(!M(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),Y(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),Y(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(y)}catch{}}catch{}}function pe(e={}){if(o.mkdirSync(h,{recursive:!0}),!H())return!1;const n=p();switch(N().mode==="direct"&&L(J)&&console.warn(c.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=a.dirname(g);o.mkdirSync(t,{recursive:!0}),ue(),o.writeFileSync(g,Ze());const s=on(),i=en(n);return!e.quiet&&i&&console.log(c.gray(`\u2139 ${i}`)),s}case"linux":{const t=a.dirname(I);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(I,nn());try{u("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{u(`loginctl enable-linger ${d.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return u("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return ze()}}function an(){const n=f({cleanupStale:!0}).pid;if(n===null)return{};if(!v(n)){try{o.unlinkSync(y)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(r){return{error:r instanceof Error?r.message:String(r)}}}async function me(e=5e3){const n=an();if(!n.stoppedPid)return n;if(!await W(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await W(n.stoppedPid,2e3)}catch(t){return{...n,error:t instanceof Error?t.message:String(t)}}try{o.unlinkSync(y)}catch{}if(Fe(),ne()){for(const t of Z())if(t!==n.stoppedPid)try{process.kill(t,"SIGTERM"),await W(t,2e3)}catch{}}return n}function un(e={}){te(re(e.api)),He(),le(),pe({quiet:e.json})||de({quiet:e.json}),e.json?w(x(()=>f({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function dn(e={}){const n=Me(),r=Ue(),t=await me(),s=f({cleanupStale:!0}),i=t.error??n.autoStartError??r.error;if(e.json){w(i?{...s,error:i}:s);return}i?console.error(c.red(`\u2717 Failed to stop completely: ${i}`)):console.log(c.green("\u2713 Background service stopped"))}function fe(e){const n=f({cleanupStale:!0});if(cn(n,e,U())){e.json?w(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}sn(),un(e)}async function j(e={}){await dn(e)}async function W(e,n=5e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;await new Promise(t=>setTimeout(t,100))}return!v(e)}async function ln(e={}){const n=f({cleanupStale:!0});if(n.remoteAccessDisabled){e.json?w({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(c.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}te(re(e.api));const r=p();r==="win32"&&Qe();const t=await me();if(t.error){e.json?w({...f(),error:t.error}):console.error(c.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(r==="linux"||r==="win32")&&pe()){e.json&&w(x(()=>f({cleanupStale:!0})));return}de({quiet:e.json}),e.json&&w(x(()=>f({cleanupStale:!0})))}function Se(e={}){const n=f({cleanupStale:!0});if(e.json){w(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${m}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function he(e){if(!o.existsSync(m)){console.log(c.gray("No logs yet"));return}try{const n=u(d.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${m}"`:`tail -n ${e.lines} "${m}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(m,"utf-8").split(`
45
45
  `).slice(-e.lines);console.log(n.join(`
46
- `))}}async function pn(){switch(await j(),p()){case"darwin":{if(o.existsSync(g)){try{u(`launchctl unload -w "${g}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(g)}ue(),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"));break}case"linux":{try{u("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(I)){o.unlinkSync(I);try{u("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{ie();try{o.unlinkSync(_)}catch{}try{o.unlinkSync(b)}catch{}try{o.unlinkSync(R)}catch{}ce(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function kn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>fe(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>j(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>Se(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>he({lines:parseInt(t.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),r=(t,s)=>{s||console.error(c.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),fe(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await j(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),ln(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),Se(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),he({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(pn)}export{Ye as buildDaemonSpawnOptions,Ze as buildMacLaunchdPlist,nn as buildSystemdUnit,Rn as buildWindowsLauncherCommand,xn as buildWindowsScheduledTaskCommands,Tn as buildWindowsScheduledTaskSuspendCommands,Ln as buildWindowsScheduledTaskXml,On as buildWindowsStartupVbs,cn as canReuseRunningDaemonForStart,ae as captureEnvForService,Fe as clearDaemonLauncher,Dn as clearDaemonPidIfOwner,He as clearRemoteAccessDisabled,je as createDaemonInstanceId,Z as findRunningDaemonProcessIds,z as getCurrentProcessDaemonLauncher,f as getDaemonStatus,P as getLaunchdServiceTarget,en as getMacBackgroundItemNotice,pe as installService,U as isAutoStartServicePresent,O as isEphemeralCliPath,ee as isIsolatedDaemonE2eInstance,Ce as isRemoteAccessDisabled,Te as isSafeSnapshotEnvKey,$n as isShennianRunServiceCommand,Me as prepareRemoteAccessPause,Be as recordStartedDaemon,kn as registerDaemonCommand,on as reloadLaunchdAgent,ue as removeLegacyMacBackgroundApp,Le as resolveServiceLaunchSpec,sn as saveEnvSnapshot,ne as shouldDiscoverGlobalDaemonProcesses,H as shouldInstallPlatformService,M as shouldReplaceRunningDaemon,de as startDaemonProcess,Ue as stopPreparedRemoteAccessService,Cn as suspendWindowsScheduledTask,Q as writeDaemonLauncher,We as writeDaemonPid};
46
+ `))}}async function pn(){switch(await j(),p()){case"darwin":{if(o.existsSync(g)){try{u(`launchctl unload -w "${g}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(g)}ue(),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"));break}case"linux":{try{u("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(I)){o.unlinkSync(I);try{u("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{ie();try{o.unlinkSync(_)}catch{}try{o.unlinkSync(b)}catch{}try{o.unlinkSync(R)}catch{}ce(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function kn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>fe(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>j(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>Se(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>he({lines:parseInt(t.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),r=(t,s)=>{s||console.error(c.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),fe(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await j(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),ln(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),Se(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),he({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(pn)}export{Ye as buildDaemonSpawnOptions,Ze as buildMacLaunchdPlist,nn as buildSystemdUnit,Rn as buildWindowsLauncherCommand,Tn as buildWindowsScheduledTaskCommands,xn as buildWindowsScheduledTaskSuspendCommands,On as buildWindowsScheduledTaskXml,Ln as buildWindowsStartupVbs,cn as canReuseRunningDaemonForStart,ae as captureEnvForService,Fe as clearDaemonLauncher,Dn as clearDaemonPidIfOwner,He as clearRemoteAccessDisabled,je as createDaemonInstanceId,Z as findRunningDaemonProcessIds,z as getCurrentProcessDaemonLauncher,f as getDaemonStatus,P as getLaunchdServiceTarget,en as getMacBackgroundItemNotice,pe as installService,U as isAutoStartServicePresent,L as isEphemeralCliPath,ee as isIsolatedDaemonE2eInstance,Ce as isRemoteAccessDisabled,xe as isSafeSnapshotEnvKey,$n as isShennianRunServiceCommand,Me as prepareRemoteAccessPause,Be as recordStartedDaemon,kn as registerDaemonCommand,on as reloadLaunchdAgent,ue as removeLegacyMacBackgroundApp,Oe as resolveServiceLaunchSpec,sn as saveEnvSnapshot,ne as shouldDiscoverGlobalDaemonProcesses,H as shouldInstallPlatformService,M as shouldReplaceRunningDaemon,de as startDaemonProcess,Ue as stopPreparedRemoteAccessService,Cn as suspendWindowsScheduledTask,Q as writeDaemonLauncher,We as writeDaemonPid};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as ve}from"commander";import k from"chalk";import Ie from"node:os";import B from"node:fs";import{loadConfig as p,saveConfig as Se,getConfigPath as ye,getShennianDir as we,resolveShennianPath as Q,normalizeAutoUpgradePolicy as Ce}from"./config/index.js";import{CliRelayClient as Re}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as ke,sanitizeCliLogValue as be}from"./log-redaction.js";import{registerPairCommand as Ee,runSmartStart as Ae}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as De,clearDaemonLauncher as oe,findRunningDaemonProcessIds as Pe,isRemoteAccessDisabled as te,prepareRemoteAccessPause as Te,stopPreparedRemoteAccessService as Me,isIsolatedDaemonE2eInstance as _e,getDaemonStatus as $e,registerDaemonCommand as Ne,writeDaemonPid as ne,writeDaemonLauncher as re}from"./commands/daemon.js";import{registerRuntimeCommand as Oe}from"./commands/runtime.js";import{registerAgentCommand as xe}from"./commands/agent.js";import{registerManagerCommand as Ve}from"./commands/manager.js";import{handleUpgradeStart as Le,registerUpgradeCommand as je}from"./commands/upgrade.js";import{registerToolsCommand as Be}from"./commands/tools.js";import{registerIntegrationCommand as He}from"./commands/integration.js";import{registerRoomCommand as Fe}from"./commands/room.js";import{SessionManager as Ge}from"./session/manager.js";import{SERVERS as H,regionToUrl as ze,urlToRegion as Ke}from"./region.js";import{clearUpgradeAttempt as Je,getCurrentVersion as ae,handleStartupCrashCheck as qe,readUpgradeAttempt as We}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ye}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as Ze}from"./upgrade/updater-bootstrap.js";import{launchUpdater as Qe,markUpdaterHealthy as Xe}from"./upgrade/updater-launcher.js";import{detectAgents as eo}from"./agents/detect.js";import{augmentProcessPath as se}from"./env-path.js";se();const F=ae();import{getCachedAgentInfos as oo,resolveAgentInfos as to}from"./agents/model-registry.js";import{initCliLogReporter as no,reportLog as b,reportUpgradeAggregateMetric as ro}from"./log-reporter.js";import{isNativeFusionEnabled as ao}from"./native-fusion/config.js";import{NativeSessionFusionService as so}from"./native-fusion/service.js";import{startDaemonLogRetention as io}from"./daemon-log.js";import{startDaemonIpcServer as ie}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as co,resolveManagedRoomIpcPaths as lo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as mo}from"./daemon-ipc/control.js";import{DAEMON_IPC_VERSION as po}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as go}from"./room/session-registry.js";import{HttpRoomBindingClient as uo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as fo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ho}from"./room/host-session-observations.js";import{RoomMirrorSyncService as vo}from"./room/mirror-sync-service.js";import{RoomActivationService as Io}from"./room/activation-service.js";import{RoomActivationAudit as So}from"./room/activation-audit.js";import{RoomAttachmentCache as yo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as wo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Co}from"./room/managed-context.js";import{createManagedRoomExecutor as Ro}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as ce,SESSION_SYNC_ADVERTISED_CAPABILITIES as le}from"@shennian/wire";import{getDaemonInstallationId as ko}from"./session/store.js";import{synchronizeMachinePersonalAuthority as bo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as de,ensurePersonalDataEpoch as Eo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Ao,DeletionSyncTerminalError as Uo}from"./session/deletion-sync-state.js";const me=we(),Do=Q("daemon.pid");function Po(){try{const t=B.readFileSync(Do,"utf-8").trim();if(t.startsWith("{")){const c=JSON.parse(t),m=Number(c.pid);return Number.isInteger(m)&&m>0?m:null}const s=parseInt(t,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function To(t){return t.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function G(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function Mo(t){const s=[`trigger=${t.trigger??"unknown"}`,`phase=${t.phase}`,`code=${t.code??"unknown"}`,`attempt=${t.reconnectAttempt}`];return t.reason&&s.push(`reason=${t.reason}`),t.error&&s.push(`error=${t.error}`),s.join(" ")}async function pe(t,s=5e3){const c=Date.now();for(;Date.now()-c<s;){try{process.kill(t,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(t,0),!1}catch{return!0}}const i=new ve;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),He(i),Fe(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Ie.hostname()).action(async t=>{const s=p(),c=t.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Ae(c,t.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async t=>{const s=Q("env.json");try{const e=JSON.parse(B.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);se()}catch{}ke(X(process.env,p()));const c=io();te()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(c),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),z=async(e,n)=>{console.log(`[${new Date().toISOString()}] ${n} (PID ${e})`),process.kill(e,"SIGTERM"),await pe(e)||(process.kill(e,"SIGKILL"),await pe(e,2e3))};try{const e=Po();if(e&&e!==process.pid)try{process.kill(e,0),m?await z(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const _=_e(process.env,me)?[]:Pe(process.pid);if(_.length>0)if(m)for(const e of _)try{await z(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${_[0]}), skipping duplicate start`),process.exit(0);const g=De();ne(process.pid,g,{version:F}),re(process.pid,void 0,g),process.on("exit",()=>{ee(process.pid,g),oe(g)});let $=null;try{$=Ze()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const N=await qe();if(N){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Qe({fromVersion:F,targetVersion:N.from,oldDaemonPid:process.pid,operation:"rollback"},{readiness:$}),console.error(`[upgrade] New version crashed ${N.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Eo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),h=t.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??H.cn.url,u=ae(),E=await eo({serverUrl:h,authToken:l.machineToken??l.accessToken}),y=E.map(e=>e.type),O=oo(E),v=new go,A=new ho,w=new Co(v),I=new uo(h),ue=new yo({actionClient:I}),fe=new wo({actionClient:I});let S=null,a;const K=Ro({authority:w,actionClient:I,attachmentCache:ue,attachmentUploader:fe,observations:A,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:n})=>{const r=v.getByRegistryKey(e)?.binding;r&&v.setBinding({registryKey:e,bindingId:r.bindingId,roomId:r.roomId,agentIdentityId:r.agentIdentityId,agentMembershipId:r.agentMembershipId,status:"paused",activationMode:"paused"}),w.revokeSession(e),a?.discardPendingManagedRoomMessages(n)}});let x=null,V=null,L=null;const C=await ie({paths:co(),handlers:mo({daemonVersion:u,protocolVersion:po,getStatus:()=>$e(),getAgents:()=>E,completeAgentAuthentication:e=>a.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!x)throw new Error("upgrade_unavailable");return x(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let R;try{R=await ie({paths:lo(),handlers:{"managed-room.execute":e=>K(e)}})}catch(e){throw await C.close(),e}let J=!1;const U=()=>{J||(J=!0,Promise.allSettled([C.close(),R.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",U),process.once("SIGTERM",U),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([C.close(),R.close()]),process.exitCode=1;return}process.removeListener("SIGINT",U),process.removeListener("SIGTERM",U);const q=`${To(h)}/relay/machine`,j=l.machineToken;if(!j)return;try{await bo({serverUrl:h,machineToken:j,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([C.close(),R.close()]),process.exit(1)}l.machineId&&no(h,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${q}... (v${u}) agents: ${y.join(",")}`),b({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:y}});let D=null,P=null,T=0,f=We();const W=Ce(l.autoUpgrade),d=new Re({serverUrl:q,machineToken:j,cliVersion:u,agentList:y,autoUpgradePolicy:W,sessionSyncProtocolVersion:ce,sessionSyncCapabilities:le,daemonInstallationId:ko(),dataEpoch:de,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),b({level:"info",wsEvent:"daemon.connected"}),O.some(o=>o.models.length>0)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:y,agents:O}}),to(E,{serverUrl:l.serverUrl??h,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(O)&&d.sendEvent({type:"event",event:"machine.agents",payload:{agentList:y,agents:o}})}).catch(()=>{}),f&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:f.from,to:f.to,...f.jobId?{jobId:f.jobId}:{},activeRunCount:a.getPendingRecoveryCount()}}),M.start(),P?.start();const e=++T,n=async()=>{try{if(await a.synchronizeSessionDeletions(),await a.recoverInterruptedRuns(),f){const o=f;f=null,o.jobId&&Xe(o.jobId,o.to),Je(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),ro({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),d.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&d.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===T&&d.getState()==="connected"&&D?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===T&&d.getState()==="connected"){if(o instanceof Uo)return;const r=o instanceof Ao?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{n()},r).unref?.()}}};n()},onDisconnected:e=>{T+=1,P?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${Mo(e)}`),b({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),b({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),a.handleReq(e)}});D=ao()?new so(d):null,P=new vo({registry:v,observations:A,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,n,o,r)=>{await S?.processMessages(e,n,o,r)},onBindingPaused:e=>{w.revokeSession(e.registryKey),a?.discardPendingManagedRoomMessages(e.nianSessionId)}});const M=new Ye({currentVersion:u,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??h,getIdleState:()=>a.getUpgradeIdleState(),sessionSyncProtocolVersion:ce,dataEpoch:de,advertisedCapabilities:le,updaterProtocolVersion:1,startUpgrade:async e=>{const{handleUpgradeStart:n}=await import("./commands/upgrade.js");await n(d,"auto-upgrade",e,{currentVersion:u,prepareForUpgrade:o=>a.prepareForUpgrade(o),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})}});M.setPolicy(W),a=new Ge(d,D,u,M,new fo({registry:v,observations:A,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${g}`}),{prepare:()=>Te(),isDisabled:()=>te(),shutdownAfterResponse:()=>{setTimeout(()=>{Me(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>w.revokeToken(e),K),x=async e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof n.targetVersion=="string"?n.targetVersion:void 0,r=n.confirmedMajor===!0;return await Le(d,`local-upgrade-${Date.now()}`,o,{currentVersion:u,confirmedMajor:r,prepareForUpgrade:he=>a.prepareForUpgrade(he),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=e=>{const n=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=`desktop-${(typeof n.targetVersion=="string"?n.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:r,...a.prepareForUpgrade(r)}},L=()=>(a.cancelUpgradePreparation(),{cancelled:!0}),S=new Io({registry:v,observations:A,enqueueConversationBatch:e=>a.enqueueManagedRoomBatch(e),loadContext:async e=>{const n=p().machineToken?.trim(),o=e.binding;if(!n||!o)throw new Error("managed_room_context_unavailable");const r=await I.status({machineToken:n,roomId:o.roomId,bindingId:o.bindingId});return{roomName:r.room.name,memberCount:r.room.memberCount,members:r.room.members,agentsByIdentityId:r.agent?{[r.agentIdentityId]:r.agent}:{},agentSystemPromptsByIdentityId:r.agent?{[r.agentIdentityId]:r.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,n)=>{const o=p().machineToken;if(!o)throw new Error("machine_not_authorized");return w.issue({registryKey:e,machineCredential:o,activationCauseMessageId:n})},audit:new So}),a.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),a.setManagedAgentDirectTurnPreparer(e=>S.prepareDirectConversationTurn(e)),a.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),B.mkdirSync(me,{recursive:!0}),ne(process.pid,g,{version:u}),re(process.pid,void 0,g),d.connect(),process.stdin.resume();let Y=!1;const Z=async()=>{if(!Y){Y=!0,console.log(k.gray(`
3
- Disconnecting...`)),b({level:"info",wsEvent:"daemon.stop"}),clearInterval(c);try{await a.cleanup(),M.stop(),D?.stop(),P?.stop(),d.disconnect(),await Promise.allSettled([C.close(),R.close()]),ee(process.pid,g),oe(g)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Z()}),process.on("SIGTERM",()=>{Z()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>G()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const ge=i.command("config").description("View or modify configuration");ge.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ye()}`);const t=p(),s=be(t,X(process.env,t));if(t.serverUrl){const c=Ke(t.serverUrl);console.log(`Region: ${c} (${H[c].label})`)}console.log(JSON.stringify(s,null,2))}),ge.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((t,s)=>{const c=p();if(t==="server"){s!=="cn"&&s!=="global"&&(console.error(k.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=s;c.serverUrl=ze(m),Se(c),console.log(k.green(`\u2713 Server set to ${H[m].label}`)),console.log(k.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(k.red(`\u2717 Unknown config key: ${t}. Supported: server`)),process.exit(1)}),Ee(i),Ue(i),Ne(i),Oe(i),xe(i),Ve(i),je(i),Be(i),i.parse();
1
+ import{Command as fe}from"commander";import b from"chalk";import he from"node:os";import j from"node:fs";import{loadConfig as p,saveConfig as ve,getConfigPath as Ie,getShennianDir as Se,resolveShennianPath as H,normalizeAutoUpgradePolicy as ye}from"./config/index.js";import{CliRelayClient as we}from"./relay/client.js";import{collectKnownLogSecrets as Q,installCliLogRedaction as Ce,sanitizeCliLogValue as Re}from"./log-redaction.js";import{registerPairCommand as ke,runSmartStart as be}from"./commands/pair.js";import{registerLogoutCommand as Pe}from"./commands/logout.js";import{clearDaemonPidIfOwner as X,createDaemonInstanceId as Ae,clearDaemonLauncher as ee,findRunningDaemonProcessIds as Ee,isRemoteAccessDisabled as oe,prepareRemoteAccessPause as Ue,stopPreparedRemoteAccessService as De,isIsolatedDaemonE2eInstance as Te,getDaemonStatus as Me,registerDaemonCommand as _e,writeDaemonPid as ne,writeDaemonLauncher as re}from"./commands/daemon.js";import{registerRuntimeCommand as $e}from"./commands/runtime.js";import{registerAgentCommand as Ne}from"./commands/agent.js";import{registerManagerCommand as Oe}from"./commands/manager.js";import{handleUpgradeStart as xe,registerUpgradeCommand as Ve}from"./commands/upgrade.js";import{registerToolsCommand as Le}from"./commands/tools.js";import{registerIntegrationCommand as je}from"./commands/integration.js";import{registerRoomCommand as He}from"./commands/room.js";import{SessionManager as Fe}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Be}from"./session/handlers/project-fs.js";import{SERVERS as F,regionToUrl as Ge,urlToRegion as qe}from"./region.js";import{clearUpgradeAttempt as ze,getCurrentVersion as te,handleStartupCrashCheck as Ke,readUpgradeAttempt as Je}from"./upgrade/engine.js";import{AutoUpgradeScheduler as We}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as Ye}from"./upgrade/updater-bootstrap.js";import{launchUpdater as Ze,markUpdaterHealthy as Qe}from"./upgrade/updater-launcher.js";import{detectAgents as Xe}from"./agents/detect.js";import{augmentProcessPath as ae}from"./env-path.js";import{acquireDaemonInstanceLock as eo}from"./commands/daemon-instance-lock.js";ae();const B=te();import{getCachedAgentInfos as oo,resolveAgentInfos as no}from"./agents/model-registry.js";import{initCliLogReporter as ro,reportLog as P,reportUpgradeAggregateMetric as to}from"./log-reporter.js";import{isNativeFusionEnabled as ao}from"./native-fusion/config.js";import{NativeSessionFusionService as so}from"./native-fusion/service.js";import{startDaemonLogRetention as io}from"./daemon-log.js";import{startDaemonIpcServer as se}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as co,resolveManagedRoomIpcPaths as lo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as mo}from"./daemon-ipc/control.js";import{DAEMON_IPC_VERSION as po}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as go}from"./room/session-registry.js";import{HttpRoomBindingClient as uo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as fo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ho}from"./room/host-session-observations.js";import{RoomMirrorSyncService as vo}from"./room/mirror-sync-service.js";import{RoomActivationService as Io,resolveManagedAgentPolicy as So}from"./room/activation-service.js";import{RoomActivationAudit as yo}from"./room/activation-audit.js";import{RoomAttachmentCache as wo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Co}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ro}from"./room/managed-context.js";import{createManagedRoomExecutor as ko}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as ie,SESSION_SYNC_ADVERTISED_CAPABILITIES as ce}from"@shennian/wire";import{getDaemonInstallationId as bo}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Po}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as le,ensurePersonalDataEpoch as Ao}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Eo,DeletionSyncTerminalError as Uo}from"./session/deletion-sync-state.js";const de=Se(),Do=H("daemon.pid"),To=H("daemon.instance.lock");function Mo(){try{const n=j.readFileSync(Do,"utf-8").trim();if(n.startsWith("{")){const d=JSON.parse(n),c=Number(d.pid);return Number.isInteger(c)&&c>0?c:null}const i=parseInt(n,10);return Number.isInteger(i)&&i>0?i:null}catch{return null}}function _o(n){return n.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function G(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
+ `),process.exitCode=1}function $o(n){const i=[`trigger=${n.trigger??"unknown"}`,`phase=${n.phase}`,`code=${n.code??"unknown"}`,`attempt=${n.reconnectAttempt}`];return n.reason&&i.push(`reason=${n.reason}`),n.error&&i.push(`error=${n.error}`),i.join(" ")}const s=new fe;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(B),s.enablePositionalOptions(),je(s),He(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",he.hostname()).action(async n=>{const i=p(),d=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??i.serverUrl??void 0;await be(d,n.name)}),s.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async n=>{const i=H("env.json");try{const e=JSON.parse(j.readFileSync(i,"utf-8"));for(const[r,o]of Object.entries(e))process.env[r]||(process.env[r]=o);ae()}catch{}Ce(Q(process.env,p()));const d=io();oe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(d),process.exit(0));const c=Ae(),h=eo({lockPath:To,instanceId:c});h.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(h.ownerPid?` (PID ${h.ownerPid})`:"")+", skipping duplicate start"),clearInterval(d),process.exit(0));try{const e=Mo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),h.release(),process.exit(0)}catch{}}catch{}const q=Te(process.env,de)?[]:Ee(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),h.release(),process.exit(0)),ne(process.pid,c,{version:B}),re(process.pid,void 0,c),process.on("exit",()=>{X(process.pid,c),ee(c)}),process.on("exit",h.release);let $=null;try{$=Ye()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const N=await Ke();if(N){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Ze({fromVersion:B,targetVersion:N.from,oldDaemonPid:process.pid,operation:"rollback"},{readiness:$}),console.error(`[upgrade] New version crashed ${N.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Ao()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),f=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??F.cn.url,g=te(),y=await Xe({serverUrl:f,authToken:l.machineToken??l.accessToken}),w=y.map(e=>e.type),O=oo(y),v=new go,A=new ho,C=new Ro(v),I=new uo(f),pe=new wo({actionClient:I}),ge=new Co({actionClient:I});let S=null,a;const z=ko({authority:C,actionClient:I,attachmentCache:pe,attachmentUploader:ge,observations:A,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:r})=>{const t=v.getByRegistryKey(e)?.binding;t&&v.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),a?.discardPendingManagedRoomMessages(r)}});let x=null,V=null,L=null;const R=await se({paths:co(),handlers:mo({daemonVersion:g,protocolVersion:po,getStatus:()=>Me(),getAgents:()=>y,completeAgentAuthentication:e=>a.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!x)throw new Error("upgrade_unavailable");return x(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let k;try{k=await se({paths:lo(),handlers:{"managed-room.execute":e=>z(e)}})}catch(e){throw await R.close(),e}let K=!1;const E=()=>{K||(K=!0,Promise.allSettled([R.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",E),process.once("SIGTERM",E),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([R.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",E),process.removeListener("SIGTERM",E);const J=`${_o(f)}/relay/machine`,U=l.machineToken;if(!U)return;try{await Po({serverUrl:f,machineToken:U,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([R.close(),k.close()]),process.exit(1)}l.machineId&&ro(f,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${J}... (v${g}) agents: ${w.join(",")}`),P({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:w}});let D=null,T=null,M=0,u=Je();const W=ye(l.autoUpgrade),m=new we({serverUrl:J,machineToken:U,cliVersion:g,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:ie,sessionSyncCapabilities:ce,daemonInstallationId:bo(),dataEpoch:le,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),P({level:"info",wsEvent:"daemon.connected"}),O.some(o=>o.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:O}}),no(y,{serverUrl:l.serverUrl??f,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(O)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),u&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:u.from,to:u.to,...u.jobId?{jobId:u.jobId}:{},activeRunCount:a.getPendingRecoveryCount()}}),_.start(),T?.start();const e=++M,r=async()=>{try{if(await a.synchronizeSessionDeletions(),await a.recoverInterruptedRuns(),u){const o=u;u=null,o.jobId&&Qe(o.jobId,o.to),ze(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),to({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),m.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===M&&m.getState()==="connected"&&D?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===M&&m.getState()==="connected"){if(o instanceof Uo)return;const t=o instanceof Eo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{r()},t).unref?.()}}};r()},onDisconnected:e=>{M+=1,a?.revokeProjectFsHandles(),T?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${$o(e)}`),P({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||a?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),P({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),a.handleReq(e)}});D=ao()?new so(m):null,T=new vo({registry:v,observations:A,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,r,o,t)=>{await S?.processMessages(e,r,o,t)},onBindingPaused:e=>{C.revokeSession(e.registryKey),a?.discardPendingManagedRoomMessages(e.nianSessionId)}});const _=new We({currentVersion:g,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??f,getIdleState:()=>a.getUpgradeIdleState(),sessionSyncProtocolVersion:ie,dataEpoch:le,advertisedCapabilities:ce,updaterProtocolVersion:1,startUpgrade:async e=>{const{handleUpgradeStart:r}=await import("./commands/upgrade.js");await r(m,"auto-upgrade",e,{currentVersion:g,prepareForUpgrade:o=>a.prepareForUpgrade(o),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})}});_.setPolicy(W),a=new Fe(m,D,g,_,new fo({registry:v,observations:A,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Ue(),isDisabled:()=>oe(),shutdownAfterResponse:()=>{setTimeout(()=>{De(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Be(U)),x=async e=>{const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof r.targetVersion=="string"?r.targetVersion:void 0,t=r.confirmedMajor===!0;return await xe(m,`local-upgrade-${Date.now()}`,o,{currentVersion:g,confirmedMajor:t,prepareForUpgrade:ue=>a.prepareForUpgrade(ue),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=e=>{const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof r.targetVersion=="string"?r.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...a.prepareForUpgrade(t)}},L=()=>(a.cancelUpgradePreparation(),{cancelled:!0}),S=new Io({registry:v,observations:A,enqueueConversationBatch:e=>a.enqueueManagedRoomBatch(e),loadContext:async e=>{const r=p().machineToken?.trim(),o=e.binding;if(!r||!o)throw new Error("managed_room_context_unavailable");const t=await I.status({machineToken:r,roomId:o.roomId,bindingId:o.bindingId});return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,r)=>{const o=p().machineToken;if(!o)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:o,activationCauseMessageId:r})},audit:new yo,resolveManagedAgentPolicy:e=>So({...e,runtime:y.find(r=>r.type===e.registration.runtimeAdapter)})}),a.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),a.setManagedAgentDirectTurnPreparer(e=>S.prepareDirectConversationTurn(e)),a.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),j.mkdirSync(de,{recursive:!0}),ne(process.pid,c,{version:g}),re(process.pid,void 0,c),m.connect(),process.stdin.resume();let Y=!1;const Z=async()=>{if(!Y){Y=!0,console.log(b.gray(`
3
+ Disconnecting...`)),P({level:"info",wsEvent:"daemon.stop"}),clearInterval(d);try{await a.cleanup(),_.stop(),D?.stop(),T?.stop(),m.disconnect(),await Promise.allSettled([R.close(),k.close()]),X(process.pid,c),ee(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Z()}),process.on("SIGTERM",()=>{Z()})}),s.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),s.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>G()),s.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const me=s.command("config").description("View or modify configuration");me.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${Ie()}`);const n=p(),i=Re(n,Q(process.env,n));if(n.serverUrl){const d=qe(n.serverUrl);console.log(`Region: ${d} (${F[d].label})`)}console.log(JSON.stringify(i,null,2))}),me.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((n,i)=>{const d=p();if(n==="server"){i!=="cn"&&i!=="global"&&(console.error(b.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=i;d.serverUrl=Ge(c),ve(d),console.log(b.green(`\u2713 Server set to ${F[c].label}`)),console.log(b.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(b.red(`\u2717 Unknown config key: ${n}. Supported: server`)),process.exit(1)}),ke(s),Pe(s),_e(s),$e(s),Ne(s),Oe(s),Ve(s),Le(s),s.parse();
@@ -14,6 +14,7 @@ export type CliRelayOptions = {
14
14
  onReq?: (req: ReqFrame) => void;
15
15
  onConnected?: () => void;
16
16
  onDisconnected?: (info: CliRelayDisconnectInfo) => void;
17
+ onProjectFsCapabilityChanged?: (active: boolean) => void;
17
18
  };
18
19
  export declare class SessionSyncRejectedError extends Error {
19
20
  readonly rejection: SessionSyncRejection;
@@ -40,6 +41,7 @@ export declare class CliRelayClient {
40
41
  private pingTimer;
41
42
  private pongTimer;
42
43
  private disposed;
44
+ private projectFsNegotiated;
43
45
  private closeTrigger;
44
46
  private lastSocketError;
45
47
  /** Buffered agent events awaiting server ack, keyed by event id */
@@ -62,6 +64,8 @@ export declare class CliRelayClient {
62
64
  sendAgentEvent(event: EventFrame): void;
63
65
  getState(): CliRelayState;
64
66
  private handleFrame;
67
+ private handleProjectFsNegotiation;
68
+ private setProjectFsNegotiated;
65
69
  /** Resend all buffered agent events after reconnect, in insertion order */
66
70
  private flushSendBuffer;
67
71
  private bufferEvent;
@@ -1 +1 @@
1
- import R from"ws";import{generateTraceId as d}from"../log-reporter.js";import{recordPersonalSyncLocalMetric as c}from"../personal-sync/metrics.js";class y extends Error{rejection;constructor(e){super(e.message||e.code),this.rejection=e,this.name="SessionSyncRejectedError"}}const P=9e4,S=3e4,I=1e3,A=.2,C=2e4,_=1e4,b=15e3,k=1e3,v=new Set([4001,4003,4004,4009,4402,4403,4406]),N=["upgrade_required","epoch_mismatch","token_revoked","ownership_transferred","invalid_identity"];function O(l,e){if(v.has(l))return!0;const t=e.trim().toLowerCase();return N.some(s=>t.startsWith(s))}class j{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",i=this.options.autoUpgradePolicy?`&upgradePolicy=${encodeURIComponent(this.options.autoUpgradePolicy)}`:"",o=this.options.sessionSyncProtocolVersion?`&syncProtocol=${this.options.sessionSyncProtocolVersion}`:"",r=this.options.sessionSyncCapabilities?.length?`&syncCapabilities=${encodeURIComponent(this.options.sessionSyncCapabilities.join(","))}`:"",u=this.options.daemonInstallationId?`&daemonInstallationId=${encodeURIComponent(this.options.daemonInstallationId)}`:"",g=this.options.dataEpoch?`&dataEpoch=${encodeURIComponent(this.options.dataEpoch)}`:"",f=this.options.updaterProtocolVersion?`&updaterProtocol=${encodeURIComponent(this.options.updaterProtocolVersion)}`:"",T=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${i}${o}${r}${u}${g}${f}`,n=new R(T);this.ws=n,this.connectTimer=setTimeout(()=>{this.ws===n&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",n.terminate())},b),n.on("open",()=>{this.ws===n&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(n),this.flushSendBuffer(),this.options.onConnected?.())}),n.on("pong",()=>{this.ws===n&&(this.clearPongTimer(),this.resetHeartbeat())}),n.on("message",a=>{if(this.ws!==n)return;let h;try{h=JSON.parse(a.toString())}catch{return}this.handleFrame(h)}),n.on("close",(a,h)=>{if(this.ws!==n)return;const E=this.state,m=h.toString(),p=O(a,m),w={code:a,reason:m,error:this.lastSocketError,phase:E,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};p&&(this.disposed=!0,c("permanentStops")),this.cleanup(p),this.options.onDisconnected?.(w),p||this.scheduleReconnect()}),n.on("error",a=>{this.lastSocketError=a.message,this.closeTrigger="socket-error",n.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),this.ws.send(JSON.stringify(e)))}sendResAndWait(e){if(this.state!=="connected"||!this.ws)return Promise.reject(new Error("Relay is not connected"));e.traceId||(e.traceId=d());const t=this.ws;return new Promise((s,i)=>{t.send(JSON.stringify(e),o=>{o?i(o):s()})})}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),c("relayEventsSent"),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=d()),c("relayRequestsSent"),new Promise((s,i)=>{const o=this.pendingRequests.get(e.id);o&&(clearTimeout(o.timer),o.reject(new Error("Superseded by a newer relay request")));const r=setTimeout(()=>{this.pendingRequests.delete(e.id),i(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:i,timer:r}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=d()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return c("relayEventsSent"),new Promise((i,o)=>{const r=this.pendingAcks.get(s);r&&(clearTimeout(r.timer),r.reject(new Error("Superseded by a newer buffered event")));const u=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),o(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:i,reject:o,timer:u}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(c("relayFramesReceived"),e.type==="res"&&e.rejection&&c("structuredRejections"),e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);if(t)if(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok)t.resolve(e);else{const i=e.rejection;t.reject(i?new y(i):new Error(e.error??"Relay event failed"))}const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}e.type==="req"&&this.options.onReq?.(e)}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>k){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},_);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},C)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},P)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(I*Math.pow(2,this.reconnectAttempts),S),t=e*A*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,c("relayReconnects"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{j as CliRelayClient,y as SessionSyncRejectedError,O as isPermanentRelayClose};
1
+ import w from"ws";import I from"node:crypto";import{parseProjectFsNegotiationChallenge as S,projectFsNegotiationProofPayload as _,PROJECT_FS_NEGOTIATION_METHOD as A,PROJECT_FS_OPAQUE_CAPABILITY as m,PROJECT_FS_SENSITIVE_AUTHORITY_VERSION as N,isProjectFsMethod as C}from"@shennian/wire";import{generateTraceId as d}from"../log-reporter.js";import{recordPersonalSyncLocalMetric as a}from"../personal-sync/metrics.js";class b extends Error{rejection;constructor(e){super(e.message||e.code),this.rejection=e,this.name="SessionSyncRejectedError"}}const k=9e4,j=3e4,O=1e3,F=.2,v=2e4,$=1e4,U=15e3,B=1e3,M=new Set([4001,4003,4004,4009,4402,4403,4406]),q=["upgrade_required","epoch_mismatch","token_revoked","ownership_transferred","invalid_identity"];function H(h,e){if(M.has(h))return!0;const t=e.trim().toLowerCase();return q.some(s=>t.startsWith(s))}class Y{options;ws=null;state="disconnected";reconnectAttempts=0;heartbeatTimer=null;reconnectTimer=null;connectTimer=null;pingTimer=null;pongTimer=null;disposed=!1;projectFsNegotiated=!1;closeTrigger;lastSocketError;sendBuffer=new Map;pendingAcks=new Map;pendingRequests=new Map;constructor(e){this.options=e}connect(){if(this.disposed)return;this.setProjectFsNegotiated(!1),this.ws&&this.cleanup(!1),this.state="connecting";const e=this.options.serverUrl.includes("?")?"&":"?",t=this.options.cliVersion?`&v=${encodeURIComponent(this.options.cliVersion)}`:"",s=this.options.agentList?.length?`&agents=${encodeURIComponent(this.options.agentList.join(","))}`:"",n=this.options.autoUpgradePolicy?`&upgradePolicy=${encodeURIComponent(this.options.autoUpgradePolicy)}`:"",o=this.options.sessionSyncProtocolVersion?`&syncProtocol=${this.options.sessionSyncProtocolVersion}`:"",r=this.options.sessionSyncCapabilities?.filter(c=>c!==m),p=r?.length?`&syncCapabilities=${encodeURIComponent(r.join(","))}`:"",f=this.options.daemonInstallationId?`&daemonInstallationId=${encodeURIComponent(this.options.daemonInstallationId)}`:"",T=this.options.dataEpoch?`&dataEpoch=${encodeURIComponent(this.options.dataEpoch)}`:"",E=this.options.updaterProtocolVersion?`&updaterProtocol=${encodeURIComponent(this.options.updaterProtocolVersion)}`:"",y=`${this.options.serverUrl}${e}token=${encodeURIComponent(this.options.machineToken)}${t}${s}${n}${o}${p}${f}${T}${E}`,i=new w(y);this.ws=i,this.connectTimer=setTimeout(()=>{this.ws===i&&this.state==="connecting"&&(this.closeTrigger="connect-timeout",i.terminate())},U),i.on("open",()=>{this.ws===i&&(this.clearConnectTimer(),this.state="connected",this.reconnectAttempts=0,this.startHeartbeat(),this.startPing(i),this.flushSendBuffer(),this.options.onConnected?.())}),i.on("pong",()=>{this.ws===i&&(this.clearPongTimer(),this.resetHeartbeat())}),i.on("message",c=>{if(this.ws!==i)return;let l;try{l=JSON.parse(c.toString())}catch{return}this.handleFrame(l)}),i.on("close",(c,l)=>{if(this.ws!==i)return;const R=this.state,g=l.toString(),u=H(c,g),P={code:c,reason:g,error:this.lastSocketError,phase:R,trigger:this.closeTrigger??"socket-close",reconnectAttempt:this.reconnectAttempts+1};u&&(this.disposed=!0,a("permanentStops")),this.cleanup(u),this.options.onDisconnected?.(P),u||this.scheduleReconnect()}),i.on("error",c=>{this.lastSocketError=c.message,this.closeTrigger="socket-error",i.close()})}disconnect(){this.disposed=!0,this.cleanup(!0)}sendRes(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),this.ws.send(JSON.stringify(e)))}sendResAndWait(e){if(this.state!=="connected"||!this.ws)return Promise.reject(new Error("Relay is not connected"));e.traceId||(e.traceId=d());const t=this.ws;return new Promise((s,n)=>{t.send(JSON.stringify(e),o=>{o?n(o):s()})})}sendEvent(e){this.state!=="connected"||!this.ws||(e.traceId||(e.traceId=d()),a("relayEventsSent"),this.ws.send(JSON.stringify(e)))}sendReq(e,t=6e4){return this.state!=="connected"||!this.ws?Promise.reject(new Error("Relay is not connected")):(e.traceId||(e.traceId=d()),a("relayRequestsSent"),new Promise((s,n)=>{const o=this.pendingRequests.get(e.id);o&&(clearTimeout(o.timer),o.reject(new Error("Superseded by a newer relay request")));const r=setTimeout(()=>{this.pendingRequests.delete(e.id),n(new Error("Relay request timed out"))},t);this.pendingRequests.set(e.id,{resolve:s,reject:n,timer:r}),this.ws?.send(JSON.stringify(e))}))}sendBufferedEvent(e,t=12e4){if(e.traceId||(e.traceId=d()),!e.id)return this.sendEvent(e),Promise.resolve();const s=e.id;return a("relayEventsSent"),new Promise((n,o)=>{const r=this.pendingAcks.get(s);r&&(clearTimeout(r.timer),r.reject(new Error("Superseded by a newer buffered event")));const p=setTimeout(()=>{this.pendingAcks.delete(s),this.sendBuffer.delete(s),o(new Error("Relay event acknowledgement timed out"))},t);this.pendingAcks.set(s,{resolve:n,reject:o,timer:p}),this.bufferEvent(e)})}sendAgentEvent(e){this.sendBufferedEvent(e).catch(()=>{})}getState(){return this.state}handleFrame(e){if(a("relayFramesReceived"),e.type==="res"&&e.rejection&&a("structuredRejections"),e.type==="event"&&e.event==="tick"){this.resetHeartbeat();return}if(e.type==="res"&&e.id){this.sendBuffer.delete(e.id);const t=this.pendingAcks.get(e.id);if(t)if(this.pendingAcks.delete(e.id),clearTimeout(t.timer),e.ok)t.resolve(e);else{const n=e.rejection;t.reject(n?new b(n):new Error(e.error??"Relay event failed"))}const s=this.pendingRequests.get(e.id);s&&(this.pendingRequests.delete(e.id),clearTimeout(s.timer),s.resolve(e));return}if(e.type==="req"&&e.method===A){this.handleProjectFsNegotiation(e);return}if(e.type==="req"&&C(e.method)&&!this.projectFsNegotiated){this.sendRes({type:"res",id:e.id,ok:!1,error:"capability_required",payload:{rejection:{code:"capability_required",retryable:!1,requiredAction:"upgrade_daemon"}}});return}e.type==="req"&&this.options.onReq?.(e)}handleProjectFsNegotiation(e){this.setProjectFsNegotiated(!1,!0);const t=S(e.params);if(!t||t.daemonInstallationId!==this.options.daemonInstallationId){this.sendRes({type:"res",id:e.id,ok:!1,error:"invalid_identity"});return}const s=I.createHmac("sha256",this.options.machineToken).update(_(e.id,t)).digest("base64url");this.sendRes({type:"res",id:e.id,ok:!0,payload:{version:N,capability:m,nonce:t.nonce,daemonInstallationId:t.daemonInstallationId,proof:s}}),this.setProjectFsNegotiated(!0)}setProjectFsNegotiated(e,t=!1){!t&&this.projectFsNegotiated===e||(this.projectFsNegotiated=e,this.options.onProjectFsCapabilityChanged?.(e))}flushSendBuffer(){if(!(this.sendBuffer.size===0||!this.ws))for(const e of this.sendBuffer.values()){if(this.state!=="connected"||!this.ws)break;this.ws.send(JSON.stringify(e))}}bufferEvent(e){if(!e.id){this.sendEvent(e);return}if(this.sendBuffer.set(e.id,e),this.sendBuffer.size>B){const t=this.sendBuffer.keys().next().value;if(t){this.sendBuffer.delete(t);const s=this.pendingAcks.get(t);s&&(this.pendingAcks.delete(t),clearTimeout(s.timer),s.reject(new Error("Relay send buffer overflow")))}}this.state==="connected"&&this.ws&&this.ws.send(JSON.stringify(e))}clearConnectTimer(){this.connectTimer!==null&&(clearTimeout(this.connectTimer),this.connectTimer=null)}startPing(e){this.clearPing(),this.pingTimer=setInterval(()=>{if(!(this.ws!==e||this.state!=="connected")){this.pongTimer=setTimeout(()=>{this.closeTrigger="pong-timeout",e.close()},$);try{e.ping()}catch{this.clearPongTimer(),this.closeTrigger="socket-error",e.close()}}},v)}clearPing(){this.pingTimer!==null&&(clearInterval(this.pingTimer),this.pingTimer=null),this.clearPongTimer()}clearPongTimer(){this.pongTimer!==null&&(clearTimeout(this.pongTimer),this.pongTimer=null)}startHeartbeat(){this.clearHeartbeat(),this.heartbeatTimer=setTimeout(()=>{this.closeTrigger="heartbeat-timeout",this.ws?.close()},k)}resetHeartbeat(){this.startHeartbeat()}clearHeartbeat(){this.heartbeatTimer!==null&&(clearTimeout(this.heartbeatTimer),this.heartbeatTimer=null)}scheduleReconnect(){if(this.disposed)return;const e=Math.min(O*Math.pow(2,this.reconnectAttempts),j),t=e*F*(Math.random()*2-1),s=Math.round(e+t);this.reconnectAttempts++,a("relayReconnects"),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.connect()},s)}cleanup(e){if(this.setProjectFsNegotiated(!1),this.state="disconnected",this.closeTrigger=void 0,this.lastSocketError=void 0,this.clearConnectTimer(),this.clearHeartbeat(),this.clearPing(),this.reconnectTimer!==null&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.ws){const t=this.ws;this.ws=null,t.removeAllListeners();try{t.close()}catch{}}for(const[t,s]of this.pendingRequests)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingRequests.delete(t);if(e)for(const[t,s]of this.pendingAcks)clearTimeout(s.timer),s.reject(new Error("Relay client disconnected")),this.pendingAcks.delete(t)}}export{Y as CliRelayClient,b as SessionSyncRejectedError,H as isPermanentRelayClose};
@@ -4,6 +4,7 @@ import type { ManagedRoomEnqueueParams, ManagedRoomQueueContext, PreparedManaged
4
4
  import type { RoomHostSessionObservationStore } from './host-session-observations.js';
5
5
  import { type RoomSessionRegistration, type RoomSessionRegistry } from './session-registry.js';
6
6
  import type { RoomActivationAudit } from './activation-audit.js';
7
+ import type { DetectedAgent } from '../agents/detect.js';
7
8
  export type RoomConversationQueueInput = {
8
9
  params: ManagedRoomEnqueueParams;
9
10
  context: ManagedRoomQueueContext;
@@ -60,6 +61,7 @@ export declare function resolveManagedAgentPolicy(input: {
60
61
  permissionPolicy: ManagedAgentPermissionPolicy;
61
62
  permissionConfirmation: ManagedAgentPermissionConfirmation | null;
62
63
  workDir: string;
64
+ runtime?: Pick<DetectedAgent, 'version' | 'runtimePlatform' | 'runtimeIsWsl' | 'executableFingerprint'>;
63
65
  }): Promise<CompiledManagedAgentPolicy>;
64
66
  export declare function managedAgentPermissionConfirmationMatches(input: {
65
67
  confirmation: ManagedAgentPermissionConfirmation | null;
@@ -1,8 +1,8 @@
1
- import{isAvailableBuiltinAgentType as A,managedAgentExecutionMode as k}from"@shennian/wire";import{getBuiltinAgentVersion as P,resolveBuiltinCommand as S}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as B,resolveCustomRuntimeExecutableFingerprint as K}from"../agents/custom-managed-runtime.js";import{loadConfig as x}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as D}from"./activation-policy.js";import{composeManagedRoomPrompt as W}from"./managed-prompt-composer.js";const E=4,R=6e4,N=6,F=5*6e4;class re{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,r,o={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],u=new Map,m=new Set,g=[...i].sort((n,a)=>H(n.sequence,a.sequence));for(const n of r){const a=this.deps.registry.getByRegistryKey(n.registryKey);!a?.binding||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const n of r){const a=this.deps.registry.getByRegistryKey(n.registryKey);if(!a?.binding||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const p=this.deps.observations.getBySource(a);if(!p?.workspacePath||p.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,o);for(const s of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(a.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:p.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const n of g){const a=new Set(D(n,r).map(p=>p.registration.registryKey));for(const p of r){const s=this.deps.registry.getByRegistryKey(p.registryKey);if(!s?.binding||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||o.agentsByIdentityId?.[s.binding.agentIdentityId]?.configurationPending||BigInt(n.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const f=u.get(s.registryKey)??[...s.binding.mentionContext??[]];if(u.set(s.registryKey,f),a.has(s.registryKey)){const l=(n.activationCause?.hopCount??0)+(n.author.principalType==="agent"?1:0),I=n.activationCause?.rootCauseMessageId??n.id;if(l>E)this.audit(s,n.id,I,l,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:s.registryKey,windowMs:R,maxActivations:N,pauseMs:F,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(s,n.id,I,l,"suppressed","rate_limited"),c.push({registryKey:s.registryKey,sequence:n.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=M([n]),C=s.binding.activationMode==="mention_only"?z([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(s.registryKey,o),d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:C,agentType:y(s.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${n.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${n.id}`},context:{registryKey:s.registryKey,causeMessageId:n.id,rootCauseMessageId:I,sequence:n.sequence,hopCount:l}}}),s.binding.activationMode==="mention_only"&&u.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&n.kind!=="system"&&n.author.membershipId!==s.binding.agentMembershipId&&(f.some(l=>l.sequence===n.sequence)||f.push({sequence:n.sequence,deliveryText:M([n])}));c.push({registryKey:s.registryKey,sequence:n.sequence})}}await this.deps.enqueueConversationBatch(d.map(n=>n.input));for(const n of m)this.deps.registry.retireLegacyActivationState(n,this.deps.now?.()??new Date);for(const[n,a]of u)this.deps.registry.replaceMentionContext(n,a,this.deps.now?.()??new Date);for(const n of d)this.audit(n.registration,n.input.context.causeMessageId,n.input.context.rootCauseMessageId,n.input.context.hopCount,"queued","conversation_queue");for(const n of c)this.deps.registry.advanceCursor({registryKey:n.registryKey,cursor:"activated",sequence:n.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r)throw new Error("managed_room_binding_unavailable");const o=await this.prepareManagedTurn(r,i.causeMessageId);return this.audit(r,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),o}async prepareDirectConversationTurn(e){const i=this.deps.registry.getByNianSessionId(e);return i?this.prepareManagedTurn(i):null}async prepareManagedTurn(e,i){if(!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const r=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!r)throw new Error("managed_room_prompt_context_unavailable");const o=r.agentsByIdentityId?.[e.binding.agentIdentityId];if(o?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!o?.permissionPolicy)throw new Error("managed_agent_policy_missing");const d=o.sessionMode==="manager"?"manager":"standard",c=d==="manager"?{...o.managerConfig,defaultWorkerAgentType:o.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:o.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}):await X({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}),g=j(r,e,m),n=this.deps.issueManagedRoomContext(e.registryKey,i);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(n))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:m.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:g.systemPrompt,sessionMode:d,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:n,managedAgentPolicy:m,managedRoomPromptSnapshot:g.snapshot}}audit(e,i,r,o,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:r,hopCount:o,outcome:d,reason:c,at:this.deps.now?.()})}}function M(t){const e=t.map(i=>{const r=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",o=i.attachments.length>0?`
1
+ import{isAvailableBuiltinAgentType as A,managedAgentExecutionMode as k}from"@shennian/wire";import{getBuiltinAgentVersion as P,resolveBuiltinCommand as S}from"../agents/command-spec.js";import{registerCustomRuntimeSecurity as x,resolveCustomRuntimeExecutableFingerprint as B}from"../agents/custom-managed-runtime.js";import{loadConfig as K}from"../config/index.js";import{resolveManagedAgentExecutableIdentity as $}from"../security/managed-agent-executable.js";import{compileManagedAgentPolicy as T}from"../security/managed-agent-policy.js";import{resolveManagedAgentRuntimeProtection as q}from"../security/managed-agent-runtime.js";import{selectRoomActivationTargets as W}from"./activation-policy.js";import{composeManagedRoomPrompt as D}from"./managed-prompt-composer.js";const E=4,R=6e4,F=6,N=5*6e4;class re{deps;latestContexts=new Map;constructor(e){this.deps=e}async processMessages(e,i,r,o={roomName:"\u5F53\u524D\u7FA4\u804A"}){const d=[],c=[],u=new Map,m=new Set,g=[...i].sort((t,a)=>H(t.sequence,a.sequence));for(const t of r){const a=this.deps.registry.getByRegistryKey(t.registryKey);!a?.binding||a.binding.roomId!==e||a.binding.status!=="active"&&a.binding.status!=="paused"&&a.binding.activationQueue.length}for(const t of r){const a=this.deps.registry.getByRegistryKey(t.registryKey);if(!a?.binding||a.binding.roomId!==e||a.binding.activationQueue.length===0&&!a.binding.activeActivation||(m.add(a.registryKey),a.binding.status!=="active"||a.binding.activationMode==="paused")||a.binding.activationQueue.length===0)continue;const l=this.deps.observations.getBySource(a);if(!l?.workspacePath||l.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");this.latestContexts.set(a.registryKey,o);for(const s of a.binding.activationQueue)d.push({registration:a,input:{params:{sessionId:a.nianSessionId,text:s.deliveryText??"\u7FA4\u804A\u4E2D\u6709\u4E00\u6761\u5F85\u5904\u7406\u6D88\u606F\uFF0C\u8BF7\u7ED3\u5408\u5F53\u524D\u7FA4\u804A\u4E0A\u4E0B\u6587\u5904\u7406\u3002",agentType:y(a.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[a.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:l.workspacePath,agentSessionId:a.sourceSessionKey,queueMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`,clientMessageId:`room-activation-${a.binding.bindingId}-${s.causeMessageId}`},context:{registryKey:a.registryKey,causeMessageId:s.causeMessageId,rootCauseMessageId:s.rootCauseMessageId,sequence:s.sequence,hopCount:s.hopCount}}})}for(const t of g){const a=new Set(W(t,r).map(l=>l.registration.registryKey));for(const l of r){const s=this.deps.registry.getByRegistryKey(l.registryKey);if(!s?.binding||s.binding.roomId!==e||s.binding.status!=="active"&&s.binding.status!=="paused"||o.agentsByIdentityId?.[s.binding.agentIdentityId]?.configurationPending||BigInt(t.sequence)<=BigInt(s.binding.lastActivatedSequence))continue;const f=u.get(s.registryKey)??[...s.binding.mentionContext??[]];if(u.set(s.registryKey,f),a.has(s.registryKey)){const p=(t.activationCause?.hopCount??0)+(t.author.principalType==="agent"?1:0),I=t.activationCause?.rootCauseMessageId??t.id;if(p>E)this.audit(s,t.id,I,p,"suppressed","max_hops");else{const h=this.deps.registry.claimActivation({registryKey:s.registryKey,windowMs:R,maxActivations:F,pauseMs:N,now:this.deps.now?.()});if(!h.allowed){h.newlyPaused&&this.audit(s,t.id,I,p,"suppressed","rate_limited"),c.push({registryKey:s.registryKey,sequence:t.sequence});continue}const b=this.deps.observations.getBySource(s);if(!b?.workspacePath||b.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const v=M([t]),C=s.binding.activationMode==="mention_only"?z([...f.map(_=>_.deliveryText),v]):v;this.latestContexts.set(s.registryKey,o),d.push({registration:s,input:{params:{sessionId:s.nianSessionId,text:C,agentType:y(s.runtimeAdapter),sessionMode:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.sessionMode??"standard",managerConfig:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig??null,managerDefaultWorkerAgentType:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:o.agentsByIdentityId?.[s.binding.agentIdentityId]?.managerConfig?.defaultWorkerModelId??null,workDir:b.workspacePath,agentSessionId:s.sourceSessionKey,queueMessageId:`room-activation-${s.binding.bindingId}-${t.id}`,clientMessageId:`room-activation-${s.binding.bindingId}-${t.id}`},context:{registryKey:s.registryKey,causeMessageId:t.id,rootCauseMessageId:I,sequence:t.sequence,hopCount:p}}}),s.binding.activationMode==="mention_only"&&u.set(s.registryKey,[])}}else s.binding.activationMode==="mention_only"&&t.kind!=="system"&&t.author.membershipId!==s.binding.agentMembershipId&&(f.some(p=>p.sequence===t.sequence)||f.push({sequence:t.sequence,deliveryText:M([t])}));c.push({registryKey:s.registryKey,sequence:t.sequence})}}await this.deps.enqueueConversationBatch(d.map(t=>t.input));for(const t of m)this.deps.registry.retireLegacyActivationState(t,this.deps.now?.()??new Date);for(const[t,a]of u)this.deps.registry.replaceMentionContext(t,a,this.deps.now?.()??new Date);for(const t of d)this.audit(t.registration,t.input.context.causeMessageId,t.input.context.rootCauseMessageId,t.input.context.hopCount,"queued","conversation_queue");for(const t of c)this.deps.registry.advanceCursor({registryKey:t.registryKey,cursor:"activated",sequence:t.sequence})}async prepareConversationTurn(e){const i=e.at(-1);if(!i)throw new Error("managed_room_queue_context_missing");const r=this.deps.registry.getByRegistryKey(i.registryKey);if(!r)throw new Error("managed_room_binding_unavailable");const o=await this.prepareManagedTurn(r,i.causeMessageId);return this.audit(r,i.causeMessageId,i.rootCauseMessageId,i.hopCount,"activated","conversation_queue_dispatch"),o}async prepareDirectConversationTurn(e){const i=this.deps.registry.getByNianSessionId(e);return i?this.prepareManagedTurn(i):null}async prepareManagedTurn(e,i){if(!e.binding||e.binding.status!=="active")throw new Error("managed_room_binding_unavailable");const r=this.deps.loadContext?await this.deps.loadContext(e):this.latestContexts.get(e.registryKey);if(!r)throw new Error("managed_room_prompt_context_unavailable");const o=r.agentsByIdentityId?.[e.binding.agentIdentityId];if(o?.configurationPending)throw new Error("managed_agent_configuration_pending");if(!o?.permissionPolicy)throw new Error("managed_agent_policy_missing");const d=o.sessionMode==="manager"?"manager":"standard",c=d==="manager"?{...o.managerConfig,defaultWorkerAgentType:o.managerConfig?.defaultWorkerAgentType??y(e.runtimeAdapter),defaultWorkerModelId:o.managerConfig?.defaultWorkerModelId??null}:null,u=this.deps.observations.getBySource(e);if(!u?.workspacePath||u.lifecycleState==="closed")throw new Error("managed_room_workspace_unavailable");const m=this.deps.resolveManagedAgentPolicy?await this.deps.resolveManagedAgentPolicy({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}):await X({registration:e,permissionPolicy:o.permissionPolicy,permissionConfirmation:o.permissionConfirmation,workDir:u.workspacePath}),g=j(r,e,m),t=this.deps.issueManagedRoomContext(e.registryKey,i);if(!/^mrc_[A-Za-z0-9_-]{43}$/.test(t))throw new Error("managed_room_context_unavailable");return{agentType:y(e.runtimeAdapter),workDir:m.adapterPolicy.workDir,agentSessionId:e.sourceSessionKey,systemPrompt:g.systemPrompt,sessionMode:d,managerConfig:c,managerDefaultWorkerAgentType:c?.defaultWorkerAgentType??null,managerDefaultWorkerModelId:c?.defaultWorkerModelId??null,roomContextRegistryKey:e.registryKey,managedRoomContextToken:t,managedAgentPolicy:m,managedRoomPromptSnapshot:g.snapshot}}audit(e,i,r,o,d,c){e.binding&&this.deps.audit?.record({roomId:e.binding.roomId,bindingId:e.binding.bindingId,causeMessageId:i,rootCauseMessageId:r,hopCount:o,outcome:d,reason:c,at:this.deps.now?.()})}}function M(n){const e=n.map(i=>{const r=i.text?.trim()||"\u53D1\u9001\u4E86\u4E00\u6761\u6D88\u606F",o=i.attachments.length>0?`
2
2
  ${i.attachments.map(V).join(`
3
- `)}`:"";return{speaker:i.author.displayName,body:`${r}${o}`}});return w(e)}function V(t){return`\u9644\u4EF6\uFF1A${`${O(t.name)} \xB7 ${L(t.byteSize)}`}\uFF5C${t.mimeType}\uFF5CattachmentId=${t.attachmentId}`}function O(t){return String(t||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function L(t){const e=Number(t);if(!Number.isFinite(e)||e<0)return`${t} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let r=e,o=-1;do r/=1024,o+=1;while(r>=1024&&o<i.length-1);const d=r>=100?0:r>=10?1:2;return`${r.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[o]}`}function z(t){return w(t.map(Q))}function Q(t){const e=t.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=t.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const r=t.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(r)return{speaker:r[1],body:r[2]};const o=t.match(/^([^\n]+)\n([\s\S]*)$/);return o?{speaker:o[1],body:o[2]}:{speaker:"\u7FA4\u6210\u5458",body:t.trim()}}function w(t){if(t.length===1){const[e]=t;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
3
+ `)}`:"";return{speaker:i.author.displayName,body:`${r}${o}`}});return w(e)}function V(n){return`\u9644\u4EF6\uFF1A${`${O(n.name)} \xB7 ${L(n.byteSize)}`}\uFF5C${n.mimeType}\uFF5CattachmentId=${n.attachmentId}`}function O(n){return String(n||"\u9644\u4EF6").replace(/[[\]\\]/g,"\\$&").replace(/[\r\n]+/g," ")}function L(n){const e=Number(n);if(!Number.isFinite(e)||e<0)return`${n} B`;if(e<1024)return`${e} B`;const i=["KB","MB","GB","TB"];let r=e,o=-1;do r/=1024,o+=1;while(r>=1024&&o<i.length-1);const d=r>=100?0:r>=10?1:2;return`${r.toFixed(d).replace(/\.0+$|(?<=\.[0-9])0+$/,"")} ${i[o]}`}function z(n){return w(n.map(Q))}function Q(n){const e=n.match(/^\[群消息\][ \t]+<([^>\n]+)>\n([\s\S]*)$/);if(e)return{speaker:e[1],body:e[2]};const i=n.match(/^\[群消息\]\n<([^>\n]+)>\n([\s\S]*)$/);if(i)return{speaker:i[1],body:i[2]};const r=n.match(/^群消息\n([^\n]+)\n([\s\S]*)$/);if(r)return{speaker:r[1],body:r[2]};const o=n.match(/^([^\n]+)\n([\s\S]*)$/);return o?{speaker:o[1],body:o[2]}:{speaker:"\u7FA4\u6210\u5458",body:n.trim()}}function w(n){if(n.length===1){const[e]=n;return`[\u7FA4\u6D88\u606F] <${e.speaker}>
4
4
  ${e.body}`}return`[\u7FA4\u6D88\u606F]
5
- ${t.map(e=>`<${e.speaker}>
5
+ ${n.map(e=>`<${e.speaker}>
6
6
  ${e.body}`).join(`
7
7
 
8
- `)}`}function j(t,e,i){const r=e.binding?t.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!r)throw new Error("managed_room_prompt_incomplete");return W({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:r.displayName,description:r.description,systemPrompt:t.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:r.configurationVersion},room:{name:t.roomName,activationMode:e.binding.activationMode,members:t.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function X(t){const e=y(t.registration.runtimeAdapter),i=A(e)?S(e):null,r=e.startsWith("custom:")?x().customAgents?.[e.slice(7)]:void 0;r&&B(e,r);const o=i?P(e,i):r?.caps.version,d=i?$(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:r?K(r):void 0},c={agentType:e,runtimeVersion:o,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=q(c),m=G({confirmation:t.permissionConfirmation,policy:t.permissionPolicy,runtimeAdapter:e,protection:u}),g=await T({...c,policy:t.permissionPolicy,workDir:t.workDir,hostFullAccessConfirmed:m&&t.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&t.permissionConfirmation?.limitedProtection});if(!g.ok){const n=g.unsupportedCapabilities?.length?`:${g.unsupportedCapabilities.join(",")}`:"";throw new Error(`${g.errorCode}${n}`)}return g.value}function G(t){const{confirmation:e,policy:i,runtimeAdapter:r,protection:o}=t,d=k(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===r&&e.runtimeVersion===o.runtimeVersion&&e.platform===o.platform&&e.executableFingerprint===o.executableFingerprint&&(e.executionMode??"restricted")===d&&e.hostFullAccess===(d==="native-full"||i.fileAccess==="host-full")&&e.limitedProtection===(d==="restricted"&&o.level!=="hard"))}function y(t){return t==="claude_code"?"claude":t}function H(t,e){const i=BigInt(t),r=BigInt(e);return i<r?-1:i>r?1:0}export{re as RoomActivationService,G as managedAgentPermissionConfirmationMatches,X as resolveManagedAgentPolicy};
8
+ `)}`}function j(n,e,i){const r=e.binding?n.agentsByIdentityId?.[e.binding.agentIdentityId]:void 0;if(!e.binding||!r)throw new Error("managed_room_prompt_incomplete");return D({runtimeAdapter:y(e.runtimeAdapter),agent:{displayName:r.displayName,description:r.description,systemPrompt:n.agentSystemPromptsByIdentityId?.[e.binding.agentIdentityId],configurationVersion:r.configurationVersion},room:{name:n.roomName,activationMode:e.binding.activationMode,members:n.members},runtimePermissions:{requestedPolicy:i.requestedPolicy,enforcement:i.enforcement,resolvedWorkDir:i.adapterPolicy.workDir}})}async function X(n){const e=y(n.registration.runtimeAdapter),i=A(e)?S(e):null,r=e.startsWith("custom:")?K().customAgents?.[e.slice(7)]:void 0;r&&x(e,r);const o=n.runtime?.version??(i?P(e,i):r?.caps.version),d=n.runtime?{platform:n.runtime.runtimePlatform??process.platform,isWsl:n.runtime.runtimeIsWsl??!1,executableFingerprint:n.runtime.executableFingerprint}:i?$(i):{platform:process.platform,isWsl:process.platform==="linux"&&!!(process.env.WSL_DISTRO_NAME||process.env.WSL_INTEROP),executableFingerprint:r?B(r):void 0},c={agentType:e,runtimeVersion:o,platform:d.platform,isWsl:d.isWsl,executableFingerprint:d.executableFingerprint,requireExecutableFingerprint:!0},u=q(c),m=G({confirmation:n.permissionConfirmation,policy:n.permissionPolicy,runtimeAdapter:e,protection:u}),g=await T({...c,policy:n.permissionPolicy,workDir:n.workDir,hostFullAccessConfirmed:m&&n.permissionConfirmation?.hostFullAccess,limitedProtectionConfirmed:m&&n.permissionConfirmation?.limitedProtection});if(!g.ok){const t=g.unsupportedCapabilities?.length?`:${g.unsupportedCapabilities.join(",")}`:"";throw new Error(`${g.errorCode}${t}`)}return g.value}function G(n){const{confirmation:e,policy:i,runtimeAdapter:r,protection:o}=n,d=k(i);return!!(e&&e.policyVersion===i.version&&e.runtimeAdapter===r&&e.runtimeVersion===o.runtimeVersion&&e.platform===o.platform&&e.executableFingerprint===o.executableFingerprint&&(e.executionMode??"restricted")===d&&e.hostFullAccess===(d==="native-full"||i.fileAccess==="host-full")&&e.limitedProtection===(d==="restricted"&&o.level!=="hard"))}function y(n){return n==="claude_code"?"claude":n}function H(n,e){const i=BigInt(n),r=BigInt(e);return i<r?-1:i>r?1:0}export{re as RoomActivationService,G as managedAgentPermissionConfirmationMatches,X as resolveManagedAgentPolicy};
@@ -1,4 +1,4 @@
1
- import { type AgentType } from '@shennian/wire';
1
+ import { type AgentType, type RoomActivationMode } from '@shennian/wire';
2
2
  import type { HttpRoomBindingClient } from './room-binding-client.js';
3
3
  import type { RoomHostSessionObservationStore } from './host-session-observations.js';
4
4
  import type { RoomSessionRegistry } from './session-registry.js';
@@ -9,6 +9,7 @@ export type ManagedRoomBindInput = {
9
9
  assignmentIntentId: string;
10
10
  sourceSessionKey: string;
11
11
  agentType: AgentType;
12
+ activationMode?: RoomActivationMode;
12
13
  };
13
14
  export type ManagedRoomRestartInput = ManagedRoomBindInput & {
14
15
  bindingId: string;
@@ -1 +1 @@
1
- import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import h from"node:crypto";class u{deps;constructor(e){this.deps=e}async bind(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey,activationCapability:"managed",activationMode:"auto_follow"});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode});const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"delivered",sequence:n.resumeSequence}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"activated",sequence:n.resumeSequence});for(const c of this.deps.registry.list())c.registryKey!==r.registryKey&&c.nianSessionId===e.previousNianSessionId&&c.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(c.registryKey);const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const s=await this.deps.bindingClient.status(e),i=s.managedWorkspacePath?.trim(),t=s.agent?.sessionMode;if(s.agentIdentityId!==e.agentIdentityId||t!=="standard"&&t!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function g(d){return h.createHash("sha256").update(d,"utf8").digest("hex")}function I(d){const e=y(d);if(e)return e;throw new Error(`Agent ${d} does not support managed Room binding.`)}export{u as TrustedManagedRoomBinder};
1
+ import{roomRuntimeAdapterForAgent as y}from"@shennian/wire";import h from"node:crypto";class u{deps;constructor(e){this.deps=e}async bind(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.create({machineToken:i,bindingIntentId:e.assignmentIntentId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey,activationCapability:"managed",activationMode:e.activationMode??"auto_follow"});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode});const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedBind"}),n}async restart(e){const{machineId:s,machineToken:i}=this.deps.getMachineCredentials();if(!s||!i)throw new Error("Machine is not paired with Shennian.");const t=I(e.agentType),o=this.deps.hostProcessIdentity(),r=this.deps.registry.register({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,nianSessionId:e.nianSessionId,hostProcessIdentity:o,capabilities:{canIdentifySession:!0,canBindAutomatically:!0,canOpenEmbeddedRoomPage:!1,canResumeIdleSession:!0,canActivateWhileHostOpen:!0}}),n=await this.deps.bindingClient.restart({machineToken:i,bindingId:e.bindingId,previousNianSessionId:e.previousNianSessionId,roomId:e.roomId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:t,sessionRegistryKey:r.registryKey});this.deps.registry.setBinding({registryKey:r.registryKey,bindingId:n.binding.id,roomId:n.binding.roomId,agentIdentityId:n.binding.agentIdentityId,agentMembershipId:n.binding.agentMembershipId,status:n.binding.status,activationMode:n.binding.activationMode}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"delivered",sequence:n.resumeSequence}),this.deps.registry.advanceCursor({registryKey:r.registryKey,cursor:"activated",sequence:n.resumeSequence});for(const c of this.deps.registry.list())c.registryKey!==r.registryKey&&c.nianSessionId===e.previousNianSessionId&&c.binding?.bindingId===e.bindingId&&this.deps.registry.detachBinding(c.registryKey);const a=await this.resolveServerOwnedWorkspace({machineToken:i,roomId:n.binding.roomId,bindingId:n.binding.id,agentIdentityId:e.agentIdentityId});return this.deps.observations.observe({machineId:s,runtimeAdapter:t,sourceSessionKey:e.sourceSessionKey,hostContextHash:g(o),workspaceHash:g(a),workspacePath:a,lifecycleState:"active",eventName:"ManagedRestart"}),n}async resolveServerOwnedWorkspace(e){const s=await this.deps.bindingClient.status(e),i=s.managedWorkspacePath?.trim(),t=s.agent?.sessionMode;if(s.agentIdentityId!==e.agentIdentityId||t!=="standard"&&t!=="manager"||!i)throw new Error("managed_room_workspace_unavailable");return i}}function g(d){return h.createHash("sha256").update(d,"utf8").digest("hex")}function I(d){const e=y(d);if(e)return e;throw new Error(`Agent ${d} does not support managed Room binding.`)}export{u as TrustedManagedRoomBinder};
@@ -1 +1 @@
1
- import{HttpRoomActionClient as r}from"./room-action-client.js";class a extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomBindingHttpError"}}class l{fetchImpl;baseUrl;actionClient;constructor(e,n=fetch){this.fetchImpl=n,this.baseUrl=e.replace(/\/+$/,""),this.actionClient=new r(e,n)}status=e=>this.actionClient.status(e);getAttachmentCapability=e=>this.actionClient.getAttachmentCapability(e);initializeAttachment=e=>this.actionClient.initializeAttachment(e);completeAttachment=e=>this.actionClient.completeAttachment(e);createRoom=e=>this.actionClient.createRoom(e);createOperatorRoom=e=>this.actionClient.createOperatorRoom(e);resolveNativeSession=e=>this.actionClient.resolveNativeSession(e);joinRoom=e=>this.actionClient.joinRoom(e);read=e=>this.actionClient.read(e);send=e=>this.actionClient.send(e);mode=e=>this.actionClient.mode(e);pause=e=>this.actionClient.pause(e);async create(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new a("room_binding_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let i=`room_binding_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(i=s.error)}catch{}throw new a(i,"Shennian Room binding request failed.")}let t;try{t=await n.json()}catch{throw new a("invalid_room_binding_response","Invalid Room binding response.")}return d(t,e)}async restart(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/session`,{method:"PATCH",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({agentIdentityId:e.agentIdentityId,previousNianSessionId:e.previousNianSessionId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new a("room_binding_server_unavailable","Shennian Room service is unavailable.")}const t=await n.json().catch(()=>null);if(!n.ok){const s=t&&typeof t.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(t.error)?t.error:`room_binding_http_${n.status}`;throw new a(s,"Shennian Room restart request failed.")}const i=t;if(!i?.binding||i.binding.id!==e.bindingId||i.binding.roomId!==e.roomId||i.binding.agentIdentityId!==e.agentIdentityId||i.binding.nianSessionId!==e.nianSessionId||i.binding.runtimeAdapter!==e.runtimeAdapter||typeof i.binding.agentMembershipId!="string"||typeof i.resumeSequence!="string"||!/^(0|[1-9]\d{0,19})$/.test(i.resumeSequence)||typeof i.idempotent!="boolean")throw new a("invalid_room_binding_response","Invalid Room restart response.");return{binding:i.binding,resumeSequence:i.resumeSequence,idempotent:i.idempotent}}}function d(o,e){if(!o||typeof o!="object")throw new a("invalid_room_binding_response","Invalid Room binding response.");const n=o,t=n.binding;if(!t||typeof t.id!="string"||typeof t.agentMembershipId!="string"||t.roomId!==e.roomId||t.agentIdentityId!==e.agentIdentityId||t.nianSessionId!==e.nianSessionId||t.runtimeAdapter!==e.runtimeAdapter||t.activationCapability!==e.activationCapability||!c(t.activationMode,e)||!["pending_approval","active","paused","detached"].includes(t.status??"")||typeof n.idempotent!="boolean")throw new a("invalid_room_binding_response","Room binding response does not match the request.");return{binding:t,idempotent:n.idempotent}}function c(o,e){return e.activationCapability==="managed"&&e.activationMode==="auto_follow"?o==="auto_follow"||o==="mention_only":o===e.activationMode}export{l as HttpRoomBindingClient,a as RoomBindingHttpError};
1
+ import{HttpRoomActionClient as r}from"./room-action-client.js";class o extends Error{code;constructor(e,n){super(n),this.code=e,this.name="RoomBindingHttpError"}}class l{fetchImpl;baseUrl;actionClient;constructor(e,n=fetch){this.fetchImpl=n,this.baseUrl=e.replace(/\/+$/,""),this.actionClient=new r(e,n)}status=e=>this.actionClient.status(e);getAttachmentCapability=e=>this.actionClient.getAttachmentCapability(e);initializeAttachment=e=>this.actionClient.initializeAttachment(e);completeAttachment=e=>this.actionClient.completeAttachment(e);createRoom=e=>this.actionClient.createRoom(e);createOperatorRoom=e=>this.actionClient.createOperatorRoom(e);resolveNativeSession=e=>this.actionClient.resolveNativeSession(e);joinRoom=e=>this.actionClient.joinRoom(e);read=e=>this.actionClient.read(e);send=e=>this.actionClient.send(e);mode=e=>this.actionClient.mode(e);pause=e=>this.actionClient.pause(e);async create(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings`,{method:"POST",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({bindingIntentId:e.bindingIntentId,agentIdentityId:e.agentIdentityId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey,activationCapability:e.activationCapability,activationMode:e.activationMode}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}if(!n.ok){let i=`room_binding_http_${n.status}`;try{const s=await n.json();typeof s.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(s.error)&&(i=s.error)}catch{}throw new o(i,"Shennian Room binding request failed.")}let t;try{t=await n.json()}catch{throw new o("invalid_room_binding_response","Invalid Room binding response.")}return d(t,e)}async restart(e){let n;try{n=await this.fetchImpl(`${this.baseUrl}/api/rooms/${encodeURIComponent(e.roomId)}/bindings/${encodeURIComponent(e.bindingId)}/session`,{method:"PATCH",headers:{authorization:`Bearer ${e.machineToken}`,"content-type":"application/json"},body:JSON.stringify({agentIdentityId:e.agentIdentityId,previousNianSessionId:e.previousNianSessionId,nianSessionId:e.nianSessionId,runtimeAdapter:e.runtimeAdapter,sessionRegistryKey:e.sessionRegistryKey}),signal:AbortSignal.timeout(1e4)})}catch{throw new o("room_binding_server_unavailable","Shennian Room service is unavailable.")}const t=await n.json().catch(()=>null);if(!n.ok){const s=t&&typeof t.error=="string"&&/^[a-z0-9_-]{1,80}$/.test(t.error)?t.error:`room_binding_http_${n.status}`;throw new o(s,"Shennian Room restart request failed.")}const i=t;if(!i?.binding||i.binding.id!==e.bindingId||i.binding.roomId!==e.roomId||i.binding.agentIdentityId!==e.agentIdentityId||i.binding.nianSessionId!==e.nianSessionId||i.binding.runtimeAdapter!==e.runtimeAdapter||typeof i.binding.agentMembershipId!="string"||typeof i.resumeSequence!="string"||!/^(0|[1-9]\d{0,19})$/.test(i.resumeSequence)||typeof i.idempotent!="boolean")throw new o("invalid_room_binding_response","Invalid Room restart response.");return{binding:i.binding,resumeSequence:i.resumeSequence,idempotent:i.idempotent}}}function d(a,e){if(!a||typeof a!="object")throw new o("invalid_room_binding_response","Invalid Room binding response.");const n=a,t=n.binding;if(!t||typeof t.id!="string"||typeof t.agentMembershipId!="string"||t.roomId!==e.roomId||t.agentIdentityId!==e.agentIdentityId||t.nianSessionId!==e.nianSessionId||t.runtimeAdapter!==e.runtimeAdapter||t.activationCapability!==e.activationCapability||!c(t.activationMode,e)||!["pending_approval","active","paused","detached"].includes(t.status??"")||typeof n.idempotent!="boolean")throw new o("invalid_room_binding_response","Room binding response does not match the request.");return{binding:t,idempotent:n.idempotent}}function c(a,e){return a===e.activationMode}export{l as HttpRoomBindingClient,o as RoomBindingHttpError};