shennian 0.4.13 → 0.4.15

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.
@@ -831,8 +831,8 @@
831
831
  },
832
832
  {
833
833
  "file": "src/session/run-recovery/journal.js",
834
- "beforeBytes": 8568,
835
- "afterBytes": 4162
834
+ "beforeBytes": 8644,
835
+ "afterBytes": 4226
836
836
  },
837
837
  {
838
838
  "file": "src/session/run-recovery/schema.js",
@@ -871,13 +871,13 @@
871
871
  },
872
872
  {
873
873
  "file": "src/upgrade/updater-bootstrap.js",
874
- "beforeBytes": 3291,
875
- "afterBytes": 1732
874
+ "beforeBytes": 3367,
875
+ "afterBytes": 1796
876
876
  },
877
877
  {
878
878
  "file": "src/upgrade/updater-launcher.js",
879
- "beforeBytes": 7949,
880
- "afterBytes": 3904
879
+ "beforeBytes": 8025,
880
+ "afterBytes": 3968
881
881
  },
882
882
  {
883
883
  "file": "src/upgrade/updater-protocol.js",
@@ -886,8 +886,8 @@
886
886
  },
887
887
  {
888
888
  "file": "src/upgrade/updater-worker.js",
889
- "beforeBytes": 13778,
890
- "afterBytes": 7385
889
+ "beforeBytes": 13998,
890
+ "afterBytes": 7478
891
891
  },
892
892
  {
893
893
  "file": "src/windows-shell.js",
@@ -1,2 +1,2 @@
1
- import n from"node:fs";import s from"node:path";import{createHash as p,randomUUID as u}from"node:crypto";import{resolveShennianPath as S}from"../../config/index.js";import{RUN_JOURNAL_SCHEMA_VERSION as w,isRecoverableJournalPhase as y,parseRunJournalRecord as h}from"./schema.js";function f(a,t){return p("sha256").update(`${a}\0${t}`).digest("hex")+".json"}function c(a){try{const t=n.lstatSync(a);if(t.isSymbolicLink()||!t.isFile())throw new Error("journal target is not a regular file")}catch(t){if(t.code!=="ENOENT")throw t}}function l(a){if(process.platform==="win32")return;const t=n.openSync(a,"r");try{n.fsyncSync(t)}finally{n.closeSync(t)}}function m(a){for(let t=0;t<3;t+=1){n.mkdirSync(a,{recursive:!0,mode:448});try{const r=n.lstatSync(a);if(r.isSymbolicLink()||!r.isDirectory())throw new Error("journal directory is unsafe");process.platform!=="win32"&&n.chmodSync(a,448);return}catch(r){if(r.code!=="ENOENT"||t===2)throw r}}}class N{daemonInstallationId;directory;quarantineDirectory;constructor(t,r={}){this.daemonInstallationId=t,this.directory=r.directory??S("runtime","run-recovery","active"),this.quarantineDirectory=s.join(s.dirname(this.directory),"quarantine"),m(this.directory),m(this.quarantineDirectory)}begin(t){const r=new Date().toISOString(),e={...t,schemaVersion:w,lastObservedEventSeq:-1,phase:"dispatching",upgradeJobId:null,interruptionReason:null,continueDispatchState:"none",recoveryAttempts:0,recoveryReason:null,createdAt:r,updatedAt:r};return this.write(e),e}update(t,r,e){const o=this.get(t,r);if(!o)return null;const i=h({...o,...e,updatedAt:new Date().toISOString()});return this.write(i),i}get(t,r){const e=this.recordPath(t,r);try{c(e);const o=h(JSON.parse(n.readFileSync(e,"utf8")));if(o.sessionId!==t||o.runId!==r)throw new Error("journal identity mismatch");return o}catch(o){return o.code==="ENOENT"||this.quarantine(e),null}}loadRecoverable(){return this.load(t=>y(t.phase))}loadPendingReconciliation(){return this.load(t=>y(t.phase)||t.phase==="interrupted_by_upgrade"||t.phase==="recovery_failed"||t.phase==="aborted")}load(t){const r=[],e=[];for(const o of n.readdirSync(this.directory)){if(!o.endsWith(".json"))continue;const i=s.join(this.directory,o);try{c(i);const d=h(JSON.parse(n.readFileSync(i,"utf8")));if(d.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");if(f(d.sessionId,d.runId)!==o)throw new Error("journal filename identity mismatch");t(d)&&r.push(d)}catch{e.push(this.quarantine(i))}}return r.sort((o,i)=>o.createdAt.localeCompare(i.createdAt)),{records:r,quarantined:e}}markPlannedUpgrade(t){const{records:r}=this.loadRecoverable();for(const e of r)this.update(e.sessionId,e.runId,{upgradeJobId:t,interruptionReason:"planned_upgrade"});return l(this.directory),r.length}markAborted(t,r){this.update(t,r,{phase:"aborted"})}markSessionAborted(t){const r=this.loadRecoverable().records.filter(e=>e.sessionId===t);for(const e of r)this.markAborted(e.sessionId,e.runId);return this.sync(),r.length}remove(t,r){const e=this.recordPath(t,r);c(e);try{n.unlinkSync(e)}catch(o){if(o.code!=="ENOENT")throw o}l(this.directory)}sync(){for(const t of n.readdirSync(this.directory)){if(!t.endsWith(".json"))continue;const r=s.join(this.directory,t);c(r);const e=n.openSync(r,"r");try{n.fsyncSync(e)}finally{n.closeSync(e)}}l(this.directory)}recordPath(t,r){return s.join(this.directory,f(t,r))}write(t){if(t.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");const r=h(t),e=this.recordPath(r.sessionId,r.runId);c(e);const o=`${e}.tmp-${process.pid}-${u()}`;try{n.writeFileSync(o,`${JSON.stringify(r,null,2)}
2
- `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&n.chmodSync(o,384);const i=n.openSync(o,"r");try{n.fsyncSync(i)}finally{n.closeSync(i)}n.renameSync(o,e),l(this.directory)}finally{try{n.unlinkSync(o)}catch{}}}quarantine(t){const r=s.join(this.quarantineDirectory,`${s.basename(t)}.${Date.now()}.${u()}.invalid`);try{return c(t),n.renameSync(t,r),process.platform!=="win32"&&n.chmodSync(r,384),l(this.quarantineDirectory),r}catch{return t}}}export{N as ActiveRunJournal};
1
+ import n from"node:fs";import s from"node:path";import{createHash as p,randomUUID as u}from"node:crypto";import{resolveShennianPath as S}from"../../config/index.js";import{RUN_JOURNAL_SCHEMA_VERSION as w,isRecoverableJournalPhase as y,parseRunJournalRecord as h}from"./schema.js";function f(a,t){return p("sha256").update(`${a}\0${t}`).digest("hex")+".json"}function c(a){try{const t=n.lstatSync(a);if(t.isSymbolicLink()||!t.isFile())throw new Error("journal target is not a regular file")}catch(t){if(t.code!=="ENOENT")throw t}}function l(a){if(process.platform==="win32")return;const t=n.openSync(a,"r");try{n.fsyncSync(t)}finally{n.closeSync(t)}}function m(a){for(let t=0;t<3;t+=1){n.mkdirSync(a,{recursive:!0,mode:448});try{const r=n.lstatSync(a);if(r.isSymbolicLink()||!r.isDirectory())throw new Error("journal directory is unsafe");process.platform!=="win32"&&n.chmodSync(a,448);return}catch(r){if(r.code!=="ENOENT"||t===2)throw r}}}class N{daemonInstallationId;directory;quarantineDirectory;constructor(t,r={}){this.daemonInstallationId=t,this.directory=r.directory??S("runtime","run-recovery","active"),this.quarantineDirectory=s.join(s.dirname(this.directory),"quarantine"),m(this.directory),m(this.quarantineDirectory)}begin(t){const r=new Date().toISOString(),e={...t,schemaVersion:w,lastObservedEventSeq:-1,phase:"dispatching",upgradeJobId:null,interruptionReason:null,continueDispatchState:"none",recoveryAttempts:0,recoveryReason:null,createdAt:r,updatedAt:r};return this.write(e),e}update(t,r,e){const o=this.get(t,r);if(!o)return null;const i=h({...o,...e,updatedAt:new Date().toISOString()});return this.write(i),i}get(t,r){const e=this.recordPath(t,r);try{c(e);const o=h(JSON.parse(n.readFileSync(e,"utf8")));if(o.sessionId!==t||o.runId!==r)throw new Error("journal identity mismatch");return o}catch(o){return o.code==="ENOENT"||this.quarantine(e),null}}loadRecoverable(){return this.load(t=>y(t.phase))}loadPendingReconciliation(){return this.load(t=>y(t.phase)||t.phase==="interrupted_by_upgrade"||t.phase==="recovery_failed"||t.phase==="aborted")}load(t){const r=[],e=[];for(const o of n.readdirSync(this.directory)){if(!o.endsWith(".json"))continue;const i=s.join(this.directory,o);try{c(i);const d=h(JSON.parse(n.readFileSync(i,"utf8")));if(d.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");if(f(d.sessionId,d.runId)!==o)throw new Error("journal filename identity mismatch");t(d)&&r.push(d)}catch{e.push(this.quarantine(i))}}return r.sort((o,i)=>o.createdAt.localeCompare(i.createdAt)),{records:r,quarantined:e}}markPlannedUpgrade(t){const{records:r}=this.loadRecoverable();for(const e of r)this.update(e.sessionId,e.runId,{upgradeJobId:t,interruptionReason:"planned_upgrade"});return l(this.directory),r.length}markAborted(t,r){this.update(t,r,{phase:"aborted"})}markSessionAborted(t){const r=this.loadRecoverable().records.filter(e=>e.sessionId===t);for(const e of r)this.markAborted(e.sessionId,e.runId);return this.sync(),r.length}remove(t,r){const e=this.recordPath(t,r);c(e);try{n.unlinkSync(e)}catch(o){if(o.code!=="ENOENT")throw o}l(this.directory)}sync(){for(const t of n.readdirSync(this.directory)){if(!t.endsWith(".json"))continue;const r=s.join(this.directory,t);c(r);const e=n.openSync(r,process.platform==="win32"?"r+":"r");try{n.fsyncSync(e)}finally{n.closeSync(e)}}l(this.directory)}recordPath(t,r){return s.join(this.directory,f(t,r))}write(t){if(t.daemonInstallationId!==this.daemonInstallationId)throw new Error("daemon installation identity mismatch");const r=h(t),e=this.recordPath(r.sessionId,r.runId);c(e);const o=`${e}.tmp-${process.pid}-${u()}`;try{n.writeFileSync(o,`${JSON.stringify(r,null,2)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"}),process.platform!=="win32"&&n.chmodSync(o,384);const i=n.openSync(o,process.platform==="win32"?"r+":"r");try{n.fsyncSync(i)}finally{n.closeSync(i)}n.renameSync(o,e),l(this.directory)}finally{try{n.unlinkSync(o)}catch{}}}quarantine(t){const r=s.join(this.quarantineDirectory,`${s.basename(t)}.${Date.now()}.${u()}.invalid`);try{return c(t),n.renameSync(t,r),process.platform!=="win32"&&n.chmodSync(r,384),l(this.quarantineDirectory),r}catch{return t}}}export{N as ActiveRunJournal};
@@ -1,2 +1,2 @@
1
- import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as p}from"node:crypto";import{resolveShennianPath as d}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function f(o,t){r.mkdirSync(e.dirname(o),{recursive:!0,mode:448});const n=`${o}.tmp-${process.pid}-${p()}`;try{r.writeFileSync(n,`${JSON.stringify(t,null,2)}
2
- `,{mode:384,flag:"wx"});const i=r.openSync(n,"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,o)}finally{try{r.unlinkSync(n)}catch{}}}function U(o={}){const t=o.root??d("runtime","updater"),n=e.join(t,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448);const i=o.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),m=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(m)){const c=`${s}.tmp-${process.pid}-${p()}`;try{r.writeFileSync(c,m,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,"r");try{r.fsyncSync(y)}finally{r.closeSync(y)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(o.now??new Date).toISOString()};return f(e.join(t,"current.json"),a),f(e.join(t,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(o=d("runtime","updater")){try{const t=JSON.parse(r.readFileSync(e.join(o,"current.json"),"utf8"));if(t.protocolVersion!==l||!t.workerPath)return null;const n=r.lstatSync(t.workerPath);return n.isSymbolicLink()||!n.isFile()?null:t}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
1
+ import r from"node:fs";import e from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as y}from"node:crypto";import{resolveShennianPath as d}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function f(o,t){r.mkdirSync(e.dirname(o),{recursive:!0,mode:448});const n=`${o}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(n,`${JSON.stringify(t,null,2)}
2
+ `,{mode:384,flag:"wx"});const i=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(i)}finally{r.closeSync(i)}r.renameSync(n,o)}finally{try{r.unlinkSync(n)}catch{}}}function U(o={}){const t=o.root??d("runtime","updater"),n=e.join(t,"versions",String(l));r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448);const i=o.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=e.join(n,"updater-worker.js"),m=r.readFileSync(i),p=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!p||!p.equals(m)){const c=`${s}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(c,m,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const u=r.openSync(c,process.platform==="win32"?"r+":"r");try{r.fsyncSync(u)}finally{r.closeSync(u)}r.renameSync(c,s)}finally{try{r.unlinkSync(c)}catch{}}}const a={protocolVersion:l,workerPath:s,installedAt:(o.now??new Date).toISOString()};return f(e.join(t,"current.json"),a),f(e.join(t,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function $(o=d("runtime","updater")){try{const t=JSON.parse(r.readFileSync(e.join(o,"current.json"),"utf8"));if(t.protocolVersion!==l||!t.workerPath)return null;const n=r.lstatSync(t.workerPath);return n.isSymbolicLink()||!n.isFile()?null:t}catch{return null}}export{U as ensureUpdaterInstalled,$ as readUpdaterReadiness};
@@ -1,3 +1,3 @@
1
- import e from"node:fs";import l from"node:path";import{randomUUID as f}from"node:crypto";import{spawn as j}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as x,resolveNpmGlobalPrefixFromPackageJson as I}from"./engine.js";import{ensureUpdaterInstalled as P}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(r,n){e.mkdirSync(l.dirname(r),{recursive:!0,mode:448});const o=`${r}.tmp-${process.pid}-${f()}`;try{e.writeFileSync(o,`${JSON.stringify(n,null,2)}
2
- `,{mode:384,flag:"wx"});const c=e.openSync(o,"r");try{e.fsyncSync(c)}finally{e.closeSync(c)}if(e.renameSync(o,r),process.platform!=="win32"){const t=e.openSync(l.dirname(r),"r");try{e.fsyncSync(t)}finally{e.closeSync(t)}}}finally{try{e.unlinkSync(o)}catch{}}}function O(r){const n=r.root??p("runtime","updater"),o=x(),c=r.npmPrefix??(o?I(o):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=r.jobId??f();return{protocolVersion:S,jobId:t,fromVersion:r.fromVersion,targetVersion:r.targetVersion,npmPrefix:c,npmExecutable:r.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:r.nodeExecutable??process.execPath,packageName:"shennian",operation:r.operation??"upgrade",oldDaemonPid:r.oldDaemonPid,platform:process.platform,createdAt:(r.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:l.join(n,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...r.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function E(r,n=p("runtime","updater")){const o=l.join(n,"jobs");e.mkdirSync(o,{recursive:!0,mode:448});const c=l.join(o,`${r.jobId}.json`),t=`${c}.tmp-${process.pid}-${f()}`;try{e.writeFileSync(t,`${JSON.stringify(r,null,2)}
3
- `,{mode:384,flag:"wx"});const a=e.openSync(t,"r");try{e.fsyncSync(a)}finally{e.closeSync(a)}if(e.renameSync(t,c),process.platform!=="win32"){const s=e.openSync(o,"r");try{e.fsyncSync(s)}finally{e.closeSync(s)}}return c}finally{try{e.unlinkSync(t)}catch{}}}function U(r,n={}){const o=n.root??r.root??p("runtime","updater"),c=n.readiness??P({root:o}),t=O({...r,root:o}),a=E(t,o),s=l.join(o,"updater.lock");e.mkdirSync(o,{recursive:!0,mode:448});let i=null,u=!1;const w=()=>{if(u){try{JSON.parse(e.readFileSync(s,"utf8")).jobId===t.jobId&&e.unlinkSync(s)}catch{}u=!1}};try{try{i=e.openSync(s,"wx",384),u=!0}catch(m){if(m.code!=="EEXIST")throw m;let h=!1;try{const y=JSON.parse(e.readFileSync(s,"utf8")),k=n.isAlive??(b=>{try{return process.kill(b,0),!0}catch{return!1}});h=!!(y.pid&&k(y.pid)&&(n.now??Date.now)()-Number(y.createdAt)<30*6e4)}catch{}if(h)throw new Error("upgrade_locked");e.rmSync(s,{force:!0}),i=e.openSync(s,"wx",384),u=!0}e.writeFileSync(i,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(n.now??Date.now)()})),e.closeSync(i),i=null;const d=(n.spawn??j)(t.nodeExecutable,[c.workerPath,a],{detached:!0,stdio:"ignore",windowsHide:!0});return d.once?.("error",m=>{g(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:m instanceof Error?m.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),w()}),d.unref(),{job:t,process:d}}catch(d){i!==null&&e.closeSync(i),w();try{e.unlinkSync(a)}catch{}throw d}}function v(r,n,o=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(r))return!1;const c=l.join(o,"results",`${r}.json`);try{const t=e.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const a=JSON.parse(e.readFileSync(c,"utf8"));return a.protocolVersion!==S||a.jobId!==r||a.state!=="starting"||a.targetVersion!==n?!1:(g(c,{...a,state:a.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:n,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{O as createUpdaterJob,U as launchUpdater,v as markUpdaterHealthy,E as writeUpdaterJob};
1
+ import r from"node:fs";import l from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as j}from"node:child_process";import{resolveShennianPath as p}from"../config/index.js";import{findPackageJson as x,resolveNpmGlobalPrefixFromPackageJson as I}from"./engine.js";import{ensureUpdaterInstalled as P}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(e,n){r.mkdirSync(l.dirname(e),{recursive:!0,mode:448});const o=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(o,`${JSON.stringify(n,null,2)}
2
+ `,{mode:384,flag:"wx"});const c=r.openSync(o,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(o,e),process.platform!=="win32"){const t=r.openSync(l.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(o)}catch{}}}function O(e){const n=e.root??p("runtime","updater"),o=x(),c=e.npmPrefix??(o?I(o):null);if(!c)throw new Error("Cannot determine the active npm prefix");const t=e.jobId??y();return{protocolVersion:S,jobId:t,fromVersion:e.fromVersion,targetVersion:e.targetVersion,npmPrefix:c,npmExecutable:e.npmExecutable??(process.platform==="win32"?"npm.cmd":"npm"),nodeExecutable:e.nodeExecutable??process.execPath,packageName:"shennian",operation:e.operation??"upgrade",oldDaemonPid:e.oldDaemonPid,platform:process.platform,createdAt:(e.now??new Date).toISOString(),daemonExitTimeoutMs:15e3,commandTimeoutMs:12e4,resultPath:l.join(n,"results",`${t}.json`),attemptPath:p("upgrade-attempt.json"),...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function E(e,n=p("runtime","updater")){const o=l.join(n,"jobs");r.mkdirSync(o,{recursive:!0,mode:448});const c=l.join(o,`${e.jobId}.json`),t=`${c}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384,flag:"wx"});const a=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(a)}finally{r.closeSync(a)}if(r.renameSync(t,c),process.platform!=="win32"){const s=r.openSync(o,"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}}return c}finally{try{r.unlinkSync(t)}catch{}}}function U(e,n={}){const o=n.root??e.root??p("runtime","updater"),c=n.readiness??P({root:o}),t=O({...e,root:o}),a=E(t,o),s=l.join(o,"updater.lock");r.mkdirSync(o,{recursive:!0,mode:448});let i=null,f=!1;const w=()=>{if(f){try{JSON.parse(r.readFileSync(s,"utf8")).jobId===t.jobId&&r.unlinkSync(s)}catch{}f=!1}};try{try{i=r.openSync(s,"wx",384),f=!0}catch(m){if(m.code!=="EEXIST")throw m;let h=!1;try{const u=JSON.parse(r.readFileSync(s,"utf8")),k=n.isAlive??(b=>{try{return process.kill(b,0),!0}catch{return!1}});h=!!(u.pid&&k(u.pid)&&(n.now??Date.now)()-Number(u.createdAt)<30*6e4)}catch{}if(h)throw new Error("upgrade_locked");r.rmSync(s,{force:!0}),i=r.openSync(s,"wx",384),f=!0}r.writeFileSync(i,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(n.now??Date.now)()})),r.closeSync(i),i=null;const d=(n.spawn??j)(t.nodeExecutable,[c.workerPath,a],{detached:!0,stdio:"ignore",windowsHide:!0});return d.once?.("error",m=>{g(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:m instanceof Error?m.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),w()}),d.unref(),{job:t,process:d}}catch(d){i!==null&&r.closeSync(i),w();try{r.unlinkSync(a)}catch{}throw d}}function v(e,n,o=p("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const c=l.join(o,"results",`${e}.json`);try{const t=r.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const a=JSON.parse(r.readFileSync(c,"utf8"));return a.protocolVersion!==S||a.jobId!==e||a.state!=="starting"||a.targetVersion!==n?!1:(g(c,{...a,state:a.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:n,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{O as createUpdaterJob,U as launchUpdater,v as markUpdaterHealthy,E as writeUpdaterJob};
@@ -1,2 +1,2 @@
1
- import a from"node:fs";import b from"node:os";import o from"node:path";import{execFile as k}from"node:child_process";import{promisify as D}from"node:util";import{fileURLToPath as P}from"node:url";const S=D(k),_=1,V="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 v(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,"r");try{a.fsyncSync(n)}finally{a.closeSync(n)}a.renameSync(t,e)}finally{try{a.unlinkSync(t)}catch{}}}function A(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=f=>{if(typeof t[f]!="string"||!String(t[f]).trim())throw new Error("invalid_job");return String(t[f])},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 N(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 F(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 u(e,r=e.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:e.platform==="win32"}}async function x(e,r,t){const n=["install","-g",`shennian@${r}`,"--prefix",e.npmPrefix];try{await t(e.npmExecutable,n,u(e))}catch{await t(e.npmExecutable,[...n,"--registry",V],u(e))}}async function g(e,r,t){const n=N(e.npmPrefix,e.platform),c=o.join(n,"dist","bin","shennian.js"),{stdout:l}=await t(e.nodeExecutable,[c,"--version"],u(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],u(e,3e4)),c}async function y(e,r,t){try{if(!e.isolatedDaemonStart&&e.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():b.userInfo().uid;await t("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],u(e,15e3));return}if(!e.isolatedDaemonStart&&e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],u(e,15e3));return}if(!e.isolatedDaemonStart&&e.platform==="win32"){await t("schtasks",["/Run","/TN","ShennianAgent"],u(e,15e3));return}}catch{}await t(e.nodeExecutable,[r,"start","--json"],u(e,3e4))}function O(e){return(r,t={})=>v(e.resultPath,{protocolVersion:_,jobId:e.jobId,state:r,fromVersion:e.fromVersion,targetVersion:e.targetVersion,operation:e.operation,...t,updatedAt:new Date().toISOString()})}function T(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 L(e,r={}){let t;try{t=A(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=T(e,t),i=O(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 g(t,t.targetVersion,d);await y(t,m,d);return}i("waiting_for_daemon"),await(r.sleep??(m=>new Promise(I=>setTimeout(I,m))))(250),await F(t,r),i("installing"),w=!0;try{await x(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"npm_install_failed"})}i("verifying");let f;try{f=await g(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"verification_failed"})}v(t.attemptPath,{from:t.fromVersion,to:t.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:t.jobId,operation:t.operation}),i("starting"),await y(t,f,d)}catch(s){const f=s.code??(s instanceof Error?s.message:"internal_error");if(!w){i("failed",{errorCode:f,errorSummary:p(s)});return}try{await x(t,t.fromVersion,d);const m=await g(t,t.fromVersion,d);await y(t,m,d),i("rolled_back",{effectiveVersion:t.fromVersion,errorCode:f,errorSummary:p(s)})}catch(m){throw i("failed",{errorCode:"rollback_failed",errorSummary:p(m)}),m}}finally{l()}}const $=process.argv[1]?o.resolve(process.argv[1]):"",j=o.resolve(P(import.meta.url));if($===j){const e=process.argv[2];e?L(e).catch(()=>{process.exitCode=1}):process.exitCode=2}export{L 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 v(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 x(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){try{if(!e.isolatedDaemonStart&&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.isolatedDaemonStart&&e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],f(e,15e3));return}if(!e.isolatedDaemonStart&&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={})=>v(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 x(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"})}v(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 x(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.13",
3
+ "version": "0.4.15",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {