shennian 0.4.30 → 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.
- package/dist/publish-build-manifest.json +52 -37
- package/dist/src/agents/acp/adapter.d.ts +5 -1
- package/dist/src/agents/acp/adapter.js +2 -2
- package/dist/src/agents/acp/client-services.d.ts +2 -1
- package/dist/src/agents/acp/client-services.js +2 -2
- package/dist/src/agents/acp/discovery.js +1 -1
- package/dist/src/agents/acp/process-client.js +1 -1
- package/dist/src/agents/claude.d.ts +1 -0
- package/dist/src/agents/claude.js +5 -5
- package/dist/src/agents/codex.js +3 -3
- package/dist/src/agents/command-spec.d.ts +1 -0
- package/dist/src/agents/command-spec.js +3 -3
- package/dist/src/agents/model-registry/service.js +1 -1
- package/dist/src/commands/daemon-instance-lock.d.ts +21 -0
- package/dist/src/commands/daemon-instance-lock.js +1 -0
- package/dist/src/commands/daemon.d.ts +2 -2
- package/dist/src/commands/daemon.js +6 -6
- package/dist/src/index.js +3 -3
- package/dist/src/relay/client.d.ts +4 -0
- package/dist/src/relay/client.js +1 -1
- package/dist/src/room/activation-service.d.ts +2 -0
- package/dist/src/room/activation-service.js +4 -4
- package/dist/src/room/managed-room-binding.d.ts +2 -1
- package/dist/src/room/managed-room-binding.js +1 -1
- package/dist/src/room/room-binding-client.js +1 -1
- package/dist/src/session/handlers/chat.js +2 -2
- package/dist/src/session/handlers/project-fs.d.ts +11 -0
- package/dist/src/session/handlers/project-fs.js +1 -0
- package/dist/src/session/handlers/room-managed.js +1 -1
- package/dist/src/session/manager.d.ts +6 -1
- package/dist/src/session/manager.js +2 -2
- package/dist/src/session/project-fs-store.d.ts +43 -0
- package/dist/src/session/project-fs-store.js +1 -0
- package/dist/src/session/queue.js +4 -4
- package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
- package/node_modules/@shennian/wire/dist/index.js +1 -0
- package/node_modules/@shennian/wire/dist/methods.d.ts +3 -1
- package/node_modules/@shennian/wire/dist/methods.js +2 -0
- package/node_modules/@shennian/wire/dist/project-fs.d.ts +146 -0
- package/node_modules/@shennian/wire/dist/project-fs.js +462 -0
- package/node_modules/@shennian/wire/dist/project.d.ts +2 -0
- package/node_modules/@shennian/wire/dist/project.js +2 -0
- package/node_modules/@shennian/wire/dist/session-sync.d.ts +8 -1
- package/node_modules/@shennian/wire/dist/session-sync.js +1 -0
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ export declare function resolveCommandFileIdentity(command: string): {
|
|
|
24
24
|
pathHash: string;
|
|
25
25
|
fingerprint: string;
|
|
26
26
|
} | null;
|
|
27
|
+
export declare function resolveCommandFilePath(command: string): string | null;
|
|
27
28
|
export declare function resolveBuiltinCommand(type: BuiltinAgentType): ResolvedCommandSpec | null;
|
|
28
29
|
export declare function buildLaunchSpec(spec: ResolvedCommandSpec, runtimeArgs: string[], cwd?: string): LaunchSpec;
|
|
29
30
|
export declare function spawnResolvedCommand(spec: ResolvedCommandSpec, runtimeArgs: string[], options?: Omit<SpawnOptions, 'cwd'> & {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{spawn as k,spawnSync as
|
|
2
|
-
${t.stderr??""}`.trim().match(/\d+\.\d+[\w.-]*/)?.[0]}function
|
|
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,
|
|
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};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getAgentConfigSummary as m}from"../config-status.js";import{isCacheFresh as v,readCache as l,writeCache as y}from"./cache.js";import{discoverModelsForAgent as A}from"./discovery.js";import{getAgentRuntimeSummary as f}from"../runtime-compatibility.js";import{resolveManagedAgentRuntimeProtection as C}from"../../security/managed-agent-runtime.js";const M={claude:"claude-alias-v2"};function S(i){const s=new Set,
|
|
1
|
+
import{getAgentConfigSummary as m}from"../config-status.js";import{isCacheFresh as v,readCache as l,writeCache as y}from"./cache.js";import{discoverModelsForAgent as A}from"./discovery.js";import{getAgentRuntimeSummary as f}from"../runtime-compatibility.js";import{resolveManagedAgentRuntimeProtection as C}from"../../security/managed-agent-runtime.js";const M={claude:"claude-alias-v2"};function S(i){const s=new Set,t=[];for(const o of i)!o.id||s.has(o.id)||(s.add(o.id),t.push(o));return t}function g(i){const s=i.type!=="pi"&&i.type!=="openclaw",t=s&&!i.type.startsWith("acp:"),o=s?C({agentType:i.type,runtimeVersion:i.version,platform:i.runtimePlatform,isWsl:i.runtimeIsWsl,executableFingerprint:i.executableFingerprint,requireExecutableFingerprint:!0}):null;return{supportsManagedAgent:t&&(o?.level==="hard"||o?.level==="partial"),supportsManagedNativeFull:s,supportsWorker:i.type!=="pi"&&i.type!=="openclaw"&&!i.type.startsWith("acp:"),supportsManagerMode:i.type==="codex"||i.type==="claude"||i.type==="pi-coding-agent",...o?{managedAgentProtection:o}:{}}}function E(i){const s=l();return i.map(t=>{const o=m(t.type),n=f(t);return{type:t.type,models:s.agents[t.type]?.models??t.models??[],...t.integration?{integration:t.integration}:{},...t.display?{display:t.display}:{},...t.acpCapabilities?{acpCapabilities:t.acpCapabilities}:{},...t.authState?{authState:t.authState}:{},...t.acpAuthMethods?{acpAuthMethods:t.acpAuthMethods}:{},...t.catalogRevision?{catalogRevision:t.catalogRevision}:{},...g(t),...o?{config:o}:{},...n?{runtime:n}:{}}})}async function O(i,s){const t=l();for(const o of i)delete t.agents[o.type];return y(t),R(i,s)}async function R(i,s){const t=l();let o=!1;const n=[];for(const e of i){const a=t.agents[e.type],p=M[e.type];let r;if(v(a,e.version,p))r=a.models;else{let c=null;try{c=S(await A(e,s)),r=c.length>0?c:a?.models??e.models??[]}catch{r=a?.models??e.models??[]}if(c&&c.length>0){const h={version:e.version,...p?{sourceRevision:p}:{},refreshedAt:Date.now(),models:r};JSON.stringify(a)!==JSON.stringify(h)&&(t.agents[e.type]=h,o=!0)}}const u=m(e.type),d=f(e);n.push({type:e.type,models:r,...e.integration?{integration:e.integration}:{},...e.display?{display:e.display}:{},...e.acpCapabilities?{acpCapabilities:e.acpCapabilities}:{},...e.authState?{authState:e.authState}:{},...e.acpAuthMethods?{acpAuthMethods:e.acpAuthMethods}:{},...e.catalogRevision?{catalogRevision:e.catalogRevision}:{},...g(e),...u?{config:u}:{},...d?{runtime:d}:{}})}return o&&y(t),n}export{E as getCachedAgentInfos,O as refreshAgentInfos,R as resolveAgentInfos};
|
|
@@ -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
|
|
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
|
|
3
|
-
<string>${
|
|
4
|
-
`),s=[r.command,...r.args].map(i=>` <string>${
|
|
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&&
|
|
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,
|
|
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
|
|
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...`)),
|
|
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;
|
package/dist/src/relay/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
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
|
|
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(
|
|
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
|
-
${
|
|
5
|
+
${n.map(e=>`<${e.speaker}>
|
|
6
6
|
${e.body}`).join(`
|
|
7
7
|
|
|
8
|
-
`)}`}function j(
|
|
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};
|