shennian 0.4.38 → 0.4.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/publish-build-manifest.json +22 -17
- package/dist/src/commands/daemon-process.js +1 -1
- package/dist/src/commands/upgrade.d.ts +2 -0
- package/dist/src/commands/upgrade.js +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/upgrade/scheduler.d.ts +4 -3
- package/dist/src/upgrade/scheduler.js +1 -1
- package/dist/src/upgrade/updater-bootstrap.js +2 -2
- package/dist/src/upgrade/updater-launcher.d.ts +1 -0
- package/dist/src/upgrade/updater-launcher.js +3 -3
- package/dist/src/upgrade/updater-protocol.d.ts +2 -0
- package/dist/src/upgrade/updater-protocol.js +1 -1
- package/dist/src/upgrade/updater-service-entry.d.ts +7 -0
- package/dist/src/upgrade/updater-service-entry.js +1 -0
- package/dist/src/upgrade/updater-worker.d.ts +37 -0
- package/dist/src/upgrade/updater-worker.js +2 -2
- package/node_modules/@shennian/wire/dist/session.d.ts +2 -0
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"jsMinified": true,
|
|
4
4
|
"minifier": "esbuild",
|
|
5
|
-
"fileCount":
|
|
5
|
+
"fileCount": 184,
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"file": "bin/shennian.js",
|
|
@@ -296,8 +296,8 @@
|
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"file": "src/commands/daemon-process.js",
|
|
299
|
-
"beforeBytes":
|
|
300
|
-
"afterBytes":
|
|
299
|
+
"beforeBytes": 3400,
|
|
300
|
+
"afterBytes": 1859
|
|
301
301
|
},
|
|
302
302
|
{
|
|
303
303
|
"file": "src/commands/daemon-start-wait.js",
|
|
@@ -366,8 +366,8 @@
|
|
|
366
366
|
},
|
|
367
367
|
{
|
|
368
368
|
"file": "src/commands/upgrade.js",
|
|
369
|
-
"beforeBytes":
|
|
370
|
-
"afterBytes":
|
|
369
|
+
"beforeBytes": 12365,
|
|
370
|
+
"afterBytes": 6391
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"file": "src/config/index.js",
|
|
@@ -441,8 +441,8 @@
|
|
|
441
441
|
},
|
|
442
442
|
{
|
|
443
443
|
"file": "src/index.js",
|
|
444
|
-
"beforeBytes":
|
|
445
|
-
"afterBytes":
|
|
444
|
+
"beforeBytes": 35948,
|
|
445
|
+
"afterBytes": 17839
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"file": "src/log-redaction.js",
|
|
@@ -891,28 +891,33 @@
|
|
|
891
891
|
},
|
|
892
892
|
{
|
|
893
893
|
"file": "src/upgrade/scheduler.js",
|
|
894
|
-
"beforeBytes":
|
|
895
|
-
"afterBytes":
|
|
894
|
+
"beforeBytes": 7326,
|
|
895
|
+
"afterBytes": 4169
|
|
896
896
|
},
|
|
897
897
|
{
|
|
898
898
|
"file": "src/upgrade/updater-bootstrap.js",
|
|
899
|
-
"beforeBytes":
|
|
900
|
-
"afterBytes":
|
|
899
|
+
"beforeBytes": 3600,
|
|
900
|
+
"afterBytes": 1845
|
|
901
901
|
},
|
|
902
902
|
{
|
|
903
903
|
"file": "src/upgrade/updater-launcher.js",
|
|
904
|
-
"beforeBytes":
|
|
905
|
-
"afterBytes":
|
|
904
|
+
"beforeBytes": 9346,
|
|
905
|
+
"afterBytes": 4701
|
|
906
906
|
},
|
|
907
907
|
{
|
|
908
908
|
"file": "src/upgrade/updater-protocol.js",
|
|
909
|
-
"beforeBytes":
|
|
910
|
-
"afterBytes":
|
|
909
|
+
"beforeBytes": 2968,
|
|
910
|
+
"afterBytes": 1796
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
"file": "src/upgrade/updater-service-entry.js",
|
|
914
|
+
"beforeBytes": 3179,
|
|
915
|
+
"afterBytes": 1866
|
|
911
916
|
},
|
|
912
917
|
{
|
|
913
918
|
"file": "src/upgrade/updater-worker.js",
|
|
914
|
-
"beforeBytes":
|
|
915
|
-
"afterBytes":
|
|
919
|
+
"beforeBytes": 30329,
|
|
920
|
+
"afterBytes": 14939
|
|
916
921
|
},
|
|
917
922
|
{
|
|
918
923
|
"file": "src/windows-shell.js",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as d}from"node:child_process";function
|
|
1
|
+
import{execFileSync as d}from"node:child_process";function a(s){const e=s.replace(/\\/g,"/").trim();return/updater-worker\.js["\s]+--service\s/.test(e)&&/\brun-service(?:\s|$)/.test(e)||l(e)||u(e,"shennian")||u(e,"npx")}function m(s,e=process.pid){let n;try{n=JSON.parse(p(s))}catch{return[]}return(Array.isArray(n)?n:n?[n]:[]).map(r=>{if(!r||typeof r!="object")return null;const i=r,t=Number(i.ProcessId),c=typeof i.CommandLine=="string"?i.CommandLine:"";return!Number.isInteger(t)||t<=0||t===e?null:a(c)?t:null}).filter(r=>typeof r=="number")}function p(s){const e=s.trim(),n=[...e].map((t,c)=>t==="["||t==="{"?c:-1).find(t=>t>=0)??-1;if(n<0)return e;const r=e[n]==="["?"]":"}",i=e.lastIndexOf(r);return i>=n?e.slice(n,i+1):e.slice(n)}function x(s=process.pid){try{const e=["$ProgressPreference = 'SilentlyContinue'","Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'run-service' } | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress"].join("; "),n=d("powershell.exe",["-NoProfile","-NonInteractive","-Command",e],{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0});return m(n,s)}catch{return[]}}function l(s){return/^"[^"]*\/node(?:\.exe)?"\s+"?[^"]*(?:\/node_modules\/shennian\/|\/dist\/bin\/shennian\.js|\/bin\/shennian)"?\s+run-service(?:\s|$)/i.test(s)||/^(?:\S*\/)?node(?:\.exe)?\s+\S*(?:\/node_modules\/shennian\/|\/dist\/bin\/shennian\.js|\/bin\/shennian)\s+run-service(?:\s|$)/i.test(s)}function u(s,e){const n=e==="shennian"?`${e}(?:\\.cmd|\\.exe)?`:`${e}(?:\\.cmd|\\.exe)?`,o=e==="npx"?"\\s+(?:--yes\\s+)?shennian\\s+run-service(?:\\s|$)":"\\s+run-service(?:\\s|$)";return new RegExp(`^"[^"]*\\/${n}"${o}`,"i").test(s)||new RegExp(`^(?:\\S*\\/)?${n}${o}`,"i").test(s)}export{x as findRunningWindowsDaemonProcessIds,a as isShennianRunServiceCommand,m as parseWindowsDaemonProcessJson};
|
|
@@ -22,6 +22,8 @@ export declare function handleUpgradeStart(client: CliRelayClient, reqId: string
|
|
|
22
22
|
cancelUpgradePreparation?: () => void;
|
|
23
23
|
shutdownAfterHandoff?: () => void;
|
|
24
24
|
suppressResponse?: boolean;
|
|
25
|
+
canHandoff?: () => boolean;
|
|
26
|
+
waitForCompletion?: boolean;
|
|
25
27
|
}): Promise<Record<string, unknown>>;
|
|
26
28
|
export declare function handleUpgradeStatus(client: CliRelayClient, reqId: string, opts?: {
|
|
27
29
|
currentVersion?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import c from"chalk";import{execSync as
|
|
1
|
+
import c from"chalk";import{execSync as I,spawn as b}from"node:child_process";import{randomUUID as E}from"node:crypto";import P from"node:os";import{createInterface as V}from"node:readline/promises";import{getCurrentVersion as y,fetchLatestVersion as v,compareVersions as k,checkForUpdate as $,acquireUpgradeLock as M,recordUpgradeFailure as A}from"../upgrade/engine.js";import{callDaemonIpc as F}from"../daemon-ipc/client.js";import{getDaemonStatus as _}from"./daemon.js";import{ensureUpdaterInstalled as U}from"../upgrade/updater-bootstrap.js";import{launchUpdater as S,createUpdaterJob as D}from"../upgrade/updater-launcher.js";import{prepareRecoveryServiceEntry as H}from"../upgrade/updater-service-entry.js";import{resolveShennianPath as L}from"../config/index.js";import{assertUpdaterAvailable as N,prepareUpdaterCandidate as q}from"../upgrade/updater-worker.js";import{getUpgradeChannel as R,setUpgradeChannel as G}from"../upgrade/channel.js";const j=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function Y(n,r){return n!=="major"||r}function ae(n={}){const r=n.platform??P.platform(),i=n.kill??process.kill,e=n.pid??process.pid,d=n.env??process.env,o=n.spawn??b,t=n.execSync??I;if(r==="win32"){const a=d.ComSpec||"cmd.exe";try{o(a,["/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{t('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:d}).unref()}catch{}i(e,"SIGTERM")}function se(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: ${R()} (saved; no installation)`),!r.check)return}console.log(`Upgrade channel: ${R()}`);const i=y();console.log(c.gray(`Current version: ${i}`)),console.log();let e=0;const d=setInterval(()=>{process.stdout.write(`\r ${j[e%j.length]} Checking for updates...`),e++},80);let o;try{o=r.to??await v(),clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(a){clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${a instanceof Error?a.message:String(a)}`)),process.exit(1)}const t=k(i,o);if(t==="none"&&!r.to){console.log(c.green(`\u2713 Already up to date (${i})`));return}if(console.log(` Available: ${c.green(o)} ${t==="major"?c.red("(major)"):t==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!r.check){if(t==="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 a=V({input:process.stdin,output:process.stdout}),p=await a.question(` Type ${o} to confirm this major upgrade: `);if(a.close(),p.trim()!==o){console.log(c.gray(" Upgrade cancelled."));return}}try{await F({method:"upgrade.start",params:{targetVersion:o,confirmedMajor:t==="major"||r.yes===!0},timeoutMs:1e4})}catch(a){if(_({cleanupStale:!0}).running)throw a;const f=U();S({fromVersion:i,targetVersion:o,oldDaemonPid:null},{readiness:f})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${i} \u2192 ${o}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function ie(n,r,i,e={}){const d=s=>{n.sendEvent({type:"event",event:"upgrade",payload:s})},o=e.currentVersion??y();let t;try{d({state:"checking",machineId:"self"}),t=i??await v()}catch(s){const l=`Version check failed: ${s instanceof Error?s.message:String(s)}`;return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const a=k(o,t);if(a==="none")return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:{alreadyLatest:!0,version:o}}),{ok:!0,alreadyLatest:!0,version:o};if(!Y(a,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 p=E();let f;try{f=M();try{N(L("runtime","updater"))}catch(s){throw f.release(),s}}catch(s){const l=s instanceof Error?s.message:String(s);return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!1,error:l}),{ok:!1,error:l}}const m={from:o,to:t,jobId:p},w=async()=>{let s=!1;try{const l=U(),u={fromVersion:o,targetVersion:t,oldDaemonPid:process.pid,jobId:p},g=D(u);if(d({state:"downloading",machineId:"self",version:t,jobId:p}),await q(g),H(g,l.workerPath),e.canHandoff&&!e.canHandoff())throw new Error("upgrade_waiting_for_idle");const x=e.prepareForUpgrade?.(p)??{activeRunCount:0};s=!0;const h=S(u,{readiness:l});return h.process?.once&&await new Promise((C,T)=>{h.process.once("spawn",C),h.process.once("error",T)}),d({state:"restarting",machineId:"self",...m,activeRunCount:x.activeRunCount}),e.shutdownAfterHandoff?.(),{ok:!0,...m,resultPath:g.resultPath}}catch(l){s&&e.cancelUpgradePreparation?.();const u=l instanceof Error?l.message:String(l);return u!=="upgrade_waiting_for_idle"&&A(t,u),d({state:"failed",machineId:"self",error:u,jobId:p}),{ok:!1,error:u}}finally{f.release()}};return e.suppressResponse||n.sendRes({type:"res",id:r,ok:!0,payload:m}),e.waitForCompletion?w():(w(),{ok:!0,...m})}async function ce(n,r,i={}){const e=await $(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{ie as handleUpgradeStart,ce as handleUpgradeStatus,Y as hasMajorUpgradeConfirmation,se as registerUpgradeCommand,ae as restartCurrentDaemonAfterUpgrade};
|
package/dist/src/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{Command as fe}from"commander";import
|
|
2
|
-
`),process.exitCode=1}function $o(n){const i=[`trigger=${n.trigger??"unknown"}`,`phase=${n.phase}`,`code=${n.code??"unknown"}`,`attempt=${n.reconnectAttempt}`];return n.reason&&i.push(`reason=${n.reason}`),n.error&&i.push(`error=${n.error}`),i.join(" ")}const s=new fe;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(B),s.enablePositionalOptions(),je(s),He(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",he.hostname()).action(async n=>{const i=p(),d=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??i.serverUrl??void 0;await be(d,n.name)}),s.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async n=>{const i=H("env.json");try{const e=JSON.parse(j.readFileSync(i,"utf-8"));for(const[r,o]of Object.entries(e))process.env[r]||(process.env[r]=o);ae()}catch{}Ce(Q(process.env,p()));const d=io();oe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(d),process.exit(0));const c=Ae(),h=eo({lockPath:To,instanceId:c});h.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(h.ownerPid?` (PID ${h.ownerPid})`:"")+", skipping duplicate start"),clearInterval(d),process.exit(0));try{const e=Mo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),h.release(),process.exit(0)}catch{}}catch{}const q=Te(process.env,de)?[]:Ee(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),h.release(),process.exit(0)),ne(process.pid,c,{version:B}),re(process.pid,void 0,c),process.on("exit",()=>{X(process.pid,c),ee(c)}),process.on("exit",h.release);let $=null;try{$=Ye()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const N=await Ke();if(N){$||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Ze({fromVersion:B,targetVersion:N.from,oldDaemonPid:process.pid,operation:"rollback"},{readiness:$}),console.error(`[upgrade] New version crashed ${N.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Ao()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),f=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??F.cn.url,g=te(),y=await Xe({serverUrl:f,authToken:l.machineToken??l.accessToken}),w=y.map(e=>e.type),O=oo(y),v=new go,A=new ho,C=new Ro(v),I=new uo(f),pe=new wo({actionClient:I}),ge=new Co({actionClient:I});let S=null,a;const z=ko({authority:C,actionClient:I,attachmentCache:pe,attachmentUploader:ge,observations:A,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:r})=>{const t=v.getByRegistryKey(e)?.binding;t&&v.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),C.revokeSession(e),a?.discardPendingManagedRoomMessages(r)}});let x=null,V=null,L=null;const R=await se({paths:co(),handlers:mo({daemonVersion:g,protocolVersion:po,getStatus:()=>Me(),getAgents:()=>y,completeAgentAuthentication:e=>a.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!x)throw new Error("upgrade_unavailable");return x(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let k;try{k=await se({paths:lo(),handlers:{"managed-room.execute":e=>z(e)}})}catch(e){throw await R.close(),e}let K=!1;const E=()=>{K||(K=!0,Promise.allSettled([R.close(),k.close()]).finally(()=>process.exit(0)))};if(process.once("SIGINT",E),process.once("SIGTERM",E),!l.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await Promise.allSettled([R.close(),k.close()]),process.exitCode=1;return}process.removeListener("SIGINT",E),process.removeListener("SIGTERM",E);const J=`${_o(f)}/relay/machine`,U=l.machineToken;if(!U)return;try{await Po({serverUrl:f,machineToken:U,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([R.close(),k.close()]),process.exit(1)}l.machineId&&ro(f,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${J}... (v${g}) agents: ${w.join(",")}`),P({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:w}});let D=null,T=null,M=0,u=Je();const W=ye(l.autoUpgrade),m=new we({serverUrl:J,machineToken:U,cliVersion:g,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:ie,sessionSyncCapabilities:ce,daemonInstallationId:bo(),dataEpoch:le,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),P({level:"info",wsEvent:"daemon.connected"}),O.some(o=>o.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:O}}),no(y,{serverUrl:l.serverUrl??f,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(O)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),u&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:u.from,to:u.to,...u.jobId?{jobId:u.jobId}:{},activeRunCount:a.getPendingRecoveryCount()}}),_.start(),T?.start();const e=++M,r=async()=>{try{if(await a.synchronizeSessionDeletions(),await a.recoverInterruptedRuns(),u){const o=u;u=null,o.jobId&&Qe(o.jobId,o.to),ze(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),to({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),m.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===M&&m.getState()==="connected"&&D?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===M&&m.getState()==="connected"){if(o instanceof Uo)return;const t=o instanceof Eo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{r()},t).unref?.()}}};r()},onDisconnected:e=>{M+=1,a?.revokeProjectFsHandles(),T?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${$o(e)}`),P({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||a?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),P({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),a.handleReq(e)}});D=ao()?new so(m):null,T=new vo({registry:v,observations:A,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,r,o,t)=>{await S?.processMessages(e,r,o,t)},onBindingPaused:e=>{C.revokeSession(e.registryKey),a?.discardPendingManagedRoomMessages(e.nianSessionId)}});const _=new We({currentVersion:g,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??f,getIdleState:()=>a.getUpgradeIdleState(),sessionSyncProtocolVersion:ie,dataEpoch:le,advertisedCapabilities:ce,updaterProtocolVersion:1,startUpgrade:async e=>{const{handleUpgradeStart:r}=await import("./commands/upgrade.js");await r(m,"auto-upgrade",e,{currentVersion:g,prepareForUpgrade:o=>a.prepareForUpgrade(o),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})}});_.setPolicy(W),a=new Fe(m,D,g,_,new fo({registry:v,observations:A,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Ue(),isDisabled:()=>oe(),shutdownAfterResponse:()=>{setTimeout(()=>{De(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Be(U)),x=async e=>{const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof r.targetVersion=="string"?r.targetVersion:void 0,t=r.confirmedMajor===!0;return await xe(m,`local-upgrade-${Date.now()}`,o,{currentVersion:g,confirmedMajor:t,prepareForUpgrade:ue=>a.prepareForUpgrade(ue),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=e=>{const r=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=`desktop-${(typeof r.targetVersion=="string"?r.targetVersion:"unknown").replace(/[^A-Za-z0-9._-]/g,"_")}-${Date.now()}`;return{jobId:t,...a.prepareForUpgrade(t)}},L=()=>(a.cancelUpgradePreparation(),{cancelled:!0}),S=new Io({registry:v,observations:A,enqueueConversationBatch:e=>a.enqueueManagedRoomBatch(e),loadContext:async e=>{const r=p().machineToken?.trim(),o=e.binding;if(!r||!o)throw new Error("managed_room_context_unavailable");const t=await I.status({machineToken:r,roomId:o.roomId,bindingId:o.bindingId});return{roomName:t.room.name,memberCount:t.room.memberCount,members:t.room.members,agentsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent}:{},agentSystemPromptsByIdentityId:t.agent?{[t.agentIdentityId]:t.agent.systemPrompt??""}:{}}},issueManagedRoomContext:(e,r)=>{const o=p().machineToken;if(!o)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:o,activationCauseMessageId:r})},audit:new yo,resolveManagedAgentPolicy:e=>So({...e,runtime:y.find(r=>r.type===e.registration.runtimeAdapter)})}),a.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),a.setManagedAgentDirectTurnPreparer(e=>S.prepareDirectConversationTurn(e)),a.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),j.mkdirSync(de,{recursive:!0}),ne(process.pid,c,{version:g}),re(process.pid,void 0,c),m.connect(),process.stdin.resume();let Y=!1;const Z=async()=>{if(!Y){Y=!0,console.log(b.gray(`
|
|
3
|
-
Disconnecting...`)),P({level:"info",wsEvent:"daemon.stop"}),clearInterval(d);try{await a.cleanup()
|
|
1
|
+
import{Command as fe}from"commander";import k from"chalk";import he from"node:os";import j from"node:fs";import{loadConfig as p,saveConfig as ve,getConfigPath as Ie,getShennianDir as Se,resolveShennianPath as H,normalizeAutoUpgradePolicy as ye}from"./config/index.js";import{CliRelayClient as we}from"./relay/client.js";import{collectKnownLogSecrets as Q,installCliLogRedaction as Ce,sanitizeCliLogValue as Re}from"./log-redaction.js";import{registerPairCommand as be,runSmartStart as ke}from"./commands/pair.js";import{registerLogoutCommand as Pe}from"./commands/logout.js";import{clearDaemonPidIfOwner as X,createDaemonInstanceId as Ae,clearDaemonLauncher as ee,findRunningDaemonProcessIds as Ee,isRemoteAccessDisabled as oe,prepareRemoteAccessPause as Ue,stopPreparedRemoteAccessService as De,isIsolatedDaemonE2eInstance as Te,getDaemonStatus as Me,registerDaemonCommand as _e,writeDaemonPid as ne,writeDaemonLauncher as re}from"./commands/daemon.js";import{registerRuntimeCommand as $e}from"./commands/runtime.js";import{registerAgentCommand as Ne}from"./commands/agent.js";import{registerManagerCommand as Oe}from"./commands/manager.js";import{handleUpgradeStart as xe,registerUpgradeCommand as Ve}from"./commands/upgrade.js";import{registerToolsCommand as Le}from"./commands/tools.js";import{registerIntegrationCommand as je}from"./commands/integration.js";import{registerRoomCommand as He}from"./commands/room.js";import{SessionManager as Fe}from"./session/manager.js";import{createTrustedProjectFsRequestResolver as Be}from"./session/handlers/project-fs.js";import{SERVERS as F,regionToUrl as Ge,urlToRegion as qe}from"./region.js";import{clearUpgradeAttempt as ze,getCurrentVersion as te,handleStartupCrashCheck as Je,readUpgradeAttempt as Ke}from"./upgrade/engine.js";import{AutoUpgradeScheduler as We}from"./upgrade/scheduler.js";import{ensureUpdaterInstalled as Ye}from"./upgrade/updater-bootstrap.js";import{launchUpdater as Ze,markUpdaterHealthy as Qe}from"./upgrade/updater-launcher.js";import{detectAgents as Xe}from"./agents/detect.js";import{augmentProcessPath as ae}from"./env-path.js";import{acquireDaemonInstanceLock as eo}from"./commands/daemon-instance-lock.js";ae();const B=te();import{getCachedAgentInfos as oo,resolveAgentInfos as no}from"./agents/model-registry.js";import{initCliLogReporter as ro,reportLog as P,reportUpgradeAggregateMetric as to}from"./log-reporter.js";import{isNativeFusionEnabled as ao}from"./native-fusion/config.js";import{NativeSessionFusionService as so}from"./native-fusion/service.js";import{startDaemonLogRetention as io}from"./daemon-log.js";import{startDaemonIpcServer as se}from"./daemon-ipc/server.js";import{resolveDaemonIpcPaths as co,resolveManagedRoomIpcPaths as lo}from"./daemon-ipc/paths.js";import{createDaemonControlHandlers as mo}from"./daemon-ipc/control.js";import{DAEMON_IPC_VERSION as po}from"./daemon-ipc/protocol.js";import{RoomSessionRegistry as go}from"./room/session-registry.js";import{HttpRoomBindingClient as uo}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as fo}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as ho}from"./room/host-session-observations.js";import{RoomMirrorSyncService as vo}from"./room/mirror-sync-service.js";import{RoomActivationService as Io,resolveManagedAgentPolicy as So}from"./room/activation-service.js";import{RoomActivationAudit as yo}from"./room/activation-audit.js";import{RoomAttachmentCache as wo}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Co}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ro}from"./room/managed-context.js";import{createManagedRoomExecutor as bo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as ie,SESSION_SYNC_ADVERTISED_CAPABILITIES as ce}from"@shennian/wire";import{getDaemonInstallationId as ko}from"./session/store.js";import{synchronizeMachinePersonalAuthority as Po}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as le,ensurePersonalDataEpoch as Ao}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as Eo,DeletionSyncTerminalError as Uo}from"./session/deletion-sync-state.js";const de=Se(),Do=H("daemon.pid"),To=H("daemon.instance.lock");function Mo(){try{const n=j.readFileSync(Do,"utf-8").trim();if(n.startsWith("{")){const d=JSON.parse(n),c=Number(d.pid);return Number.isInteger(c)&&c>0?c:null}const i=parseInt(n,10);return Number.isInteger(i)&&i>0?i:null}catch{return null}}function _o(n){return n.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function G(){process.stdout.write(`${JSON.stringify({ok:!1,error:"integration_command_retired",message:"Room MCP and Hooks were retired. Use the Shennian Skill and CLI.",nextAction:{type:"open_install_guide",url:"https://shennian.net/install.md"}})}
|
|
2
|
+
`),process.exitCode=1}function $o(n){const i=[`trigger=${n.trigger??"unknown"}`,`phase=${n.phase}`,`code=${n.code??"unknown"}`,`attempt=${n.reconnectAttempt}`];return n.reason&&i.push(`reason=${n.reason}`),n.error&&i.push(`error=${n.error}`),i.join(" ")}const s=new fe;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(B),s.enablePositionalOptions(),je(s),He(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",he.hostname()).action(async n=>{const i=p(),d=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??i.serverUrl??void 0;await ke(d,n.name)}),s.command("run-service",{hidden:!0}).description("(internal) Connect to relay server, called by the background service").option("--api <url>","Server URL override").action(async n=>{const i=H("env.json");try{const e=JSON.parse(j.readFileSync(i,"utf-8"));for(const[t,o]of Object.entries(e))process.env[t]||(process.env[t]=o);ae()}catch{}Ce(Q(process.env,p()));const d=io();oe()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(d),process.exit(0));const c=Ae(),h=eo({lockPath:To,instanceId:c});h.acquired||(console.log(`[${new Date().toISOString()}] daemon already running`+(h.ownerPid?` (PID ${h.ownerPid})`:"")+", skipping duplicate start"),clearInterval(d),process.exit(0));try{const e=Mo();if(e&&e!==process.pid)try{process.kill(e,0),console.log(`[${new Date().toISOString()}] daemon already running (PID ${e}), skipping duplicate start`),h.release(),process.exit(0)}catch{}}catch{}const q=Te(process.env,de)?[]:Ee(process.pid);q.length>0&&(console.log(`[${new Date().toISOString()}] daemon already running (PID ${q[0]}), skipping duplicate start`),h.release(),process.exit(0)),ne(process.pid,c,{version:B}),re(process.pid,void 0,c),process.on("exit",()=>{X(process.pid,c),ee(c)}),process.on("exit",h.release);let N=null;try{N=Ye()}catch(e){console.error(`[upgrade] updater bootstrap unavailable: ${e instanceof Error?e.message:String(e)}`)}const A=await Je();if(A){N||(console.error("[upgrade] Crash-loop rollback blocked: updater bootstrap unavailable"),process.exit(1));try{Ze({fromVersion:B,targetVersion:A.from,oldDaemonPid:process.pid,operation:"rollback",rollbackJobId:A.jobId},{readiness:N}),console.error(`[upgrade] New version crashed ${A.attemptCount} times; external rollback scheduled.`),process.stdin.resume();return}catch(e){console.error(`[upgrade] Crash-loop rollback could not start: ${e instanceof Error?e.message:String(e)}`),process.exit(1)}}try{Ao()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const l=p(),f=n.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??l.serverUrl??F.cn.url,g=te(),y=await Xe({serverUrl:f,authToken:l.machineToken??l.accessToken}),w=y.map(e=>e.type),O=oo(y),v=new go,E=new ho,C=new Ro(v),I=new uo(f),pe=new wo({actionClient:I}),ge=new Co({actionClient:I});let S=null,a;const z=bo({authority:C,actionClient:I,attachmentCache:pe,attachmentUploader:ge,observations:E,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:t})=>{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"}),C.revokeSession(e),a?.discardPendingManagedRoomMessages(t)}});let x=null,V=null,L=null;const R=await se({paths:co(),handlers:mo({daemonVersion:g,protocolVersion:po,getStatus:()=>Me(),getAgents:()=>y,completeAgentAuthentication:e=>a.resumePendingAcpAuthentication(e),startUpgrade:async e=>{if(!x)throw new Error("upgrade_unavailable");return x(e)},prepareExternalUpgrade:e=>{if(!V)throw new Error("external_upgrade_unavailable");return V(e)},cancelExternalUpgrade:()=>{if(!L)throw new Error("external_upgrade_unavailable");return L()}})});let b;try{b=await se({paths:lo(),handlers:{"managed-room.execute":e=>z(e)}})}catch(e){throw await R.close(),e}let J=!1;const U=()=>{J||(J=!0,Promise.allSettled([R.close(),b.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([R.close(),b.close()]),process.exitCode=1;return}process.removeListener("SIGINT",U),process.removeListener("SIGTERM",U);const K=`${_o(f)}/relay/machine`,D=l.machineToken;if(!D)return;try{await Po({serverUrl:f,machineToken:D,machineId:l.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await Promise.allSettled([R.close(),b.close()]),process.exit(1)}l.machineId&&ro(f,l.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${K}... (v${g}) agents: ${w.join(",")}`),P({level:"info",wsEvent:"daemon.start",metadata:{version:g,agents:w}});let T=null,M=null,_=0,u=Ke();const W=ye(l.autoUpgrade),m=new we({serverUrl:K,machineToken:D,cliVersion:g,agentList:w,autoUpgradePolicy:W,sessionSyncProtocolVersion:ie,sessionSyncCapabilities:ce,daemonInstallationId:ko(),dataEpoch:le,updaterProtocolVersion:1,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),P({level:"info",wsEvent:"daemon.connected"}),O.some(o=>o.models.length>0)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:O}}),no(y,{serverUrl:l.serverUrl??f,authToken:l.machineToken??l.accessToken}).then(o=>{JSON.stringify(o)!==JSON.stringify(O)&&m.sendEvent({type:"event",event:"machine.agents",payload:{agentList:w,agents:o}})}).catch(()=>{}),u&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"recovering",machineId:"self",from:u.from,to:u.to,...u.jobId?{jobId:u.jobId}:{},activeRunCount:a.getPendingRecoveryCount()}}),$.start(),M?.start();const e=++_,t=async()=>{try{if(await a.synchronizeSessionDeletions(),await a.recoverInterruptedRuns(),u){const o=u;u=null,o.jobId&&Qe(o.jobId,o.to),ze(),console.log(`[${new Date().toISOString()}] [upgrade] ${o.operation==="rollback"?"Rolled back":"Healthy"}: ${o.from} \u2192 ${o.to}`),to({kind:"upgrade",result:o.operation==="rollback"?"rolled_back":"healthy",stage:"terminal"}),m.sendEvent({type:"event",event:"upgrade",payload:{state:o.operation==="rollback"?"rolled_back":"healthy",machineId:"self",from:o.from,to:o.to,...o.jobId?{jobId:o.jobId}:{}}}),o.operation!=="rollback"&&m.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:o.from,to:o.to}})}e===_&&m.getState()==="connected"&&T?.handleConnected()}catch(o){if(console.error("[session.deletions] startup catch-up failed",o),e===_&&m.getState()==="connected"){if(o instanceof Uo)return;const r=o instanceof Eo?Math.max(1e3,o.retryAfterMs):5e3;setTimeout(()=>{t()},r).unref?.()}}};t()},onDisconnected:e=>{_+=1,a?.revokeProjectFsHandles(),M?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${$o(e)}`),P({level:"warn",wsEvent:"daemon.disconnected",metadata:{code:e.code,reason:e.reason,error:e.error,phase:e.phase,trigger:e.trigger,reconnectAttempt:e.reconnectAttempt}})},onProjectFsCapabilityChanged:e=>{e||a?.revokeProjectFsHandles()},onReq:e=>{console.log(`[${new Date().toISOString()}] [req] ${e.method}`),P({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),a.handleReq(e)}});T=ao()?new so(m):null,M=new vo({registry:v,observations:E,actionClient:I,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,t,o,r)=>{await S?.processMessages(e,t,o,r)},onBindingPaused:e=>{C.revokeSession(e.registryKey),a?.discardPendingManagedRoomMessages(e.nianSessionId)}});const $=new We({currentVersion:g,machineId:l.machineId??"unknown-machine",serverUrl:l.serverUrl??f,getIdleState:()=>a.getUpgradeIdleState(),sessionSyncProtocolVersion:ie,dataEpoch:le,advertisedCapabilities:ce,updaterProtocolVersion:1,startUpgrade:async(e,t)=>{const{handleUpgradeStart:o}=await import("./commands/upgrade.js");await o(m,"auto-upgrade",e,{currentVersion:g,prepareForUpgrade:r=>a.prepareForUpgrade(r),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0,waitForCompletion:!0,canHandoff:t?void 0:()=>a.getUpgradeIdleState().idle})}});$.setPolicy(W),a=new Fe(m,T,g,$,new fo({registry:v,observations:E,bindingClient:I,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${c}`}),{prepare:()=>Ue(),isDisabled:()=>oe(),shutdownAfterResponse:()=>{setTimeout(()=>{De(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>C.revokeToken(e),z,null,Be(D)),x=async e=>{const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=typeof t.targetVersion=="string"?t.targetVersion:void 0,r=t.confirmedMajor===!0;return await xe(m,`local-upgrade-${Date.now()}`,o,{currentVersion:g,confirmedMajor:r,prepareForUpgrade:ue=>a.prepareForUpgrade(ue),cancelUpgradePreparation:()=>a.cancelUpgradePreparation(),shutdownAfterHandoff:()=>{setTimeout(()=>process.kill(process.pid,"SIGTERM"),100).unref?.()},suppressResponse:!0})},V=e=>{const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},r=`desktop-${(typeof t.targetVersion=="string"?t.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:E,enqueueConversationBatch:e=>a.enqueueManagedRoomBatch(e),loadContext:async e=>{const t=p().machineToken?.trim(),o=e.binding;if(!t||!o)throw new Error("managed_room_context_unavailable");const r=await I.status({machineToken:t,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,t)=>{const o=p().machineToken;if(!o)throw new Error("machine_not_authorized");return C.issue({registryKey:e,machineCredential:o,activationCauseMessageId:t})},audit:new yo,resolveManagedAgentPolicy:e=>So({...e,runtime:y.find(t=>t.type===e.registration.runtimeAdapter)})}),a.setManagedRoomTurnPreparer(e=>S.prepareConversationTurn(e)),a.setManagedAgentDirectTurnPreparer(e=>S.prepareDirectConversationTurn(e)),a.setManagedRecoveryPreparer(async e=>e.source==="managed_direct"?S.prepareDirectConversationTurn(e.sessionId):e.source!=="managed_room"||!e.roomContextRegistryKey||!e.activationCauseMessageId||!e.rootCauseMessageId||!e.activationSequence||e.activationHopCount===null?null:S.prepareConversationTurn([{registryKey:e.roomContextRegistryKey,causeMessageId:e.activationCauseMessageId,rootCauseMessageId:e.rootCauseMessageId,sequence:e.activationSequence,hopCount:e.activationHopCount}])),j.mkdirSync(de,{recursive:!0}),ne(process.pid,c,{version:g}),re(process.pid,void 0,c),m.connect(),process.stdin.resume();let Y=!1;const Z=async()=>{if(!Y){Y=!0,console.log(k.gray(`
|
|
3
|
+
Disconnecting...`)),P({level:"info",wsEvent:"daemon.stop"}),clearInterval(d);try{await a.cleanup(),$.stop(),T?.stop(),M?.stop(),m.disconnect(),await Promise.allSettled([R.close(),b.close()]),X(process.pid,c),ee(c)}finally{process.exit(0)}}};process.on("SIGINT",()=>{Z()}),process.on("SIGTERM",()=>{Z()})}),s.command("workbuddy-session-hook",{hidden:!0}).description("(retired) WorkBuddy Room Hook").allowUnknownOption(!0).action(()=>G()),s.command("room-session-hook",{hidden:!0}).description("(internal) Authorize the official Room tool for one trusted Agent Session").argument("<runtime>","Trusted runtime adapter").allowUnknownOption(!0).action(()=>G()),s.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>G());const me=s.command("config").description("View or modify configuration");me.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${Ie()}`);const n=p(),i=Re(n,Q(process.env,n));if(n.serverUrl){const d=qe(n.serverUrl);console.log(`Region: ${d} (${F[d].label})`)}console.log(JSON.stringify(i,null,2))}),me.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((n,i)=>{const d=p();if(n==="server"){i!=="cn"&&i!=="global"&&(console.error(k.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const c=i;d.serverUrl=Ge(c),ve(d),console.log(k.green(`\u2713 Server set to ${F[c].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: ${n}. Supported: server`)),process.exit(1)}),be(s),Pe(s),_e(s),$e(s),Ne(s),Oe(s),Ve(s),Le(s),s.parse();
|
|
@@ -12,6 +12,7 @@ export type CliReleasePolicy = {
|
|
|
12
12
|
blockedCurrentVersions: string[];
|
|
13
13
|
killSwitch: boolean;
|
|
14
14
|
releaseLevel: 'normal' | 'required' | 'blocked';
|
|
15
|
+
/** Legacy wire field; normal updates now wait for idle without a deadline. */
|
|
15
16
|
maxIdleWaitMs: number;
|
|
16
17
|
minimumSessionSyncProtocolVersion?: number;
|
|
17
18
|
minimumDataEpoch?: number;
|
|
@@ -24,7 +25,7 @@ type AutoUpgradeSchedulerDeps = {
|
|
|
24
25
|
machineId: string;
|
|
25
26
|
serverUrl: string;
|
|
26
27
|
getIdleState: () => UpgradeIdleState;
|
|
27
|
-
startUpgrade: (targetVersion: string) => Promise<void>;
|
|
28
|
+
startUpgrade: (targetVersion: string, mandatory?: boolean) => Promise<void>;
|
|
28
29
|
checkForUpdate?: (currentVersion?: string) => Promise<VersionCheckResult>;
|
|
29
30
|
fetchReleasePolicy?: () => Promise<CliReleasePolicy>;
|
|
30
31
|
inCooldown?: (version: string) => boolean;
|
|
@@ -32,7 +33,7 @@ type AutoUpgradeSchedulerDeps = {
|
|
|
32
33
|
intervalMs?: number;
|
|
33
34
|
setTimeout?: (handler: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
|
34
35
|
clearTimeout?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
35
|
-
|
|
36
|
+
random?: () => number;
|
|
36
37
|
sessionSyncProtocolVersion?: number;
|
|
37
38
|
dataEpoch?: number;
|
|
38
39
|
advertisedCapabilities?: readonly string[];
|
|
@@ -47,13 +48,13 @@ export declare class AutoUpgradeScheduler {
|
|
|
47
48
|
private timer;
|
|
48
49
|
private started;
|
|
49
50
|
private upgradeInFlight;
|
|
50
|
-
private eligibleSince;
|
|
51
51
|
constructor(deps: AutoUpgradeSchedulerDeps);
|
|
52
52
|
start(): void;
|
|
53
53
|
stop(): void;
|
|
54
54
|
setPolicy(policy: RuntimeAutoUpgradePolicy): void;
|
|
55
55
|
getPolicy(): RuntimeAutoUpgradePolicy;
|
|
56
56
|
runOnce(): Promise<void>;
|
|
57
|
+
private nextIntervalMs;
|
|
57
58
|
private arm;
|
|
58
59
|
}
|
|
59
60
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{checkForUpdate as
|
|
1
|
+
import{checkForUpdate as l,isUpgradeVersionInCooldown as c}from"./engine.js";const u={automaticPatchEnabled:!1,rolloutPercentage:0,pausedVersions:[],blockedVersions:[],blockedCurrentVersions:[],killSwitch:!0,releaseLevel:"normal",maxIdleWaitMs:30*6e4,requiredCapabilities:[],externalUpdaterEnabled:!1};function d(r){let i=2166136261;for(let t=0;t<r.length;t++)i^=r.charCodeAt(t),i=Math.imul(i,16777619);return(i>>>0)%100}function m(r,i,t,e){if(r.killSwitch||!r.automaticPatchEnabled||r.blockedVersions.includes(e)||r.pausedVersions.includes(e)||r.pausedVersions.includes(t))return!1;const s=Math.max(0,Math.min(100,Math.floor(r.rolloutPercentage)));return d(i)<s}async function h(r){const i=new URL("/api/releases/cli-policy",r),t=await fetch(i,{headers:{Accept:"application/json"},signal:AbortSignal.timeout(1e4)});if(!t.ok)throw new Error(`release policy returned ${t.status}`);const e=await t.json();return{automaticPatchEnabled:e.automaticPatchEnabled===!0,rolloutPercentage:typeof e.rolloutPercentage=="number"?e.rolloutPercentage:0,pausedVersions:Array.isArray(e.pausedVersions)?e.pausedVersions.filter(s=>typeof s=="string"):[],blockedVersions:Array.isArray(e.blockedVersions)?e.blockedVersions.filter(s=>typeof s=="string"):[],blockedCurrentVersions:Array.isArray(e.blockedCurrentVersions)?e.blockedCurrentVersions.filter(s=>typeof s=="string"):[],killSwitch:e.killSwitch!==!1,releaseLevel:e.releaseLevel==="required"||e.releaseLevel==="blocked"?e.releaseLevel:"normal",maxIdleWaitMs:typeof e.maxIdleWaitMs=="number"&&Number.isFinite(e.maxIdleWaitMs)?Math.max(0,Math.min(1440*6e4,Math.floor(e.maxIdleWaitMs))):30*6e4,...typeof e.minimumSessionSyncProtocolVersion=="number"?{minimumSessionSyncProtocolVersion:e.minimumSessionSyncProtocolVersion}:{},...typeof e.minimumDataEpoch=="number"?{minimumDataEpoch:e.minimumDataEpoch}:{},requiredCapabilities:Array.isArray(e.requiredCapabilities)?e.requiredCapabilities.filter(s=>typeof s=="string"):[],externalUpdaterEnabled:e.externalUpdaterEnabled===!0,...typeof e.minimumUpdaterProtocolVersion=="number"?{minimumUpdaterProtocolVersion:e.minimumUpdaterProtocolVersion}:{}}}class f{deps;policy="none";timer=null;started=!1;upgradeInFlight=!1;constructor(i){this.deps=i}start(){this.started||(this.started=!0,this.arm(this.deps.initialDelayMs??3e4))}stop(){this.started=!1,this.timer&&(this.deps.clearTimeout??clearTimeout)(this.timer),this.timer=null}setPolicy(i){this.policy=i,this.started&&(this.timer||this.arm(0))}getPolicy(){return this.policy}async runOnce(){if(this.upgradeInFlight)return;const i=await(this.deps.checkForUpdate??l)(this.deps.currentVersion);if(!i.hasUpdate||(this.deps.inCooldown??c)(i.latest))return;let t=u;try{t=await(this.deps.fetchReleasePolicy??(()=>h(this.deps.serverUrl)))()}catch{return}if(t.killSwitch||!t.externalUpdaterEnabled||t.blockedVersions.includes(i.latest)||t.pausedVersions.includes(i.latest)||t.pausedVersions.includes(i.current))return;const e=t.blockedCurrentVersions.includes(i.current)?"blocked":t.releaseLevel,s=t.minimumSessionSyncProtocolVersion!==void 0&&(this.deps.sessionSyncProtocolVersion??0)<t.minimumSessionSyncProtocolVersion||t.minimumDataEpoch!==void 0&&(this.deps.dataEpoch??0)<t.minimumDataEpoch||t.requiredCapabilities.some(o=>!(this.deps.advertisedCapabilities??[]).includes(o))||t.minimumUpdaterProtocolVersion!==void 0&&(this.deps.updaterProtocolVersion??0)<t.minimumUpdaterProtocolVersion,a=e==="required"||e==="blocked"||s;if(!a&&(this.policy!=="patch"||i.changeType!=="patch"||!m(t,this.deps.machineId,i.current,i.latest)))return;const n=this.deps.getIdleState();if(!(!a&&!n.idle)){this.upgradeInFlight=!0;try{await this.deps.startUpgrade(i.latest,a)}finally{this.upgradeInFlight=!1}}}nextIntervalMs(){return this.deps.intervalMs??5*6e4+Math.floor((this.deps.random??Math.random)()*10*6e4)}arm(i){if(!this.started||this.timer)return;const t=this.deps.setTimeout??setTimeout;this.timer=t(()=>{this.timer=null,this.runOnce().catch(()=>{}).finally(()=>{this.started&&this.arm(this.nextIntervalMs())})},i),this.timer.unref?.()}}export{f as AutoUpgradeScheduler,d as deterministicRolloutBucket,h as fetchCliReleasePolicy,m as isAutomaticPatchAllowed};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import r from"node:fs";import
|
|
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,
|
|
1
|
+
import r from"node:fs";import t from"node:path";import{fileURLToPath as S}from"node:url";import{randomUUID as d}from"node:crypto";import{resolveShennianPath as f}from"../config/index.js";import{UPDATER_PROTOCOL_VERSION as l}from"./updater-protocol.js";function m(e,o){r.mkdirSync(t.dirname(e),{recursive:!0,mode:448});const n=`${e}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(n,`${JSON.stringify(o,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,e)}finally{try{r.unlinkSync(n)}catch{}}}function P(e={}){const o=e.root??f("runtime","updater"),n=t.join(o,"versions",`${l}-offline`);r.mkdirSync(n,{recursive:!0,mode:448}),process.platform!=="win32"&&r.chmodSync(n,448),m(t.join(n,"package.json"),{type:"module"});const i=e.assetPath??S(new URL("./updater-worker.js",import.meta.url)),s=t.join(n,"updater-worker.js"),p=r.readFileSync(i),u=(()=>{try{return r.readFileSync(s)}catch{return null}})();if(!u||!u.equals(p)){const c=`${s}.tmp-${process.pid}-${d()}`;try{r.writeFileSync(c,p,{mode:448,flag:"wx"}),process.platform!=="win32"&&r.chmodSync(c,448);const y=r.openSync(c,process.platform==="win32"?"r+":"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:(e.now??new Date).toISOString()};return m(t.join(o,"current.json"),a),m(t.join(o,"readiness.json"),{protocolVersion:a.protocolVersion,installedAt:a.installedAt}),a}function U(e=f("runtime","updater")){try{const o=JSON.parse(r.readFileSync(t.join(e,"current.json"),"utf8"));if(o.protocolVersion!==l||!o.workerPath)return null;const n=r.lstatSync(o.workerPath);return n.isSymbolicLink()||!n.isFile()?null:o}catch{return null}}export{P as ensureUpdaterInstalled,U as readUpdaterReadiness};
|
|
@@ -13,6 +13,7 @@ export declare function createUpdaterJob(input: {
|
|
|
13
13
|
jobId?: string;
|
|
14
14
|
operation?: 'upgrade' | 'rollback';
|
|
15
15
|
isolatedDaemonStart?: boolean;
|
|
16
|
+
rollbackJobId?: string;
|
|
16
17
|
}): UpdaterJob;
|
|
17
18
|
export declare function writeUpdaterJob(job: UpdaterJob, root?: string): string;
|
|
18
19
|
export declare function needsSystemdUpdater(platform: string, cgroup: string): boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{mode:384,flag:"wx"});const c=
|
|
3
|
-
`,{mode:384,flag:"wx"});const s=
|
|
1
|
+
import r from"node:fs";import d from"node:path";import{randomUUID as y}from"node:crypto";import{spawn as j,spawnSync as I}from"node:child_process";import{resolveShennianPath as u}from"../config/index.js";import{findPackageJson as P,resolveNpmGlobalPrefixFromPackageJson as E}from"./engine.js";import{ensureUpdaterInstalled as J}from"./updater-bootstrap.js";import{UPDATER_PROTOCOL_VERSION as S}from"./updater-protocol.js";function g(e,o){r.mkdirSync(d.dirname(e),{recursive:!0,mode:448});const n=`${e}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(n,`${JSON.stringify(o,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"});const c=r.openSync(n,process.platform==="win32"?"r+":"r");try{r.fsyncSync(c)}finally{r.closeSync(c)}if(r.renameSync(n,e),process.platform!=="win32"){const t=r.openSync(d.dirname(e),"r");try{r.fsyncSync(t)}finally{r.closeSync(t)}}}finally{try{r.unlinkSync(n)}catch{}}}function $(e){const o=e.root??u("runtime","updater"),n=P(),c=e.npmPrefix??(n?E(n):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:d.join(o,"results",`${t}.json`),attemptPath:u("upgrade-attempt.json"),...e.rollbackJobId?{rollbackJobId:e.rollbackJobId}:{},...e.isolatedDaemonStart?{isolatedDaemonStart:!0}:{}}}function O(e,o=u("runtime","updater")){const n=d.join(o,"jobs");r.mkdirSync(n,{recursive:!0,mode:448});const c=d.join(n,`${e.jobId}.json`),t=`${c}.tmp-${process.pid}-${y()}`;try{r.writeFileSync(t,`${JSON.stringify(e,null,2)}
|
|
3
|
+
`,{mode:384,flag:"wx"});const s=r.openSync(t,process.platform==="win32"?"r+":"r");try{r.fsyncSync(s)}finally{r.closeSync(s)}if(r.renameSync(t,c),process.platform!=="win32"){const a=r.openSync(n,"r");try{r.fsyncSync(a)}finally{r.closeSync(a)}}return c}finally{try{r.unlinkSync(t)}catch{}}}function _(e,o){return e==="linux"&&/(?:^|\/)shennian\.service(?:\n|$)/m.test(o.trim())}function C(e,o={}){const n=o.root??e.root??u("runtime","updater"),c=o.readiness??J({root:n}),t=$({...e,root:n}),s=O(t,n),a=d.join(n,"updater.lock");r.mkdirSync(n,{recursive:!0,mode:448});let l=null,m=!1;const w=()=>{if(m){try{JSON.parse(r.readFileSync(a,"utf8")).jobId===t.jobId&&r.unlinkSync(a)}catch{}m=!1}};try{try{l=r.openSync(a,"wx",384),m=!0}catch(i){if(i.code!=="EEXIST")throw i;let h=!1;try{const b=JSON.parse(r.readFileSync(a,"utf8")),k=o.isAlive??(x=>{try{return process.kill(x,0),!0}catch{return!1}});h=!!(b.pid&&k(b.pid))}catch{}if(h)throw new Error("upgrade_locked");r.rmSync(a,{force:!0}),l=r.openSync(a,"wx",384),m=!0}r.writeFileSync(l,JSON.stringify({jobId:t.jobId,pid:process.pid,createdAt:(o.now??Date.now)()})),r.closeSync(l),l=null;let p=o.serviceCgroup??"";if(process.platform==="linux"&&!o.spawn&&o.serviceCgroup===void 0)try{p=r.readFileSync("/proc/self/cgroup","utf8")}catch{}if(_(o.platform??process.platform,p)){const i=(o.systemdRun??I)("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,c.workerPath,s],{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 f=(o.spawn??j)(t.nodeExecutable,[c.workerPath,s],{detached:!0,stdio:"ignore",windowsHide:!0});return f.once?.("error",i=>{g(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()}),w()}),f.unref(),{job:t,process:f}}catch(p){l!==null&&r.closeSync(l),w();try{r.unlinkSync(s)}catch{}throw p}}function H(e,o,n=u("runtime","updater")){if(!/^[A-Za-z0-9_-]{1,128}$/.test(e))return!1;const c=d.join(n,"results",`${e}.json`);try{const t=r.lstatSync(c);if(t.isSymbolicLink()||!t.isFile())return!1;const s=JSON.parse(r.readFileSync(c,"utf8"));return s.protocolVersion!==S||s.jobId!==e||s.state!=="starting"||s.targetVersion!==o?!1:(g(c,{...s,state:s.operation==="rollback"?"rolled_back":"healthy",effectiveVersion:o,updatedAt:new Date().toISOString()}),!0)}catch{return!1}}export{$ as createUpdaterJob,C as launchUpdater,H as markUpdaterHealthy,_ as needsSystemdUpdater,O as writeUpdaterJob};
|
|
@@ -18,6 +18,8 @@ export type UpdaterJob = {
|
|
|
18
18
|
attemptPath: string;
|
|
19
19
|
/** Release-smoke only: bypass installed platform service and use exact-bin fallback. */
|
|
20
20
|
isolatedDaemonStart?: boolean;
|
|
21
|
+
/** Original local installation transaction used by crash-loop recovery. */
|
|
22
|
+
rollbackJobId?: string;
|
|
21
23
|
};
|
|
22
24
|
export type UpdaterResultState = 'waiting_for_daemon' | 'installing' | 'verifying' | 'starting' | 'healthy' | 'rolled_back' | 'failed';
|
|
23
25
|
export type UpdaterErrorCode = 'invalid_job' | 'job_expired' | 'upgrade_locked' | 'daemon_exit_timeout' | 'npm_install_failed' | 'verification_failed' | 'rollback_failed' | 'daemon_start_failed' | 'internal_error';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const b=1,d=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;function p(o){if(!o||typeof o!="object"||Array.isArray(o))throw new Error("invalid_job");const r=o,e=m=>{const i=r[m];if(typeof i!="string"||!i.trim())throw new Error("invalid_job");return i};if(r.protocolVersion!==1)throw new Error("invalid_job");const s=e("fromVersion"),l=e("targetVersion");if(!d.test(s)||!d.test(l))throw new Error("invalid_job");const c=e("createdAt");if(!Number.isFinite(Date.parse(c)))throw new Error("invalid_job");if(r.packageName!=="shennian")throw new Error("invalid_job");const t=r.oldDaemonPid==null?null:Number(r.oldDaemonPid);if(t!=null&&(!Number.isSafeInteger(t)||t<=0))throw new Error("invalid_job");const n=Number(r.daemonExitTimeoutMs),a=Number(r.commandTimeoutMs);if(!Number.isSafeInteger(n)||n<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:e("jobId"),fromVersion:s,targetVersion:l,npmPrefix:e("npmPrefix"),npmExecutable:e("npmExecutable"),nodeExecutable:e("nodeExecutable"),packageName:"shennian",operation:r.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:t,platform:e("platform"),createdAt:c,daemonExitTimeoutMs:n,commandTimeoutMs:a,resultPath:e("resultPath"),attemptPath:e("attemptPath"),isolatedDaemonStart:r.isolatedDaemonStart===!0,...typeof r.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(r.rollbackJobId)?{rollbackJobId:r.rollbackJobId}:{}}}function u(o){return(o instanceof Error?o.message:String(o)).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)}export{b as UPDATER_PROTOCOL_VERSION,p as parseUpdaterJob,u as sanitizeUpdaterError};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import type { UpdaterJob } from './updater-protocol.js';
|
|
3
|
+
/** Update only this installation's service entry, without restarting the daemon. */
|
|
4
|
+
export declare function prepareRecoveryServiceEntry(job: UpdaterJob, workerPath: string, deps?: {
|
|
5
|
+
home?: string;
|
|
6
|
+
run?: typeof execFileSync;
|
|
7
|
+
}): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import r from"node:fs";import S from"node:os";import a from"node:path";import{execFileSync as h}from"node:child_process";import{buildWindowsLauncherCommand as g,escapeXml as w,quoteSystemdArg as x}from"../commands/daemon-windows.js";import{updaterPaths as E}from"./updater-worker.js";function L(e,l,m={}){if(e.isolatedDaemonStart)return;const d=a.dirname(a.dirname(e.resultPath)),c=m.home??S.homedir(),p=a.join(E(e).active,"dist/bin/shennian.js"),s=[l,"--service",d,p,"run-service"];let t,i;if(e.platform==="linux"?(t=a.join(c,".config/systemd/user/shennian.service"),i=n=>{if(!/^ExecStart=.*(?:shennian|updater-worker)/m.test(n))throw new Error("unsupported_service_entry");return n.replace(/^ExecStart=.*$/m,`ExecStart=${[e.nodeExecutable,...s].map(x).join(" ")}`)}):e.platform==="darwin"?(t=a.join(c,"Library/LaunchAgents/com.shennian.agent.plist"),i=n=>{if(!n.includes("com.shennian.agent")||!/<key>ProgramArguments<\/key>\s*<array>[\s\S]*?<\/array>/.test(n))throw new Error("unsupported_service_entry");return n.replace(/(<key>ProgramArguments<\/key>\s*)<array>[\s\S]*?<\/array>/,`$1<array>${[e.nodeExecutable,...s].map(f=>`<string>${w(f)}</string>`).join("")}</array>`)}):(t=a.join(a.dirname(e.attemptPath),"autostart.cmd"),i=()=>g({command:e.nodeExecutable,args:s},a.join(a.dirname(e.attemptPath),"daemon.log"))),!r.existsSync(t))return;if(r.lstatSync(t).isSymbolicLink())throw new Error("unsafe_service_entry");const y=r.readFileSync(t,"utf8"),u=i(y);if(u===y)return;const o=`${t}.upgrade-${e.jobId}`;try{r.writeFileSync(o,u,{flag:"wx",mode:r.statSync(t).mode&511});const n=r.openSync(o,e.platform==="win32"?"r+":"r");try{r.fsyncSync(n)}finally{r.closeSync(n)}r.renameSync(o,t),e.platform==="linux"&&(m.run??h)("systemctl",["--user","daemon-reload"],{stdio:"pipe",timeout:15e3})}finally{try{r.unlinkSync(o)}catch{}}}export{L as prepareRecoveryServiceEntry};
|
|
@@ -1,11 +1,48 @@
|
|
|
1
1
|
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
2
|
declare const execFile: typeof execFileCallback.__promisify__;
|
|
3
|
+
type Job = {
|
|
4
|
+
protocolVersion: 1;
|
|
5
|
+
jobId: string;
|
|
6
|
+
fromVersion: string;
|
|
7
|
+
targetVersion: string;
|
|
8
|
+
npmPrefix: string;
|
|
9
|
+
npmExecutable: string;
|
|
10
|
+
nodeExecutable: string;
|
|
11
|
+
packageName: 'shennian';
|
|
12
|
+
operation: 'upgrade' | 'rollback';
|
|
13
|
+
oldDaemonPid: number | null;
|
|
14
|
+
platform: NodeJS.Platform;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
daemonExitTimeoutMs: number;
|
|
17
|
+
commandTimeoutMs: number;
|
|
18
|
+
resultPath: string;
|
|
19
|
+
attemptPath: string;
|
|
20
|
+
isolatedDaemonStart?: boolean;
|
|
21
|
+
rollbackJobId?: string;
|
|
22
|
+
};
|
|
3
23
|
type WorkerDeps = {
|
|
4
24
|
now?: () => number;
|
|
5
25
|
isAlive?: (pid: number) => boolean;
|
|
6
26
|
kill?: (pid: number, signal: NodeJS.Signals) => void;
|
|
7
27
|
sleep?: (ms: number) => Promise<void>;
|
|
8
28
|
execFile?: typeof execFile;
|
|
29
|
+
healthTimeoutMs?: number;
|
|
30
|
+
serviceRecovery?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export declare function updaterPaths(job: Job): {
|
|
33
|
+
active: string;
|
|
34
|
+
transaction: string;
|
|
35
|
+
prefix: string;
|
|
36
|
+
candidate: string;
|
|
37
|
+
previous: string;
|
|
38
|
+
rejected: string;
|
|
39
|
+
marker: string;
|
|
9
40
|
};
|
|
41
|
+
export declare function verifyLocalPackage(directory: string, version: string): void;
|
|
42
|
+
export declare function assertUpdaterAvailable(root: string): void;
|
|
43
|
+
export declare function prepareUpdaterCandidate(job: Job, deps?: WorkerDeps): Promise<void>;
|
|
44
|
+
/** Validate an already materialized candidate too (offline repair/artifact transport). */
|
|
45
|
+
export declare function finalizeUpdaterCandidate(job: Job, deps?: WorkerDeps): Promise<void>;
|
|
10
46
|
export declare function runUpdaterJob(jobPath: string, deps?: WorkerDeps): Promise<void>;
|
|
47
|
+
export declare function recoverServiceEntry(root: string): Promise<void>;
|
|
11
48
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`,{mode:384,flag:"wx"});const n=a.openSync(t,process.platform==="win32"?"r+":"r");try{a.fsyncSync(n)}finally{a.closeSync(n)}a.renameSync(t,e)}finally{try{a.unlinkSync(t)}catch{}}}function N(e){const r=a.lstatSync(e);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const t=JSON.parse(a.readFileSync(e,"utf8")),n=u=>{if(typeof t[u]!="string"||!String(t[u]).trim())throw new Error("invalid_job");return String(t[u])},c=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),i=n("targetVersion");if(t.protocolVersion!==_||t.packageName!=="shennian")throw new Error("invalid_job");if(!c.test(l)||!c.test(i))throw new Error("invalid_job");if(!Number.isFinite(Date.parse(n("createdAt"))))throw new Error("invalid_job");if(!/^[A-Za-z0-9_-]{1,128}$/.test(n("jobId")))throw new Error("invalid_job");if(!["darwin","linux","win32"].includes(n("platform")))throw new Error("invalid_job");const d=t.oldDaemonPid==null?null:Number(t.oldDaemonPid),w=Number(t.daemonExitTimeoutMs),s=Number(t.commandTimeoutMs);if(d!=null&&(!Number.isSafeInteger(d)||d<1))throw new Error("invalid_job");if(!Number.isSafeInteger(w)||w<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(s)||s<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:i,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:t.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:d,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:w,commandTimeoutMs:s,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:t.isolatedDaemonStart===!0}}function F(e,r){return r==="win32"?o.win32.join(e,"node_modules","shennian"):o.posix.join(e,"lib","node_modules","shennian")}function h(e){try{return process.kill(e,0),!0}catch{return!1}}async function O(e,r){if(!e.oldDaemonPid||!(r.isAlive??h)(e.oldDaemonPid))return;const t=r.kill??((l,i)=>process.kill(l,i)),n=r.sleep??(l=>new Promise(i=>setTimeout(i,l)));t(e.oldDaemonPid,"SIGTERM");const c=(r.now??Date.now)()+e.daemonExitTimeoutMs;for(;(r.now??Date.now)()<c;){if(!(r.isAlive??h)(e.oldDaemonPid))return;await n(100)}if(r.kill)t(e.oldDaemonPid,"SIGKILL");else if(e.platform==="win32")try{await(r.execFile??S)("taskkill",["/PID",String(e.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-e.oldDaemonPid,"SIGKILL")}catch{try{process.kill(e.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??h)(e.oldDaemonPid))throw new Error("daemon_exit_timeout")}function f(e,r=e.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:e.platform==="win32"}}async function v(e,r,t){const n=["install","-g",`shennian@${r}`,"--prefix",e.npmPrefix];try{await t(e.npmExecutable,n,f(e))}catch{await t(e.npmExecutable,[...n,"--registry",A],f(e))}}async function y(e,r,t){const n=F(e.npmPrefix,e.platform),c=o.join(n,"dist","bin","shennian.js"),{stdout:l}=await t(e.nodeExecutable,[c,"--version"],f(e,1e4));if(l.trim()!==r)throw new Error("verification_failed");return await t(e.npmExecutable,["ls","-g",`shennian@${r}`,"--omit=dev","--depth=0","--prefix",e.npmPrefix],f(e,3e4)),c}async function g(e,r,t){if(e.isolatedDaemonStart){await t(e.nodeExecutable,[r,"start","--json"],f(e,3e4));return}try{if(e.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():k.userInfo().uid;await t("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],f(e,15e3));return}if(e.platform==="linux"){await t("systemctl",["--user","restart","shennian"],f(e,15e3));return}if(e.platform==="win32"){await t("schtasks",["/Run","/TN","ShennianAgent"],f(e,15e3));return}}catch{}await t(e.nodeExecutable,[r,"start","--json"],f(e,3e4))}function T(e){return(r,t={})=>x(e.resultPath,{protocolVersion:_,jobId:e.jobId,state:r,fromVersion:e.fromVersion,targetVersion:e.targetVersion,operation:e.operation,...t,updatedAt:new Date().toISOString()})}function L(e,r){const t=o.dirname(o.dirname(e)),n=o.join(t,"updater.lock");a.mkdirSync(t,{recursive:!0,mode:448});try{const c=a.openSync(n,"wx",384);a.writeFileSync(c,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(c)}catch(c){if(c.code!=="EEXIST")throw c;try{const i=JSON.parse(a.readFileSync(n,"utf8"));if(i.jobId!==r.jobId&&i.pid&&h(i.pid)&&Date.now()-Number(i.createdAt)<E)throw new Error("upgrade_locked")}catch(i){if(i instanceof Error&&i.message==="upgrade_locked")throw i}a.rmSync(n,{force:!0});const l=a.openSync(n,"wx",384);a.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),a.closeSync(l)}return()=>{try{a.unlinkSync(n)}catch{}}}async function $(e,r={}){let t;try{t=N(e)}catch{throw new Error("invalid_job")}if((r.now??Date.now)()-Date.parse(t.createdAt)>E)throw new Error("job_expired");const n=o.dirname(o.dirname(o.resolve(e)));if(o.basename(o.dirname(o.resolve(e)))!=="jobs"||o.basename(o.resolve(e))!==`${t.jobId}.json`)throw new Error("invalid_job");if(o.dirname(o.resolve(t.resultPath))!==o.join(n,"results"))throw new Error("invalid_job");if(o.basename(o.resolve(t.resultPath))!==`${t.jobId}.json`)throw new Error("invalid_job");const c=o.dirname(o.dirname(n));if(o.resolve(t.attemptPath)!==o.join(c,"upgrade-attempt.json"))throw new Error("invalid_job");const l=L(e,t),i=T(t),d=r.execFile??S;let w=!1;try{let s=null;try{s=JSON.parse(a.readFileSync(t.resultPath,"utf8"))}catch{}if(s?.jobId===t.jobId&&s.targetVersion===t.targetVersion&&["healthy","rolled_back","failed"].includes(s.state??""))return;if(s?.jobId===t.jobId&&s.state==="starting"){const m=await y(t,t.targetVersion,d);await g(t,m,d);return}i("waiting_for_daemon"),await(r.sleep??(m=>new Promise(b=>setTimeout(b,m))))(250),await O(t,r),i("installing"),w=!0;try{await v(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"npm_install_failed"})}i("verifying");let u;try{u=await y(t,t.targetVersion,d)}catch(m){throw Object.assign(new Error(p(m)),{code:"verification_failed"})}x(t.attemptPath,{from:t.fromVersion,to:t.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:t.jobId,operation:t.operation}),i("starting"),await g(t,u,d)}catch(s){const u=s.code??(s instanceof Error?s.message:"internal_error");if(!w){i("failed",{errorCode:u,errorSummary:p(s)});return}try{await v(t,t.fromVersion,d);const m=await y(t,t.fromVersion,d);await g(t,m,d),i("rolled_back",{effectiveVersion:t.fromVersion,errorCode:u,errorSummary:p(s)})}catch(m){throw i("failed",{errorCode:"rollback_failed",errorSummary:p(m)}),m}}finally{l()}}function I(e){try{return a.realpathSync.native(e)}catch{return o.resolve(e)}}const j=process.argv[1]?I(process.argv[1]):"",J=I(V(import.meta.url));if(j===J){const e=process.argv[2];e?$(e).catch(()=>{process.exitCode=1}):process.exitCode=2}export{$ as runUpdaterJob};
|
|
1
|
+
import o from"node:fs";import D from"node:os";import i from"node:path";import{execFile as U}from"node:child_process";import{promisify as Z}from"node:util";import{fileURLToPath as H,pathToFileURL as K}from"node:url";const j=Z(U),L=1,B="https://registry.npmjs.org",W=30*6e4;function A(t){return(t instanceof Error?t.message:String(t)).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 k(t,r){o.mkdirSync(i.dirname(t),{recursive:!0,mode:448});const e=`${t}.tmp-${process.pid}-${Date.now()}`;try{o.writeFileSync(e,`${JSON.stringify(r,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"});const n=o.openSync(e,process.platform==="win32"?"r+":"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}if(o.renameSync(e,t),process.platform!=="win32"){const s=o.openSync(i.dirname(t),"r");try{o.fsyncSync(s)}finally{o.closeSync(s)}}}finally{try{o.unlinkSync(e)}catch{}}}function O(t){const r=o.lstatSync(t);if(r.isSymbolicLink()||!r.isFile())throw new Error("invalid_job");const e=JSON.parse(o.readFileSync(t,"utf8")),n=w=>{if(typeof e[w]!="string"||!String(e[w]).trim())throw new Error("invalid_job");return String(e[w])},s=/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/,l=n("fromVersion"),c=n("targetVersion");if(e.protocolVersion!==L||e.packageName!=="shennian")throw new Error("invalid_job");if(!s.test(l)||!s.test(c))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 f=e.oldDaemonPid==null?null:Number(e.oldDaemonPid),a=Number(e.daemonExitTimeoutMs),u=Number(e.commandTimeoutMs);if(f!=null&&(!Number.isSafeInteger(f)||f<1))throw new Error("invalid_job");if(!Number.isSafeInteger(a)||a<1e3)throw new Error("invalid_job");if(!Number.isSafeInteger(u)||u<1e4)throw new Error("invalid_job");return{protocolVersion:1,jobId:n("jobId"),fromVersion:l,targetVersion:c,npmPrefix:n("npmPrefix"),npmExecutable:n("npmExecutable"),nodeExecutable:n("nodeExecutable"),packageName:"shennian",operation:e.operation==="rollback"?"rollback":"upgrade",oldDaemonPid:f,platform:n("platform"),createdAt:n("createdAt"),daemonExitTimeoutMs:a,commandTimeoutMs:u,resultPath:n("resultPath"),attemptPath:n("attemptPath"),isolatedDaemonStart:e.isolatedDaemonStart===!0,...typeof e.rollbackJobId=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(e.rollbackJobId)?{rollbackJobId:e.rollbackJobId}:{}}}function T(t,r){return r==="win32"?i.win32.join(t,"node_modules","shennian"):i.posix.join(t,"lib","node_modules","shennian")}function x(t){const r=T(t.npmPrefix,t.platform),e=i.join(i.dirname(r),`.shennian-upgrade-${t.rollbackJobId??t.jobId}`),n=i.join(e,"candidate");return{active:r,transaction:e,prefix:n,candidate:T(n,t.platform),previous:i.join(e,"previous"),rejected:i.join(e,"rejected"),marker:i.join(e,"transaction.json")}}function p(t){try{return JSON.parse(o.readFileSync(t,"utf8"))}catch{return null}}function N(t){const r=o.lstatSync(t);if(!r.isDirectory()||r.isSymbolicLink())throw new Error("unsafe_installation")}function y(t,r){N(t);const e=o.realpathSync(t),n=a=>{const u=i.relative(e,o.realpathSync(a));if(u.startsWith(".."+i.sep)||u===".."||i.isAbsolute(u))throw new Error("external_package_dependency")},s=a=>{for(const u of o.readdirSync(a,{withFileTypes:!0})){const w=i.join(a,u.name);u.isSymbolicLink()?n(w):u.isDirectory()&&s(w)}};s(t);const l=p(i.join(t,"package.json"));if(l?.name!=="shennian"||l.version!==r||l.bin?.shennian!=="dist/bin/shennian.js")throw new Error("verification_failed");n(i.join(t,"dist","bin","shennian.js"));const c=new Set,f=a=>{const u=o.realpathSync(a);if(c.has(u))return;c.add(u);const w=p(i.join(a,"package.json"));if(!w)throw new Error("incomplete_package");for(const v of Object.keys(w.dependencies??{})){if(Object.hasOwn(w.optionalDependencies??{},v))continue;if(!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i.test(v))throw new Error("invalid_dependency");let g=a,_=!1;for(;g===t||g.startsWith(t+i.sep);){const S=i.join(g,"node_modules",v);if(o.existsSync(i.join(S,"package.json"))){n(S),f(S),_=!0;break}g=i.dirname(g)}if(!_)throw new Error("incomplete_package")}};f(t)}function ue(t){const r=p(i.join(t,"updater.lock"));if(J(r))throw new Error("upgrade_locked")}async function X(t,r={}){const e=x(t);N(i.dirname(e.active)),y(e.active,t.fromVersion),o.mkdirSync(e.transaction,{mode:448});const n=r.execFile??j;try{await ee({...t,npmPrefix:e.prefix},t.targetVersion,n),await q(t,r)}catch(s){throw o.rmSync(e.transaction,{recursive:!0,force:!0}),Object.assign(new Error(A(s)),{code:"candidate_preparation_failed"})}}async function q(t,r={}){const e=x(t);N(e.transaction);const n=p(e.marker);if(n&&n.phase!=="prepared")throw new Error("transaction_already_started");y(e.active,t.fromVersion),y(e.candidate,t.targetVersion);const s=r.execFile??j;await E({...t,npmPrefix:e.prefix},t.targetVersion,s),await s(t.npmExecutable,["ls","-g",`shennian@${t.targetVersion}`,"--all","--omit=dev","--offline","--depth=100","--prefix",e.prefix],h(t,3e4)),k(e.marker,{jobId:t.jobId,fromVersion:t.fromVersion,targetVersion:t.targetVersion,phase:"prepared"})}function F(t,r){if(o.renameSync(t,r),process.platform!=="win32")for(const e of new Set([i.dirname(t),i.dirname(r)])){const n=o.openSync(e,"r");try{o.fsyncSync(n)}finally{o.closeSync(n)}}}function Y(t){const r=x(t),e=i.dirname(t.resultPath);for(const n of o.readdirSync(i.dirname(r.active),{withFileTypes:!0})){if(!n.isDirectory()||!/^\.shennian-upgrade-[A-Za-z0-9_-]+$/.test(n.name))continue;const s=i.join(i.dirname(r.active),n.name);if(s===r.transaction)continue;const l=p(i.join(s,"transaction.json"));if(!l?.jobId||n.name!==`.shennian-upgrade-${l.jobId}`)continue;const c=p(i.join(e,`${l.jobId}.json`));c?.jobId===l.jobId&&["healthy","rolled_back"].includes(c.state??"")&&o.rmSync(s,{recursive:!0,force:!0})}}function Q(t){const r=i.join(i.dirname(t.attemptPath),"upgrade-failures.json");let e={versions:{}};try{e=JSON.parse(o.readFileSync(r,"utf8"))}catch{}const n=e.versions?.[t.targetVersion],s=Math.max(0,Number(n?.failureCount)||0)+1,l=Date.now(),c=[5*6e4,30*6e4,120*6e4,360*6e4];k(r,{versions:{...e.versions,[t.targetVersion]:{version:t.targetVersion,failureCount:s,firstFailedAt:n?.firstFailedAt??l,lastFailedAt:l,nextRetryAt:l+c[Math.min(s-1,c.length-1)],error:"upgrade_rolled_back"}}})}function b(t,r){const e=x(t),n=p(e.marker);if(!n||n.jobId!==(t.rollbackJobId??t.jobId))throw new Error("invalid_transaction");k(e.marker,{...n,phase:r})}function J(t){const r=Date.now()-D.uptime()*1e3;return!!(t?.pid&&(t.createdAt===void 0||t.createdAt>=r-2e3)&&I(t.pid))}function I(t){try{return process.kill(t,0),!0}catch{return!1}}async function $(t,r){if(r.serviceRecovery||!t.oldDaemonPid||!(r.isAlive??I)(t.oldDaemonPid))return;const e=r.kill??((l,c)=>process.kill(l,c)),n=r.sleep??(l=>new Promise(c=>setTimeout(c,l)));e(t.oldDaemonPid,"SIGTERM");const s=(r.now??Date.now)()+t.daemonExitTimeoutMs;for(;(r.now??Date.now)()<s;){if(!(r.isAlive??I)(t.oldDaemonPid))return;await n(100)}if(r.kill)e(t.oldDaemonPid,"SIGKILL");else if(t.platform==="win32")try{await(r.execFile??j)("taskkill",["/PID",String(t.oldDaemonPid),"/T","/F"],{timeout:15e3,windowsHide:!0,shell:!0})}catch{}else try{process.kill(-t.oldDaemonPid,"SIGKILL")}catch{try{process.kill(t.oldDaemonPid,"SIGKILL")}catch{}}if(await n(250),(r.isAlive??I)(t.oldDaemonPid))throw new Error("daemon_exit_timeout")}function h(t,r=t.commandTimeoutMs){return{timeout:r,windowsHide:!0,shell:t.platform==="win32"}}async function ee(t,r,e){const n=["install","-g",`shennian@${r}`,"--prefix",t.npmPrefix];try{await e(t.npmExecutable,n,h(t))}catch{await e(t.npmExecutable,[...n,"--registry",B],h(t))}}async function E(t,r,e){const n=T(t.npmPrefix,t.platform),s=i.join(n,"dist","bin","shennian.js"),{stdout:l}=await e(t.nodeExecutable,[s,"--version"],h(t,1e4));if(l.trim()!==r)throw new Error("verification_failed");return s}async function P(t,r,e){if(t.isolatedDaemonStart){await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4));return}try{if(t.platform==="darwin"){const n=typeof process.getuid=="function"?process.getuid():D.userInfo().uid;try{await e("launchctl",["bootstrap",`gui/${n}`,i.join(D.homedir(),"Library/LaunchAgents/com.shennian.agent.plist")],h(t,15e3))}catch{}await e("launchctl",["kickstart","-k",`gui/${n}/com.shennian.agent`],h(t,15e3));return}if(t.platform==="linux"){await e("systemctl",["--user","restart","shennian"],h(t,15e3));return}if(t.platform==="win32"){await e("schtasks",["/Run","/TN","ShennianAgent"],h(t,15e3));return}}catch{}await e(t.nodeExecutable,[r,"start","--json"],h(t,3e4))}async function C(t,r){if(!t.isolatedDaemonStart)try{t.platform==="darwin"?await r("launchctl",["bootout",`gui/${process.getuid?.()??D.userInfo().uid}/com.shennian.agent`],h(t,15e3)):t.platform==="linux"?await r("systemctl",["--user","stop","shennian"],h(t,15e3)):t.platform==="win32"&&await r("schtasks",["/End","/TN","ShennianAgent"],h(t,15e3))}catch{}}function te(t){return(r,e={})=>k(t.resultPath,{protocolVersion:L,jobId:t.jobId,state:r,fromVersion:t.fromVersion,targetVersion:t.targetVersion,operation:t.operation,...e,updatedAt:new Date().toISOString()})}function re(t,r){const e=i.dirname(i.dirname(t)),n=i.join(e,"updater.lock");o.mkdirSync(e,{recursive:!0,mode:448});try{const s=o.openSync(n,"wx",384);o.writeFileSync(s,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),o.closeSync(s)}catch(s){if(s.code!=="EEXIST")throw s;try{const c=JSON.parse(o.readFileSync(n,"utf8"));if((c.jobId!==r.jobId||c.pid!==r.oldDaemonPid)&&J(c))throw new Error("upgrade_locked")}catch(c){if(c instanceof Error&&c.message==="upgrade_locked")throw c}o.rmSync(n,{force:!0});const l=o.openSync(n,"wx",384);o.writeFileSync(l,JSON.stringify({jobId:r.jobId,pid:process.pid,createdAt:Date.now()})),o.closeSync(l)}return()=>{try{o.unlinkSync(n)}catch{}}}async function M(t,r={}){let e;try{e=O(t)}catch{throw new Error("invalid_job")}if(!r.serviceRecovery&&(r.now??Date.now)()-Date.parse(e.createdAt)>W)throw new Error("job_expired");const n=i.dirname(i.dirname(i.resolve(t)));if(i.basename(i.dirname(i.resolve(t)))!=="jobs"||i.basename(i.resolve(t))!==`${e.jobId}.json`)throw new Error("invalid_job");if(i.dirname(i.resolve(e.resultPath))!==i.join(n,"results"))throw new Error("invalid_job");if(i.basename(i.resolve(e.resultPath))!==`${e.jobId}.json`)throw new Error("invalid_job");const s=i.dirname(i.dirname(n));if(i.resolve(e.attemptPath)!==i.join(s,"upgrade-attempt.json"))throw new Error("invalid_job");const l=re(t,e),c=te(e),f=r.execFile??j,a=x(e),u=i.join(n,"active-job.json"),w=r.sleep??(d=>new Promise(m=>setTimeout(m,d)));let v=!1,g={};const _=d=>{d&&Q(e),k(e.attemptPath,{from:d?e.targetVersion:e.fromVersion,to:d?e.fromVersion:e.targetVersion,attemptCount:0,attemptAt:Date.now(),jobId:e.jobId,operation:d?"rollback":"upgrade"}),c("starting",d?{operation:"rollback",...g,effectiveVersion:e.fromVersion,targetVersion:e.fromVersion,fromVersion:e.targetVersion}:{})},S=async d=>{if(r.serviceRecovery)return;const m=(r.now??Date.now)()+(r.healthTimeoutMs??9e4);for(;(r.now??Date.now)()<m;){if(p(e.resultPath)?.state===(d?"rolled_back":"healthy"))return;await w(250)}throw new Error("daemon_health_timeout")},G=async()=>{const d=p(i.join(s,"daemon.pid"));d?.version===e.targetVersion&&Number.isSafeInteger(d.pid)&&typeof d.pid=="number"&&d.pid!==process.pid&&await $({...e,oldDaemonPid:d.pid},r)},R=async()=>{if(b(e,"rolling_back"),r.serviceRecovery||await C(e,f),await G(),!o.existsSync(a.previous))y(a.active,e.fromVersion);else{if(y(a.previous,e.fromVersion),o.existsSync(a.active)){if(o.existsSync(a.rejected))throw new Error("rollback_destination_exists");F(a.active,a.rejected)}F(a.previous,a.active)}b(e,"restored"),y(a.active,e.fromVersion);const d=await E(e,e.fromVersion,f);_(!0),r.serviceRecovery||await P(e,d,f),await S(!0)};try{const d=p(e.resultPath);if(d?.jobId===e.jobId&&["healthy","rolled_back","failed"].includes(d.state??""))return;const m=p(a.marker);if(m&&(m.jobId!==(e.rollbackJobId??e.jobId)||m.fromVersion!==(e.operation==="rollback"?e.targetVersion:e.fromVersion)))throw new Error("invalid_transaction");if(e.operation==="rollback"){e={...e,fromVersion:e.targetVersion,targetVersion:e.fromVersion},v=!0,k(u,{jobPath:t}),await $(e,r),await R();return}if(m&&m.phase!=="prepared"){v=!0,m.phase==="restored"||m.phase==="rolling_back"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),_(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await S(!0)):m.phase==="switching"&&!o.existsSync(a.previous)?(y(a.active,e.fromVersion),b(e,"restored"),_(!0),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f),await S(!0)):await R();return}m||(c("installing"),await X(e,r)),y(a.candidate,e.targetVersion),y(a.active,e.fromVersion),c("waiting_for_daemon"),await w(250),k(u,{jobPath:t}),r.serviceRecovery||await C(e,f),await $(e,r),b(e,"switching"),v=!0,F(a.active,a.previous),F(a.candidate,a.active),c("verifying"),y(a.active,e.targetVersion);const V=await E(e,e.targetVersion,f);b(e,"starting"),_(!1),r.serviceRecovery||await P(e,V,f),await S(!1)}catch(d){const m=d.code??(d instanceof Error?d.message:"internal_error");if(!v){c("failed",{errorCode:m,errorSummary:A(d)}),(!e.oldDaemonPid||!(r.isAlive??I)(e.oldDaemonPid))&&(y(a.active,e.fromVersion),r.serviceRecovery||await P(e,await E(e,e.fromVersion,f),f));return}g={errorCode:m,errorSummary:A(d)};try{await R()}catch(V){throw c("failed",{errorCode:"rollback_failed",errorSummary:A(V)}),V}}finally{const d=p(e.resultPath);if(["healthy","rolled_back"].includes(d?.state??""))try{Y(e)}catch{}l()}}async function ne(t){const r=p(i.join(t,"active-job.json"));if(!r?.jobPath)return;const e=i.resolve(r.jobPath);if(i.dirname(e)!==i.join(i.resolve(t),"jobs"))throw new Error("invalid_job");const n=O(e);for(;;){const s=p(n.resultPath);if(["healthy","rolled_back","failed"].includes(s?.state??""))return;const l=p(i.join(t,"updater.lock")),c=l?.pid!==process.pid&&J(l),f=p(x(n).marker);if(c){if(s?.state==="starting"&&["starting","restored"].includes(f?.phase??""))return;await new Promise(a=>setTimeout(a,250));continue}await M(e,{serviceRecovery:!0});return}}function z(t){try{return o.realpathSync.native(t)}catch{return i.resolve(t)}}const ie=process.argv[1]?z(process.argv[1]):"",oe=z(H(import.meta.url));if(ie===oe)if(process.argv[2]==="--service"){const[,,,t,r,...e]=process.argv;ne(t).then(async()=>{process.argv=[process.execPath,r,...e],await import(K(r).href)}).catch(()=>{process.exitCode=1})}else{const t=process.argv[2];t?M(t).catch(()=>{process.exitCode=1}):process.exitCode=2}export{ue as assertUpdaterAvailable,q as finalizeUpdaterCandidate,X as prepareUpdaterCandidate,ne as recoverServiceEntry,M as runUpdaterJob,x as updaterPaths,y as verifyLocalPackage};
|
|
@@ -271,6 +271,8 @@ export type NianSession = {
|
|
|
271
271
|
id: string;
|
|
272
272
|
/** Device-local lifecycle only; never sent as canonical session metadata. */
|
|
273
273
|
localCreationState?: 'draft' | 'created';
|
|
274
|
+
/** Client-local user-message sort watermark; not canonical server metadata. */
|
|
275
|
+
lastUserMessageAt?: string | null;
|
|
274
276
|
agentIdentityId?: string | null;
|
|
275
277
|
machineId: string;
|
|
276
278
|
userId: string;
|