shennian 0.5.22 → 0.5.24

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.
@@ -571,8 +571,8 @@
571
571
  },
572
572
  {
573
573
  "file": "src/commands/upgrade.js",
574
- "beforeBytes": 14049,
575
- "afterBytes": 7260
574
+ "beforeBytes": 14344,
575
+ "afterBytes": 7396
576
576
  },
577
577
  {
578
578
  "file": "src/commands/worker.js",
@@ -906,8 +906,8 @@
906
906
  },
907
907
  {
908
908
  "file": "src/room/managed-context.js",
909
- "beforeBytes": 5441,
910
- "afterBytes": 2760
909
+ "beforeBytes": 5760,
910
+ "afterBytes": 2781
911
911
  },
912
912
  {
913
913
  "file": "src/room/managed-prompt-composer.js",
@@ -1386,8 +1386,8 @@
1386
1386
  },
1387
1387
  {
1388
1388
  "file": "src/upgrade/updater-launcher.js",
1389
- "beforeBytes": 10380,
1390
- "afterBytes": 5299
1389
+ "beforeBytes": 10435,
1390
+ "afterBytes": 5350
1391
1391
  },
1392
1392
  {
1393
1393
  "file": "src/upgrade/updater-protocol.js",
@@ -1 +1 @@
1
- import c from"chalk";import{execSync as V,spawn as P}from"node:child_process";import{randomUUID as v}from"node:crypto";import _ from"node:os";import{createInterface as $}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as k,compareVersions as w,checkForUpdate as A,acquireUpgradeLock as M,recordUpgradeFailure as F}from"../upgrade/engine.js";import{callDaemonIpc as D}from"../daemon-ipc/client.js";import{getDaemonStatus as O}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as j,waitForUpdaterReady as H,confirmUpdaterHandoff as q}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as C}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as L}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as R}from"../upgrade/updater-worker.js";import{getUpgradeChannel as T,setUpgradeChannel as G}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as b,getCliUpdateSourceOrder as x}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(n,r){return n!=="major"||r}function ie(n={}){const r=n.platform??_.platform(),i=n.kill??process.kill,e=n.pid??process.pid,p=n.env??process.env,t=n.spawn??P,s=n.execSync??V;if(r==="win32"){const o=p.ComSpec||"cmd.exe";try{t(o,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),i(e,"SIGTERM");return}catch{try{s('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{t("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:p}).unref()}catch{}i(e,"SIGTERM")}function ce(n){n.command("upgrade").description("Upgrade shennian CLI to the latest version").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version upgrade (required in non-interactive environments)").action(async r=>{if(r.channel!==void 0){if(r.to||r.yes)throw new Error("--channel cannot be combined with --to or --yes");if(G(r.channel),console.log(`Upgrade channel: ${T()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${T()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const p=setInterval(()=>{process.stdout.write(`\r ${I[e%I.length]} Checking for updates...`),e++},80);let t;try{t=r.to??await k(),clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(o){clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${o instanceof Error?o.message:String(o)}`)),process.exit(1)}const s=w(i,t);if(s==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(t)} ${s==="major"?c.red("(major)"):s==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(s==="major"&&(console.log(c.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!r.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const o=$({input:process.stdin,output:process.stdout}),d=await o.question(` Type ${t} to confirm this major upgrade: `);if(o.close(),d.trim()!==t){console.log(c.gray(" Upgrade cancelled."));return}}try{await D({method:"upgrade.start",params:{targetVersion:t,confirmedMajor:s==="major"||r.yes===!0},timeoutMs:1e4})}catch(o){const d=O({cleanupStale:!0});if(d.running){const a=o&&typeof o=="object"&&"code"in o?o.code:null;throw process.platform==="win32"&&a==="ipc_access_denied"&&d.daemonVersion&&w(d.daemonVersion,"0.5.12")!=="none"?new Error("legacy_s4u_migration_required: The older Windows daemon is running in an inaccessible S4U session. The upgrade was not scheduled; inspect shennian status --json and request assisted migration. Do not force-stop the daemon.",{cause:o}):new Error("A Shennian daemon process is running, but its local control IPC did not accept the upgrade. Run shennian status --json and inspect ipc.state, the Client version, service account and logs. The upgrade was not scheduled; do not force-stop the daemon.",{cause:o})}const m=U(),u={fromVersion:i,targetVersion:t,oldDaemonPid:null,jobId:v()},g=j(u);await R(g,{fetchOwnedArtifact:b,sourceOrder:x()}),C(g,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${t}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function de(n,r,i,e={}){const p=a=>{n.sendEvent({type:"event",event:"upgrade",payload:a})},t=e.currentVersion??y();let s;try{p({state:"checking",machineId:"self"}),s=i??await k()}catch(a){const l=`Version check failed: ${a instanceof Error?a.message:String(a)}`;return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const o=w(t,s);if(o==="none")return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:t}}),{ok:!0,alreadyLatest:!0,version:t};if(!Y(o,e.confirmedMajor===!0))return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const d=v();let m;try{m=M();try{N(L("runtime","updater"))}catch(a){throw m.release(),a}}catch(a){const l=a instanceof Error?a.message:String(a);return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const u={from:t,to:s,jobId:d},g=async()=>{let a=!1;try{const l=U(),f={fromVersion:t,targetVersion:s,oldDaemonPid:process.pid,jobId:d,requireHandoffAck:!0},h=j(f);if(p({state:"downloading",machineId:"self",version:s,jobId:d}),await R(h,{fetchOwnedArtifact:b,sourceOrder:x()}),C(h,l.workerPath),S(f,{readiness:l}),await H(h),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const E=e.prepareForUpgrade?.(d)??{activeRunCount:0};return a=!0,q(h),p({state:"restarting",machineId:"self",...u,activeRunCount:E.activeRunCount}),{ok:!0,...u,resultPath:h.resultPath}}catch(l){a&&e.cancelUpgradePreparation?.();const f=l instanceof Error?l.message:String(l);return f!=="upgrade_waiting_for_idle"&&F(s,f),p({state:"failed",machineId:"self",error:f,jobId:d}),{ok:!1,error:f}}finally{m.release()}};return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:u}),e.waitForCompletion?g():(g(),{ok:!0,...u})}async function le(n,r,i={}){const e=await A(i.currentVersion);n.sendRes({type:"res",id:r,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:i.currentVersion??y()}})}export{de as handleUpgradeStart,le as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,ce as registerUpgradeCommand,ie as restartCurrentDaemonAfterUpgrade};
1
+ import c from"chalk";import{execSync as P,spawn as _}from"node:child_process";import{randomUUID as v}from"node:crypto";import $ from"node:os";import{createInterface as A}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as k,compareVersions as w,checkForUpdate as M,acquireUpgradeLock as F,recordUpgradeFailure as D}from"../upgrade/engine.js";import{callDaemonIpc as O}from"../daemon-ipc/client.js";import{getDaemonStatus as H}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as C,waitForUpdaterReady as q,confirmUpdaterHandoff as L}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as j}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as N}from"../config/index.js";import{assertUpdaterAvailable as G,prepareUpdaterCandidate as R}from"../upgrade/updater-worker.js";import{getUpgradeChannel as T,setUpgradeChannel as Y}from"../upgrade/channel.js";import{fetchOwnedCliArtifact as b,getCliUpdateSourceOrder as x}from"../upgrade/distribution.js";const I=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function z(r,e){return r!=="major"||e}function V(r,e){const a=w(r,e);return a==="none"&&r!==e?w(e,r):a}function ce(r={}){const e=r.platform??$.platform(),a=r.kill??process.kill,n=r.pid??process.pid,p=r.env??process.env,o=r.spawn??_,i=r.execSync??P;if(e==="win32"){const t=p.ComSpec||"cmd.exe";try{o(t,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),a(n,"SIGTERM");return}catch{try{i('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{o("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:p}).unref()}catch{}a(n,"SIGTERM")}function de(r){r.command("upgrade").description("Upgrade Shennian CLI, or select an older version with --to").option("--channel <channel>","Persist stable (npm latest) or preview (npm next); does not install").option("--check","Only check for updates, do not install").option("--to <version>","Upgrade to a specific version").option("--yes","Confirm a major version change (required in non-interactive environments)").action(async e=>{if(e.channel!==void 0){if(e.to||e.yes)throw new Error("--channel cannot be combined with --to or --yes");if(Y(e.channel),console.log(`Upgrade channel: ${T()} (saved; no installation)`),!e.check)return}console.log(`Upgrade channel: ${T()}`);const a=y();console.log(c.gray(`Current version: ${a}`)),console.log();let n=0;const p=setInterval(()=>{process.stdout.write(`\r ${I[n%I.length]} Checking for updates...`),n++},80);let o;try{o=e.to??await k(),clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(t){clearInterval(p),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${t instanceof Error?t.message:String(t)}`)),process.exit(1)}const i=V(a,o);if(i==="none"&&!e.to){console.log(c.green(`\u2713 Already up to date (${a})`));return}if(console.log(` Available: ${c.green(o)} ${i==="major"?c.red("(major)"):i==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!e.check){if(i==="major"&&(console.log(c.yellow(" \u26A0 This is a major version change and may contain breaking changes.")),console.log(c.gray(" Please review the changelog before upgrading.")),console.log(),!e.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major version changes require explicit confirmation; rerun with --yes.")),process.exit(1));const t=A({input:process.stdin,output:process.stdout}),d=await t.question(` Type ${o} to confirm this major upgrade: `);if(t.close(),d.trim()!==o){console.log(c.gray(" Upgrade cancelled."));return}}try{await O({method:"upgrade.start",params:{targetVersion:o,confirmedMajor:i==="major"||e.yes===!0},timeoutMs:1e4})}catch(t){const d=H({cleanupStale:!0});if(d.running){const s=t&&typeof t=="object"&&"code"in t?t.code:null;throw process.platform==="win32"&&s==="ipc_access_denied"&&d.daemonVersion&&w(d.daemonVersion,"0.5.12")!=="none"?new Error("legacy_s4u_migration_required: The older Windows daemon is running in an inaccessible S4U session. The upgrade was not scheduled; inspect shennian status --json and request assisted migration. Do not force-stop the daemon.",{cause:t}):new Error("A Shennian daemon process is running, but its local control IPC did not accept the upgrade. Run shennian status --json and inspect ipc.state, the Client version, service account and logs. The upgrade was not scheduled; do not force-stop the daemon.",{cause:t})}const m=U(),u={fromVersion:a,targetVersion:o,oldDaemonPid:null,jobId:v()},h=C(u);await R(h,{fetchOwnedArtifact:b,sourceOrder:x()}),j(h,m.workerPath),S(u,{readiness:m})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${a} \u2192 ${o}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function le(r,e,a,n={}){const p=s=>{r.sendEvent({type:"event",event:"upgrade",payload:s})},o=n.currentVersion??y();let i;try{p({state:"checking",machineId:"self"}),i=a??await k()}catch(s){const l=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return n.suppressResponse||r.sendRes({type:"res",id:e,ok:!1,error:l}),{ok:!1,error:l}}const t=V(o,i);if(i===o)return n.suppressResponse||r.sendRes({type:"res",id:e,ok:!0,payload:{alreadyLatest:!0,version:o}}),{ok:!0,alreadyLatest:!0,version:o};if(!z(t,n.confirmedMajor===!0))return n.suppressResponse||r.sendRes({type:"res",id:e,ok:!1,error:"Major version changes require explicit second confirmation"}),{ok:!1,error:"Major version changes require explicit second confirmation"};const d=v();let m;try{m=F();try{G(N("runtime","updater"))}catch(s){throw m.release(),s}}catch(s){const l=s instanceof Error?s.message:String(s);return n.suppressResponse||r.sendRes({type:"res",id:e,ok:!1,error:l}),{ok:!1,error:l}}const u={from:o,to:i,jobId:d},h=async()=>{let s=!1;try{const l=U(),f={fromVersion:o,targetVersion:i,oldDaemonPid:process.pid,jobId:d,requireHandoffAck:!0},g=C(f);if(p({state:"downloading",machineId:"self",version:i,jobId:d}),await R(g,{fetchOwnedArtifact:b,sourceOrder:x()}),j(g,l.workerPath),S(f,{readiness:l}),await q(g),n.canHandoff&&!n.canHandoff())throw new Error("upgrade_waiting_for_idle");const E=n.prepareForUpgrade?.(d)??{activeRunCount:0};return s=!0,L(g),p({state:"restarting",machineId:"self",...u,activeRunCount:E.activeRunCount}),{ok:!0,...u,resultPath:g.resultPath}}catch(l){s&&n.cancelUpgradePreparation?.();const f=l instanceof Error?l.message:String(l);return f!=="upgrade_waiting_for_idle"&&D(i,f),p({state:"failed",machineId:"self",error:f,jobId:d}),{ok:!1,error:f}}finally{m.release()}};return n.suppressResponse||r.sendRes({type:"res",id:e,ok:!0,payload:u}),n.waitForCompletion?h():(h(),{ok:!0,...u})}async function pe(r,e,a={}){const n=await M(a.currentVersion);r.sendRes({type:"res",id:e,ok:!0,payload:n.hasUpdate?{hasUpdate:!0,current:n.current,latest:n.latest,changeType:n.changeType}:{hasUpdate:!1,current:a.currentVersion??y()}})}export{V as classifyBidirectionalVersionChange,le as handleUpgradeStart,pe as handleUpgradeStatus,z as hasMajorUpgradeConfirmation,de as registerUpgradeCommand,ce as restartCurrentDaemonAfterUpgrade};
@@ -1 +1 @@
1
- import d from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class a extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}const g=256;class l{registry;grants=new Map;revoked=new Set;constructor(e){this.registry=e}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new a("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new a("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new a("managed_room_membership_unavailable","Managed Room membership is unavailable.");const o=e.machineCredential.trim();if(!o)throw new a("machine_not_authorized","This machine is not authorized yet.");const r=`mrc_${d.randomBytes(32).toString("base64url")}`,n=s(r);return this.grants.set(n,{tokenHash:n,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,bindingId:t.binding.bindingId,roomId:t.binding.roomId,credentialHash:s(o),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get","pause"]),propagateWorkerRoomOrigin:e.propagateWorkerRoomOrigin??!0,activationCauseMessageId:e.activationCauseMessageId}),r}resolve(e){const t=s(e.token),o=this.grants.get(t);if(!o)throw this.revoked.has(t)?new a("managed_room_context_revoked","Managed Room context was revoked."):new a("managed_room_context_expired","Managed Room context expired.");if(!c(o.credentialHash,s(e.machineCredential)))throw new a("managed_room_context_invalid","Managed Room context is invalid.");if(e.action!=="manager"&&!o.allowedActions.has(e.action))throw new a("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const r=this.registry.getByRegistryKey(o.registryKey),n=r?.binding;if(!r||!n||r.registrationEpoch!==o.registrationEpoch||n.bindingId!==o.bindingId||n.roomId!==o.roomId||r.lifecycleState==="closed"||n.status!=="active"&&n.status!=="paused"||!n.agentMembershipId)throw this.grants.delete(t),this.rememberRevoked(t),new a("managed_room_context_revoked","Managed Room context was revoked.");return{grant:o,registration:r,binding:n}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,o]of this.grants)o.registryKey===e&&this.grants.delete(t)}rememberRevoked(e){if(this.revoked.add(e),this.revoked.size>g){const t=this.revoked.values().next().value;t!==void 0&&this.revoked.delete(t)}}}function s(i){return d.createHash("sha256").update(i,"utf8").digest("hex")}function c(i,e){return d.timingSafeEqual(Buffer.from(i,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,l as ManagedRoomContextAuthority,a as ManagedRoomContextError};
1
+ import d from"node:crypto";const h="SHENNIAN_MANAGED_ROOM_CONTEXT";class a extends Error{code;constructor(e,t){super(t),this.code=e,this.name="ManagedRoomContextError"}}const g=256;class l{registry;grants=new Map;revoked=new Set;constructor(e){this.registry=e}issue(e){const t=this.registry.getByRegistryKey(e.registryKey);if(!t?.binding||t.lifecycleState==="closed")throw new a("managed_room_context_unavailable","Managed Room context is unavailable.");if(t.binding.status!=="active"&&t.binding.status!=="paused")throw new a("managed_room_binding_unavailable","Managed Room binding is unavailable.");if(!t.binding.agentMembershipId)throw new a("managed_room_membership_unavailable","Managed Room membership is unavailable.");const o=e.machineCredential.trim();if(!o)throw new a("machine_not_authorized","This machine is not authorized yet.");const r=`mrc_${d.randomBytes(32).toString("base64url")}`,n=s(r);return this.grants.set(n,{tokenHash:n,registryKey:t.registryKey,registrationEpoch:t.registrationEpoch,bindingId:t.binding.bindingId,roomId:t.binding.roomId,credentialHash:s(o),allowedActions:new Set(e.allowedActions??["status","read","send","attachment.get","pause"]),propagateWorkerRoomOrigin:e.propagateWorkerRoomOrigin??!0,activationCauseMessageId:e.activationCauseMessageId}),r}resolve(e){const t=s(e.token),o=this.grants.get(t);if(!o)throw this.revoked.has(t)?new a("managed_room_context_revoked","Managed Room context was revoked."):new a("managed_room_context_expired","Managed Room context expired.");if(!c(o.credentialHash,s(e.machineCredential)))throw new a("managed_room_context_invalid","Managed Room context is invalid.");if(e.action!=="manager"&&e.action!=="status"&&!o.allowedActions.has(e.action))throw new a("managed_room_action_forbidden","This action is not allowed in the managed Room context.");const r=this.registry.getByRegistryKey(o.registryKey),n=r?.binding;if(!r||!n||r.registrationEpoch!==o.registrationEpoch||n.bindingId!==o.bindingId||n.roomId!==o.roomId||r.lifecycleState==="closed"||n.status!=="active"&&n.status!=="paused"||!n.agentMembershipId)throw this.grants.delete(t),this.rememberRevoked(t),new a("managed_room_context_revoked","Managed Room context was revoked.");return{grant:o,registration:r,binding:n}}revokeToken(e){this.grants.delete(s(e))}revokeSession(e){for(const[t,o]of this.grants)o.registryKey===e&&this.grants.delete(t)}rememberRevoked(e){if(this.revoked.add(e),this.revoked.size>g){const t=this.revoked.values().next().value;t!==void 0&&this.revoked.delete(t)}}}function s(i){return d.createHash("sha256").update(i,"utf8").digest("hex")}function c(i,e){return d.timingSafeEqual(Buffer.from(i,"hex"),Buffer.from(e,"hex"))}export{h as MANAGED_ROOM_CONTEXT_ENV,l as ManagedRoomContextAuthority,a as ManagedRoomContextError};
@@ -1,3 +1,3 @@
1
- import r from"node:fs";import d from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as I,spawnSync as j}from"node:child_process";import{resolveShennianPath as f}from"../config/index.js";import{findPackageJson as P,resolveNpmGlobalPrefixFromPackageJson as _}from"./engine.js";import{ensureUpdaterInstalled as E}from"./updater-bootstrap.js";import{resolveNpmCommand as J}from"./npm-command.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function w(e,o){r.mkdirSync(d.dirname(e),{recursive:!0,mode:448});const a=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(a,`${JSON.stringify(o,null,2)}
2
- `,{mode:384,flag:"wx"});const n=r.openSync(a,process.platform==="win32"?"r+":"r");try{r.fsyncSync(n)}finally{r.closeSync(n)}if(r.renameSync(a,e),process.platform!=="win32"){const t=r.openSync(d.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(a)}catch{}}}function $(e){const o=e.root??f("runtime","updater"),a=P(),n=e.npmPrefix??(a?_(a):null);if(!n)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:n,npmExecutable:e.npmExecutable??J(e.nodeExecutable??process.execPath),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:d.join(o,"results",`${t}.json`),attemptPath:f("upgrade-attempt.json"),...e.requireHandoffAck?{requireHandoffAck:!0}:{},...e.rollbackJobId?{rollbackJobId:e.rollbackJobId}:{},...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function O(e,o=f("runtime","updater")){const a=d.join(o,"jobs");r.mkdirSync(a,{recursive:!0,mode:448});const n=d.join(a,`${e.jobId}.json`),t=`${n}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
- `,{mode:384,flag:"wx"});const c=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(t,n),process.platform!=="win32"){const s=r.openSync(a,"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}}return n}finally{try{r.unlinkSync(t)}catch{}}}function v(e,o){return e==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function R(e,o={}){const a=o.root??e.root??f("runtime","updater"),n=o.readiness??E({root:a}),t=$({...e,root:a}),c=O(t,a),s=d.join(a,"updater.lock");r.mkdirSync(a,{recursive:!0,mode:448});let l=null,p=!1;const h=()=>{if(p){try{JSON.parse(r.readFileSync(s,"utf8")).jobId===t.jobId&&r.unlinkSync(s)}catch{}p=!1}};try{try{l=r.openSync(s,"wx",384),p=!0}catch(i){if(i.code!=="EEXIST")throw i;let b=!1;try{const g=JSON.parse(r.readFileSync(s,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});b=!!(g.pid&&k(g.pid))}catch{}if(b)throw new Error("upgrade_locked");r.rmSync(s,{force:!0}),l=r.openSync(s,"wx",384),p=!0}r.writeFileSync(l,JSON.stringify({jobId:t.jobId,phase:"handoff",pid:process.pid,createdAt:(o.now??Date.now)()})),r.closeSync(l),l=null;let u=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{u=r.readFileSync("/proc/self/cgroup","utf8")}catch{}if(v(o.platform??process.platform,u)){const i=(o.systemdRun??j)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,n.workerPath,c],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const m=(o.spawn??I)(t.nodeExecutable,[n.workerPath,c],{detached:!0,stdio:"ignore",windowsHide:!0});return m.once?.("error",i=>{w(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),h()}),m.unref(),{job:t,process:m}}catch(u){l!==null&&r.closeSync(l),h();try{r.unlinkSync(c)}catch{}throw u}}async function L(e,o=8e3){const a=Date.now()+o;for(;Date.now()<a;){let n=null;try{n=JSON.parse(r.readFileSync(e.resultPath,"utf8"))}catch{}if(n?.jobId===e.jobId&&n.state==="waiting_for_daemon")return;if(n?.jobId===e.jobId&&n.state==="failed")throw new Error(n.errorCode??"updater_not_ready");await new Promise(t=>setTimeout(t,50))}throw new Error("updater_readiness_timeout")}function q(e){w(`${e.resultPath}.handoff`,{jobId:e.jobId})}function M(e,o,a=f("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const n=d.join(a,"results",`${e}.json`);try{const t=r.lstatSync(n);if(t.isSymbolicLink()||!t.isFile())return!1;const c=JSON.parse(r.readFileSync(n,"utf8"));return c.protocolVersion!==S||c.jobId!==e||c.state!=="starting"||c.targetVersion!==o?!1:(w(n,{...c,state:c.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{q as confirmUpdaterHandoff,$ as createUpdaterJob,R as launchUpdater,M as markUpdaterHealthy,v as needsSystemdUpdater,L as waitForUpdaterReady,O as writeUpdaterJob};
1
+ import r from"node:fs";import d from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as I,spawnSync as j}from"node:child_process";import{resolveShennianPath as f}from"../config/index.js";import{findPackageJson as P,resolveNpmGlobalPrefixFromPackageJson as _}from"./engine.js";import{ensureUpdaterInstalled as E}from"./updater-bootstrap.js";import{resolveNpmCommand as O}from"./npm-command.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function w(e,o){r.mkdirSync(d.dirname(e),{recursive:!0,mode:448});const a=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(a,`${JSON.stringify(o,null,2)}
2
+ `,{mode:384,flag:"wx"});const n=r.openSync(a,process.platform==="win32"?"r+":"r");try{r.fsyncSync(n)}finally{r.closeSync(n)}if(r.renameSync(a,e),process.platform!=="win32"){const t=r.openSync(d.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(a)}catch{}}}function J(e){const o=e.root??f("runtime","updater"),a=P(),n=e.npmPrefix??(a?_(a):null);if(!n)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:n,npmExecutable:e.npmExecutable??O(e.nodeExecutable??process.execPath),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:d.join(o,"results",`${t}.json`),attemptPath:f("upgrade-attempt.json"),...e.requireHandoffAck?{requireHandoffAck:!0}:{},...e.rollbackJobId?{rollbackJobId:e.rollbackJobId}:{},...e.isolatedDaemonStart||process.env.SHENNIAN_DAEMON_SUPERVISOR==="docker"?{isolatedDaemonStart:!0}:{}}}function $(e,o=f("runtime","updater")){const a=d.join(o,"jobs");r.mkdirSync(a,{recursive:!0,mode:448});const n=d.join(a,`${e.jobId}.json`),t=`${n}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384,flag:"wx"});const c=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(t,n),process.platform!=="win32"){const s=r.openSync(a,"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}}return n}finally{try{r.unlinkSync(t)}catch{}}}function v(e,o){return e==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function R(e,o={}){const a=o.root??e.root??f("runtime","updater"),n=o.readiness??E({root:a}),t=J({...e,root:a}),c=$(t,a),s=d.join(a,"updater.lock");r.mkdirSync(a,{recursive:!0,mode:448});let l=null,p=!1;const h=()=>{if(p){try{JSON.parse(r.readFileSync(s,"utf8")).jobId===t.jobId&&r.unlinkSync(s)}catch{}p=!1}};try{try{l=r.openSync(s,"wx",384),p=!0}catch(i){if(i.code!=="EEXIST")throw i;let b=!1;try{const k=JSON.parse(r.readFileSync(s,"utf8")),g=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});b=!!(k.pid&&g(k.pid))}catch{}if(b)throw new Error("upgrade_locked");r.rmSync(s,{force:!0}),l=r.openSync(s,"wx",384),p=!0}r.writeFileSync(l,JSON.stringify({jobId:t.jobId,phase:"handoff",pid:process.pid,createdAt:(o.now??Date.now)()})),r.closeSync(l),l=null;let u=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{u=r.readFileSync("/proc/self/cgroup","utf8")}catch{}if(v(o.platform??process.platform,u)){const i=(o.systemdRun??j)("systemd-run",["--user","--quiet","--collect",`--unit=shennian-updater-${t.jobId}`,"--property=Type=exec","--property=KillMode=process",`--setenv=PATH=${process.env.PATH??"/usr/local/bin:/usr/bin:/bin"}`,"--",t.nodeExecutable,n.workerPath,c],{encoding:"utf8",stdio:"pipe",timeout:1e4});if(i.error||i.status!==0)throw new Error("updater_service_launch_failed");return{job:t,process:null}}const m=(o.spawn??I)(t.nodeExecutable,[n.workerPath,c],{detached:!0,stdio:"ignore",windowsHide:!0});return m.once?.("error",i=>{w(t.resultPath,{protocolVersion:S,jobId:t.jobId,state:"failed",fromVersion:t.fromVersion,targetVersion:t.targetVersion,errorCode:"internal_error",errorSummary:i instanceof Error?i.message.slice(0,500):"updater_spawn_failed",updatedAt:new Date().toISOString()}),h()}),m.unref(),{job:t,process:m}}catch(u){l!==null&&r.closeSync(l),h();try{r.unlinkSync(c)}catch{}throw u}}async function L(e,o=8e3){const a=Date.now()+o;for(;Date.now()<a;){let n=null;try{n=JSON.parse(r.readFileSync(e.resultPath,"utf8"))}catch{}if(n?.jobId===e.jobId&&n.state==="waiting_for_daemon")return;if(n?.jobId===e.jobId&&n.state==="failed")throw new Error(n.errorCode??"updater_not_ready");await new Promise(t=>setTimeout(t,50))}throw new Error("updater_readiness_timeout")}function M(e){w(`${e.resultPath}.handoff`,{jobId:e.jobId})}function q(e,o,a=f("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const n=d.join(a,"results",`${e}.json`);try{const t=r.lstatSync(n);if(t.isSymbolicLink()||!t.isFile())return!1;const c=JSON.parse(r.readFileSync(n,"utf8"));return c.protocolVersion!==S||c.jobId!==e||c.state!=="starting"||c.targetVersion!==o?!1:(w(n,{...c,state:c.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{M as confirmUpdaterHandoff,J as createUpdaterJob,R as launchUpdater,q as markUpdaterHealthy,v as needsSystemdUpdater,L as waitForUpdaterReady,$ as writeUpdaterJob};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.5.22",
3
+ "version": "0.5.24",
4
4
  "description": "Shennian \u2014 AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {