shennian 0.4.14 → 0.4.16

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.
@@ -436,8 +436,8 @@
436
436
  },
437
437
  {
438
438
  "file": "src/index.js",
439
- "beforeBytes": 35733,
440
- "afterBytes": 17671
439
+ "beforeBytes": 35946,
440
+ "afterBytes": 17661
441
441
  },
442
442
  {
443
443
  "file": "src/log-redaction.js",
@@ -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": 13960,
890
- "afterBytes": 7446
889
+ "beforeBytes": 13998,
890
+ "afterBytes": 7478
891
891
  },
892
892
  {
893
893
  "file": "src/windows-shell.js",
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import{Command as ve}from"commander";import k from"chalk";import Ie from"node:os";import B from"node:fs";import{loadConfig as p,saveConfig as Se,getConfigPath as ye,getShennianDir as we,resolveShennianPath as Q,normalizeAutoUpgradePolicy as Ce}from"./config/index.js";import{CliRelayClient as Re}from"./relay/client.js";import{collectKnownLogSecrets as X,installCliLogRedaction as ke,sanitizeCliLogValue as be}from"./log-redaction.js";import{registerPairCommand as Ee,runSmartStart as Ae}from"./commands/pair.js";import{registerLogoutCommand as Ue}from"./commands/logout.js";import{clearDaemonPidIfOwner as ee,createDaemonInstanceId as De,clearDaemonLauncher as oe,findRunningDaemonProcessIds as Pe,isRemoteAccessDisabled as te,prepareRemoteAccessPause as Te,stopPreparedRemoteAccessService as Me,isIsolatedDaemonE2eInstance as _e,getDaemonStatus as $e,registerDaemonCommand as Ne,writeDaemonPid as ne,writeDaemonLauncher as re}from"./commands/daemon.js";import{registerRuntimeCommand as Oe}from"./commands/runtime.js";import{registerAgentCommand as xe}from"./commands/agent.js";import{registerManagerCommand as Ve}from"./commands/manager.js";import{handleUpgradeStart as Le,registerUpgradeCommand as je}from"./commands/upgrade.js";import{registerToolsCommand as Be}from"./commands/tools.js";import{registerIntegrationCommand as He}from"./commands/integration.js";import{registerRoomCommand as Fe}from"./commands/room.js";import{SessionManager as Ge}from"./session/manager.js";import{SERVERS as H,regionToUrl as ze,urlToRegion as Ke}from"./region.js";import{clearUpgradeAttempt as Je,getCurrentVersion as ae,handleStartupCrashCheck as qe,readUpgradeAttempt as We}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ye}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as Ze}from"./upgrade/updater-bootstrap.js";import{launchUpdater as Qe,markUpdaterHealthy as Xe}from"./upgrade/updater-launcher.js";import{detectAgents as eo}from"./agents/detect.js";import{augmentProcessPath as se}from"./env-path.js";se();const F=ae();import{getCachedAgentInfos as oo,resolveAgentInfos as to}from"./agents/model-registry.js";import{initCliLogReporter as no,reportLog as b,reportUpgradeAggregateMetric as ro}from"./log-reporter.js";import{isNativeFusionEnabled as ao}from"./native-fusion/config.js";import{NativeSessionFusionService as so}from"./native-fusion/service.js";import{startDaemonLogRetention as io}from"./daemon-log.js";import{startDaemonIpcServer as ie}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as co,resolveManagedRoomIpcPaths as lo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as mo}from"./daemon-ipc/control.js";import{DAEMON_IPC_VERSION as po}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as go}from"./room/session-registry.js";import{HttpRoomBindingClient as uo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as fo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ho}from"./room/host-session-observations.js";import{RoomMirrorSyncService as vo}from"./room/mirror-sync-service.js";import{RoomActivationService as Io}from"./room/activation-service.js";import{RoomActivationAudit as So}from"./room/activation-audit.js";import{RoomAttachmentCache as yo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as wo}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Co}from"./room/managed-context.js";import{createManagedRoomExecutor as Ro}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as ce,SESSION_SYNC_ADVERTISED_CAPABILITIES as le}from"@shennian/wire";import{getDaemonInstallationId as ko}from"./session/store.js";import{synchronizeMachinePersonalAuthority as bo}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as de,ensurePersonalDataEpoch as Eo}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Ao,DeletionSyncTerminalError as Uo}from"./session/deletion-sync-state.js";const me=we(),Do=Q("daemon.pid");function Po(){try{const t=B.readFileSync(Do,"utf-8").trim();if(t.startsWith("{")){const c=JSON.parse(t),m=Number(c.pid);return Number.isInteger(m)&&m>0?m:null}const s=parseInt(t,10);return Number.isInteger(s)&&s>0?s:null}catch{return null}}function To(t){return t.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function G(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
2
- `),process.exitCode=1}function Mo(t){const s=[`trigger=${t.trigger??"unknown"}`,`phase=${t.phase}`,`code=${t.code??"unknown"}`,`attempt=${t.reconnectAttempt}`];return t.reason&&s.push(`reason=${t.reason}`),t.error&&s.push(`error=${t.error}`),s.join(" ")}async function pe(t,s=5e3){const c=Date.now();for(;Date.now()-c<s;){try{process.kill(t,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(t,0),!1}catch{return!0}}const i=new ve;i.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(F),i.enablePositionalOptions(),He(i),Fe(i),i.option("--api <url>","Server URL override").option("--name <name>","Machine name",Ie.hostname()).action(async t=>{const s=p(),c=t.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??s.serverUrl??void 0;await Ae(c,t.name)}),i.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async t=>{const s=Q("env.json");try{const e=JSON.parse(B.readFileSync(s,"utf-8"));for(const[n,o]of Object.entries(e))process.env[n]||(process.env[n]=o);se()}catch{}ke(X(process.env,p()));const c=io();te()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(c),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),z=async(e,n)=>{console.log(`[${new Date().toISOString()}] ${n} (PID ${e})`),process.kill(e,"SIGTERM"),await pe(e)||(process.kill(e,"SIGKILL"),await pe(e,2e3))};try{const e=Po();if(e&&e!==process.pid)try{process.kill(e,0),m?await z(e,"managed start taking over from existing daemon"):(console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),process.exit(0))}catch{}}catch{}const _=_e(process.env,me)?[]:Pe(process.pid);if(_.length>0)if(m)for(const e of _)try{await z(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${_[0]}), skipping duplicate start`),process.exit(0);const g=De();ne(process.pid,g,{version:F}),re(process.pid,void 0,g),process.on("exit",()=>{ee(process.pid,g),oe(g)});let $=null;try{$=Ze()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const N=await qe();if(N){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Qe({fromVersion:F,targetVersion:N.from,oldDaemonPid:process.pid,operation:"rollback"},{readiness:$}),console.error(`[upgrade] New version crashed ${N.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Eo()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),h=t.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??H.cn.url,u=ae(),E=await eo({serverUrl:h,authToken:l.machineToken??l.accessToken}),y=E.map(e=>e.type),O=oo(E),v=new go,A=new ho,w=new Co(v),I=new uo(h),ue=new yo({actionClient:I}),fe=new wo({actionClient:I});let S=null,a;const K=Ro({authority:w,actionClient:I,attachmentCache:ue,attachmentUploader:fe,observations:A,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:n})=>{const r=v.getByRegistryKey(e)?.binding;r&&v.setBinding({registryKey:e,bindingId:r.bindingId,roomId:r.roomId,agentIdentityId:r.agentIdentityId,agentMembershipId:r.agentMembershipId,status:"paused",activationMode:"paused"}),w.revokeSession(e),a?.discardPendingManagedRoomMessages(n)}});let x=null,V=null,L=null;const C=await ie({paths:co(),handlers:mo({daemonVersion:u,protocolVersion:po,getStatus:()=>$e(),getAgents:()=>E,completeAgentAuthentication:e=>a.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!x)throw new Error("upgrade_unavailable");return x(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let R;try{R=await ie({paths:lo(),handlers:{"managed-room.execute":e=>K(e)}})}catch(e){throw await C.close(),e}let J=!1;const U=()=>{J||(J=!0,Promise.allSettled([C.close(),R.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",U),process.once("SIGTERM",U),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([C.close(),R.close()]),process.exitCode=1;return}process.removeListener("SIGINT",U),process.removeListener("SIGTERM",U);const q=`${To(h)}/relay/machine`,j=l.machineToken;if(!j)return;try{await bo({serverUrl:h,machineToken:j,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([C.close(),R.close()]),process.exitCode=1;return}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(`
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
3
  Disconnecting...`)),b({level:"info",wsEvent:"daemon.stop"}),clearInterval(c);try{await a.cleanup(),M.stop(),D?.stop(),P?.stop(),d.disconnect(),await Promise.allSettled([C.close(),R.close()]),ee(process.pid,g),oe(g)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Z()}),process.on("SIGTERM",()=>{Z()})}),i.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),i.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>G()),i.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const ge=i.command("config").description("View or modify configuration");ge.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${ye()}`);const t=p(),s=be(t,X(process.env,t));if(t.serverUrl){const c=Ke(t.serverUrl);console.log(`Region: ${c} (${H[c].label})`)}console.log(JSON.stringify(s,null,2))}),ge.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((t,s)=>{const c=p();if(t==="server"){s!=="cn"&&s!=="global"&&(console.error(k.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=s;c.serverUrl=ze(m),Se(c),console.log(k.green(`\u2713 Server set to ${H[m].label}`)),console.log(k.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(k.red(`\u2717 Unknown config key: ${t}. Supported: server`)),process.exit(1)}),Ee(i),Ue(i),Ne(i),Oe(i),xe(i),Ve(i),je(i),Be(i),i.parse();
@@ -1,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
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,"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};
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.14",
3
+ "version": "0.4.16",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {