shennian 0.4.19 → 0.4.21

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.
@@ -886,8 +886,8 @@
886
886
  },
887
887
  {
888
888
  "file": "src/upgrade/updater-worker.js",
889
- "beforeBytes": 14538,
890
- "afterBytes": 7608
889
+ "beforeBytes": 14372,
890
+ "afterBytes": 7495
891
891
  },
892
892
  {
893
893
  "file": "src/windows-shell.js",
@@ -6,14 +6,6 @@ type WorkerDeps = {
6
6
  kill?: (pid: number, signal: NodeJS.Signals) => void;
7
7
  sleep?: (ms: number) => Promise<void>;
8
8
  execFile?: typeof execFile;
9
- spawnDetached?: (command: string, args: string[], options: {
10
- cwd: string;
11
- detached: true;
12
- stdio: 'ignore';
13
- windowsHide: true;
14
- }) => {
15
- unref(): void;
16
- };
17
9
  };
18
10
  export declare function runUpdaterJob(jobPath: string, deps?: WorkerDeps): Promise<void>;
19
11
  export {};
@@ -1,2 +1,2 @@
1
- import a from"node:fs";import k from"node:os";import o from"node:path";import{execFile as D,spawn as P}from"node:child_process";import{promisify as V}from"node:util";import{fileURLToPath as A}from"node:url";const S=V(D),E=1,N="https://registry.npmjs.org",_=30*6e4;function p(e){return(e instanceof Error?e.message:String(e)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}function x(e,r){a.mkdirSync(o.dirname(e),{recursive:!0,mode:448});const t=`${e}.tmp-${process.pid}-${Date.now()}`;try{a.writeFileSync(t,`${JSON.stringify(r,null,2)}
2
- `,{mode:384,flag:"wx"});const n=a.openSync(t,process.platform==="win32"?"r+":"r");try{a.fsyncSync(n)}finally{a.closeSync(n)}a.renameSync(t,e)}finally{try{a.unlinkSync(t)}catch{}}}function F(e){const r=a.lstatSync(e);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const t=JSON.parse(a.readFileSync(e,"utf8")),n=u=>{if(typeof t[u]!="string"||!String(t[u]).trim())throw new Error("invalid_job");return String(t[u])},s=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),i=n("targetVersion");if(t.protocolVersion!==E||t.packageName!=="shennian")throw new Error("invalid_job");if(!s.test(l)||!s.test(i))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const d=t.oldDaemonPid==null?null:Number(t.oldDaemonPid),w=Number(t.daemonExitTimeoutMs),c=Number(t.commandTimeoutMs);if(d!=null&&(!Number.isSafeInteger(d)||d<1))throw new Error("invalid_job");if(!Number.isSafeInteger(w)||w<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(c)||c<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:i,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:t.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:d,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:w,commandTimeoutMs:c,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:t.isolatedDaemonStart===!0}}function O(e,r){return r==="win32"?o.win32.join(e,"node_modules","shennian"):o.posix.join(e,"lib","node_modules","shennian")}function h(e){try{return process.kill(e,0),!0}catch{return!1}}async function T(e,r){if(!e.oldDaemonPid||!(r.isAlive??h)(e.oldDaemonPid))return;const t=r.kill??((l,i)=>process.kill(l,i)),n=r.sleep??(l=>new Promise(i=>setTimeout(i,l)));t(e.oldDaemonPid,"SIGTERM");const s=(r.now??Date.now)()+e.daemonExitTimeoutMs;for(;(r.now??Date.now)()<s;){if(!(r.isAlive??h)(e.oldDaemonPid))return;await n(100)}if(r.kill)t(e.oldDaemonPid,"SIGKILL");else if(e.platform==="win32")try{await(r.execFile??S)("taskkill",["/PID",String(e.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-e.oldDaemonPid,"SIGKILL")}catch{try{process.kill(e.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??h)(e.oldDaemonPid))throw new Error("daemon_exit_timeout")}function f(e,r=e.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:e.platform==="win32"}}async function v(e,r,t){const n=["install","-g",`shennian@${r}`,"--prefix",e.npmPrefix];try{await t(e.npmExecutable,n,f(e))}catch{await t(e.npmExecutable,[...n,"--registry",N],f(e))}}async function y(e,r,t){const n=O(e.npmPrefix,e.platform),s=o.join(n,"dist","bin","shennian.js"),{stdout:l}=await t(e.nodeExecutable,[s,"--version"],f(e,1e4));if(l.trim()!==r)throw new Error("verification_failed");return await t(e.npmExecutable,["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0","--prefix",e.npmPrefix],f(e,3e4)),s}async function g(e,r,t,n=P){if(e.isolatedDaemonStart){n(e.nodeExecutable,[r,"run-service"],{cwd:e.npmPrefix,detached:!0,stdio:"ignore",windowsHide:!0}).unref();return}try{if(e.platform==="darwin"){const s=typeof process.getuid=="function"?process.getuid():k.userInfo().uid;await t("launchctl",["kickstart","-k",`gui/${s}/com.shennian.agent`],f(e,15e3));return}if(e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],f(e,15e3));return}if(e.platform==="win32"){await t("schtasks",["/Run","/TN","ShennianAgent"],f(e,15e3));return}}catch{}await t(e.nodeExecutable,[r,"start","--json"],f(e,3e4))}function L(e){return(r,t={})=>x(e.resultPath,{protocolVersion:E,jobId:e.jobId,state:r,fromVersion:e.fromVersion,targetVersion:e.targetVersion,operation:e.operation,...t,updatedAt:new Date().toISOString()})}function $(e,r){const t=o.dirname(o.dirname(e)),n=o.join(t,"updater.lock");a.mkdirSync(t,{recursive:!0,mode:448});try{const s=a.openSync(n,"wx",384);a.writeFileSync(s,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(s)}catch(s){if(s.code!=="EEXIST")throw s;try{const i=JSON.parse(a.readFileSync(n,"utf8"));if(i.jobId!==r.jobId&&i.pid&&h(i.pid)&&Date.now()-Number(i.createdAt)<_)throw new Error("upgrade_locked")}catch(i){if(i instanceof Error&&i.message==="upgrade_locked")throw i}a.rmSync(n,{force:!0});const l=a.openSync(n,"wx",384);a.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(l)}return()=>{try{a.unlinkSync(n)}catch{}}}async function j(e,r={}){let t;try{t=F(e)}catch{throw new Error("invalid_job")}if((r.now??Date.now)()-Date.parse(t.createdAt)>_)throw new Error("job_expired");const n=o.dirname(o.dirname(o.resolve(e)));if(o.basename(o.dirname(o.resolve(e)))!=="jobs"||o.basename(o.resolve(e))!==`${t.jobId}.json`)throw new Error("invalid_job");if(o.dirname(o.resolve(t.resultPath))!==o.join(n,"results"))throw new Error("invalid_job");if(o.basename(o.resolve(t.resultPath))!==`${t.jobId}.json`)throw new Error("invalid_job");const s=o.dirname(o.dirname(n));if(o.resolve(t.attemptPath)!==o.join(s,"upgrade-attempt.json"))throw new Error("invalid_job");const l=$(e,t),i=L(t),d=r.execFile??S;let w=!1;try{let c=null;try{c=JSON.parse(a.readFileSync(t.resultPath,"utf8"))}catch{}if(c?.jobId===t.jobId&&c.targetVersion===t.targetVersion&&["healthy","rolled_back","failed"].includes(c.state??""))return;if(c?.jobId===t.jobId&&c.state==="starting"){const m=await y(t,t.targetVersion,d);await g(t,m,d,r.spawnDetached);return}i("waiting_for_daemon"),await(r.sleep??(m=>new Promise(b=>setTimeout(b,m))))(250),await T(t,r),i("installing"),w=!0;try{await v(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"npm_install_failed"})}i("verifying");let u;try{u=await y(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"verification_failed"})}x(t.attemptPath,{from:t.fromVersion,to:t.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:t.jobId,operation:t.operation}),i("starting"),await g(t,u,d,r.spawnDetached)}catch(c){const u=c.code??(c instanceof Error?c.message:"internal_error");if(!w){i("failed",{errorCode:u,errorSummary:p(c)});return}try{await v(t,t.fromVersion,d);const m=await y(t,t.fromVersion,d);await g(t,m,d,r.spawnDetached),i("rolled_back",{effectiveVersion:t.fromVersion,errorCode:u,errorSummary:p(c)})}catch(m){throw i("failed",{errorCode:"rollback_failed",errorSummary:p(m)}),m}}finally{l()}}function I(e){try{return a.realpathSync.native(e)}catch{return o.resolve(e)}}const J=process.argv[1]?I(process.argv[1]):"",C=I(A(import.meta.url));if(J===C){const e=process.argv[2];e?j(e).catch(()=>{process.exitCode=1}):process.exitCode=2}export{j as runUpdaterJob};
1
+ import a from"node:fs";import k from"node:os";import o from"node:path";import{execFile as D}from"node:child_process";import{promisify as P}from"node:util";import{fileURLToPath as V}from"node:url";const S=P(D),_=1,A="https://registry.npmjs.org",E=30*6e4;function p(e){return(e instanceof Error?e.message:String(e)).replace(/[A-Za-z]:\\[^\s]+|\/(?:[^\s/]+\/)+[^\s]+/g,"<path>").replace(/(token|password|secret|authorization)\s*[=:]\s*[^\s]+/gi,"$1=<redacted>").replace(/\s+/g," ").slice(0,500)}function x(e,r){a.mkdirSync(o.dirname(e),{recursive:!0,mode:448});const t=`${e}.tmp-${process.pid}-${Date.now()}`;try{a.writeFileSync(t,`${JSON.stringify(r,null,2)}
2
+ `,{mode:384,flag:"wx"});const n=a.openSync(t,process.platform==="win32"?"r+":"r");try{a.fsyncSync(n)}finally{a.closeSync(n)}a.renameSync(t,e)}finally{try{a.unlinkSync(t)}catch{}}}function N(e){const r=a.lstatSync(e);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const t=JSON.parse(a.readFileSync(e,"utf8")),n=u=>{if(typeof t[u]!="string"||!String(t[u]).trim())throw new Error("invalid_job");return String(t[u])},c=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),i=n("targetVersion");if(t.protocolVersion!==_||t.packageName!=="shennian")throw new Error("invalid_job");if(!c.test(l)||!c.test(i))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const d=t.oldDaemonPid==null?null:Number(t.oldDaemonPid),w=Number(t.daemonExitTimeoutMs),s=Number(t.commandTimeoutMs);if(d!=null&&(!Number.isSafeInteger(d)||d<1))throw new Error("invalid_job");if(!Number.isSafeInteger(w)||w<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(s)||s<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:i,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:t.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:d,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:w,commandTimeoutMs:s,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:t.isolatedDaemonStart===!0}}function F(e,r){return r==="win32"?o.win32.join(e,"node_modules","shennian"):o.posix.join(e,"lib","node_modules","shennian")}function h(e){try{return process.kill(e,0),!0}catch{return!1}}async function O(e,r){if(!e.oldDaemonPid||!(r.isAlive??h)(e.oldDaemonPid))return;const t=r.kill??((l,i)=>process.kill(l,i)),n=r.sleep??(l=>new Promise(i=>setTimeout(i,l)));t(e.oldDaemonPid,"SIGTERM");const c=(r.now??Date.now)()+e.daemonExitTimeoutMs;for(;(r.now??Date.now)()<c;){if(!(r.isAlive??h)(e.oldDaemonPid))return;await n(100)}if(r.kill)t(e.oldDaemonPid,"SIGKILL");else if(e.platform==="win32")try{await(r.execFile??S)("taskkill",["/PID",String(e.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-e.oldDaemonPid,"SIGKILL")}catch{try{process.kill(e.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??h)(e.oldDaemonPid))throw new Error("daemon_exit_timeout")}function f(e,r=e.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:e.platform==="win32"}}async function v(e,r,t){const n=["install","-g",`shennian@${r}`,"--prefix",e.npmPrefix];try{await t(e.npmExecutable,n,f(e))}catch{await t(e.npmExecutable,[...n,"--registry",A],f(e))}}async function y(e,r,t){const n=F(e.npmPrefix,e.platform),c=o.join(n,"dist","bin","shennian.js"),{stdout:l}=await t(e.nodeExecutable,[c,"--version"],f(e,1e4));if(l.trim()!==r)throw new Error("verification_failed");return await t(e.npmExecutable,["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0","--prefix",e.npmPrefix],f(e,3e4)),c}async function g(e,r,t){if(e.isolatedDaemonStart){await t(e.nodeExecutable,[r,"start","--json"],f(e,3e4));return}try{if(e.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():k.userInfo().uid;await t("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],f(e,15e3));return}if(e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],f(e,15e3));return}if(e.platform==="win32"){await t("schtasks",["/Run","/TN","ShennianAgent"],f(e,15e3));return}}catch{}await t(e.nodeExecutable,[r,"start","--json"],f(e,3e4))}function T(e){return(r,t={})=>x(e.resultPath,{protocolVersion:_,jobId:e.jobId,state:r,fromVersion:e.fromVersion,targetVersion:e.targetVersion,operation:e.operation,...t,updatedAt:new Date().toISOString()})}function L(e,r){const t=o.dirname(o.dirname(e)),n=o.join(t,"updater.lock");a.mkdirSync(t,{recursive:!0,mode:448});try{const c=a.openSync(n,"wx",384);a.writeFileSync(c,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(c)}catch(c){if(c.code!=="EEXIST")throw c;try{const i=JSON.parse(a.readFileSync(n,"utf8"));if(i.jobId!==r.jobId&&i.pid&&h(i.pid)&&Date.now()-Number(i.createdAt)<E)throw new Error("upgrade_locked")}catch(i){if(i instanceof Error&&i.message==="upgrade_locked")throw i}a.rmSync(n,{force:!0});const l=a.openSync(n,"wx",384);a.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(l)}return()=>{try{a.unlinkSync(n)}catch{}}}async function $(e,r={}){let t;try{t=N(e)}catch{throw new Error("invalid_job")}if((r.now??Date.now)()-Date.parse(t.createdAt)>E)throw new Error("job_expired");const n=o.dirname(o.dirname(o.resolve(e)));if(o.basename(o.dirname(o.resolve(e)))!=="jobs"||o.basename(o.resolve(e))!==`${t.jobId}.json`)throw new Error("invalid_job");if(o.dirname(o.resolve(t.resultPath))!==o.join(n,"results"))throw new Error("invalid_job");if(o.basename(o.resolve(t.resultPath))!==`${t.jobId}.json`)throw new Error("invalid_job");const c=o.dirname(o.dirname(n));if(o.resolve(t.attemptPath)!==o.join(c,"upgrade-attempt.json"))throw new Error("invalid_job");const l=L(e,t),i=T(t),d=r.execFile??S;let w=!1;try{let s=null;try{s=JSON.parse(a.readFileSync(t.resultPath,"utf8"))}catch{}if(s?.jobId===t.jobId&&s.targetVersion===t.targetVersion&&["healthy","rolled_back","failed"].includes(s.state??""))return;if(s?.jobId===t.jobId&&s.state==="starting"){const m=await y(t,t.targetVersion,d);await g(t,m,d);return}i("waiting_for_daemon"),await(r.sleep??(m=>new Promise(b=>setTimeout(b,m))))(250),await O(t,r),i("installing"),w=!0;try{await v(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"npm_install_failed"})}i("verifying");let u;try{u=await y(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"verification_failed"})}x(t.attemptPath,{from:t.fromVersion,to:t.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:t.jobId,operation:t.operation}),i("starting"),await g(t,u,d)}catch(s){const u=s.code??(s instanceof Error?s.message:"internal_error");if(!w){i("failed",{errorCode:u,errorSummary:p(s)});return}try{await v(t,t.fromVersion,d);const m=await y(t,t.fromVersion,d);await g(t,m,d),i("rolled_back",{effectiveVersion:t.fromVersion,errorCode:u,errorSummary:p(s)})}catch(m){throw i("failed",{errorCode:"rollback_failed",errorSummary:p(m)}),m}}finally{l()}}function I(e){try{return a.realpathSync.native(e)}catch{return o.resolve(e)}}const j=process.argv[1]?I(process.argv[1]):"",J=I(V(import.meta.url));if(j===J){const e=process.argv[2];e?$(e).catch(()=>{process.exitCode=1}):process.exitCode=2}export{$ as runUpdaterJob};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.4.19",
3
+ "version": "0.4.21",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {