shennian 0.4.11 → 0.4.13

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.
Files changed (122) hide show
  1. package/dist/publish-build-manifest.json +183 -53
  2. package/dist/src/agents/acp/adapter.d.ts +78 -0
  3. package/dist/src/agents/acp/adapter.js +3 -0
  4. package/dist/src/agents/acp/auth-state.d.ts +3 -0
  5. package/dist/src/agents/acp/auth-state.js +1 -0
  6. package/dist/src/agents/acp/auth.d.ts +7 -0
  7. package/dist/src/agents/acp/auth.js +1 -0
  8. package/dist/src/agents/acp/capability-cache.d.ts +14 -0
  9. package/dist/src/agents/acp/capability-cache.js +2 -0
  10. package/dist/src/agents/acp/catalog.d.ts +11 -0
  11. package/dist/src/agents/acp/catalog.js +2 -0
  12. package/dist/src/agents/acp/client-services.d.ts +32 -0
  13. package/dist/src/agents/acp/client-services.js +3 -0
  14. package/dist/src/agents/acp/discovery.d.ts +9 -0
  15. package/dist/src/agents/acp/discovery.js +1 -0
  16. package/dist/src/agents/acp/events.d.ts +4 -0
  17. package/dist/src/agents/acp/events.js +3 -0
  18. package/dist/src/agents/acp/manifests.d.ts +63 -0
  19. package/dist/src/agents/acp/manifests.js +2 -0
  20. package/dist/src/agents/acp/metrics.d.ts +24 -0
  21. package/dist/src/agents/acp/metrics.js +1 -0
  22. package/dist/src/agents/acp/package-commands.d.ts +4 -0
  23. package/dist/src/agents/acp/package-commands.js +1 -0
  24. package/dist/src/agents/acp/process-client.d.ts +39 -0
  25. package/dist/src/agents/acp/process-client.js +1 -0
  26. package/dist/src/agents/acp/process-tree.d.ts +2 -0
  27. package/dist/src/agents/acp/process-tree.js +1 -0
  28. package/dist/src/agents/acp/types.d.ts +31 -0
  29. package/dist/src/agents/acp/types.js +1 -0
  30. package/dist/src/agents/acp/watcher.d.ts +22 -0
  31. package/dist/src/agents/acp/watcher.js +1 -0
  32. package/dist/src/agents/adapter.d.ts +96 -2
  33. package/dist/src/agents/adapter.js +1 -1
  34. package/dist/src/agents/claude.d.ts +14 -0
  35. package/dist/src/agents/claude.js +5 -5
  36. package/dist/src/agents/codex.d.ts +6 -0
  37. package/dist/src/agents/codex.js +1 -1
  38. package/dist/src/agents/command-spec.d.ts +5 -0
  39. package/dist/src/agents/command-spec.js +3 -3
  40. package/dist/src/agents/cursor.d.ts +6 -0
  41. package/dist/src/agents/cursor.js +1 -1
  42. package/dist/src/agents/custom-recovery-conformance.d.ts +2 -0
  43. package/dist/src/agents/custom-recovery-conformance.js +2 -0
  44. package/dist/src/agents/custom.d.ts +9 -0
  45. package/dist/src/agents/custom.js +4 -4
  46. package/dist/src/agents/detect.d.ts +11 -2
  47. package/dist/src/agents/detect.js +1 -1
  48. package/dist/src/agents/gemini.d.ts +6 -0
  49. package/dist/src/agents/gemini.js +1 -1
  50. package/dist/src/agents/manager.d.ts +2 -0
  51. package/dist/src/agents/manager.js +5 -5
  52. package/dist/src/agents/model-registry/service.js +1 -1
  53. package/dist/src/agents/opencode.d.ts +6 -0
  54. package/dist/src/agents/opencode.js +1 -1
  55. package/dist/src/agents/pi-coding-agent.d.ts +6 -0
  56. package/dist/src/agents/pi-coding-agent.js +4 -4
  57. package/dist/src/agents/workbuddy.d.ts +6 -0
  58. package/dist/src/agents/workbuddy.js +2 -2
  59. package/dist/src/commands/agent.d.ts +2 -0
  60. package/dist/src/commands/agent.js +2 -0
  61. package/dist/src/commands/daemon.d.ts +6 -2
  62. package/dist/src/commands/daemon.js +19 -23
  63. package/dist/src/commands/pair.js +10 -10
  64. package/dist/src/commands/upgrade.d.ts +7 -1
  65. package/dist/src/commands/upgrade.js +1 -1
  66. package/dist/src/config/index.d.ts +1 -1
  67. package/dist/src/config/index.js +2 -2
  68. package/dist/src/daemon-ipc/control.d.ts +16 -0
  69. package/dist/src/daemon-ipc/control.js +1 -0
  70. package/dist/src/index.js +3 -3
  71. package/dist/src/log-reporter.d.ts +23 -0
  72. package/dist/src/log-reporter.js +1 -1
  73. package/dist/src/relay/client.d.ts +1 -0
  74. package/dist/src/relay/client.js +1 -1
  75. package/dist/src/session/handlers/agents.d.ts +1 -0
  76. package/dist/src/session/handlers/agents.js +1 -1
  77. package/dist/src/session/handlers/chat.d.ts +3 -0
  78. package/dist/src/session/handlers/chat.js +2 -2
  79. package/dist/src/session/manager.d.ts +20 -2
  80. package/dist/src/session/manager.js +3 -1
  81. package/dist/src/session/queue.js +2 -2
  82. package/dist/src/session/run-recovery/barrier.d.ts +12 -0
  83. package/dist/src/session/run-recovery/barrier.js +1 -0
  84. package/dist/src/session/run-recovery/coordinator.d.ts +32 -0
  85. package/dist/src/session/run-recovery/coordinator.js +1 -0
  86. package/dist/src/session/run-recovery/journal.d.ts +30 -0
  87. package/dist/src/session/run-recovery/journal.js +2 -0
  88. package/dist/src/session/run-recovery/schema.d.ts +43 -0
  89. package/dist/src/session/run-recovery/schema.js +1 -0
  90. package/dist/src/session/types.d.ts +10 -0
  91. package/dist/src/upgrade/engine.d.ts +6 -5
  92. package/dist/src/upgrade/engine.js +2 -2
  93. package/dist/src/upgrade/scheduler.d.ts +14 -0
  94. package/dist/src/upgrade/scheduler.js +1 -1
  95. package/dist/src/upgrade/updater-bootstrap.d.ts +12 -0
  96. package/dist/src/upgrade/updater-bootstrap.js +2 -0
  97. package/dist/src/upgrade/updater-launcher.d.ts +28 -0
  98. package/dist/src/upgrade/updater-launcher.js +3 -0
  99. package/dist/src/upgrade/updater-protocol.d.ts +37 -0
  100. package/dist/src/upgrade/updater-protocol.js +1 -0
  101. package/dist/src/upgrade/updater-worker.d.ts +11 -0
  102. package/dist/src/upgrade/updater-worker.js +2 -0
  103. package/node_modules/@shennian/wire/dist/events.d.ts +31 -0
  104. package/node_modules/@shennian/wire/dist/index.d.ts +1 -0
  105. package/node_modules/@shennian/wire/dist/index.js +1 -0
  106. package/node_modules/@shennian/wire/dist/machine.d.ts +49 -1
  107. package/node_modules/@shennian/wire/dist/message-payload.d.ts +1 -0
  108. package/node_modules/@shennian/wire/dist/message-payload.js +4 -1
  109. package/node_modules/@shennian/wire/dist/methods.d.ts +1 -1
  110. package/node_modules/@shennian/wire/dist/relay-connectivity.d.ts +30 -0
  111. package/node_modules/@shennian/wire/dist/relay-connectivity.js +29 -0
  112. package/node_modules/@shennian/wire/dist/session-sync.d.ts +2 -2
  113. package/node_modules/@shennian/wire/dist/session-sync.js +2 -0
  114. package/node_modules/@shennian/wire/dist/session.d.ts +84 -1
  115. package/node_modules/@shennian/wire/dist/session.js +5 -1
  116. package/package.json +5 -3
  117. package/dist/native/macos-background-client/Shennian.app/Contents/CodeResources +0 -0
  118. package/dist/native/macos-background-client/Shennian.app/Contents/Info.plist +0 -17
  119. package/dist/native/macos-background-client/Shennian.app/Contents/MacOS/ShennianClientLauncher +0 -0
  120. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/en.lproj/InfoPlist.strings +0 -2
  121. package/dist/native/macos-background-client/Shennian.app/Contents/Resources/zh-Hans.lproj/InfoPlist.strings +0 -2
  122. package/dist/native/macos-background-client/Shennian.app/Contents/_CodeSignature/CodeResources +0 -153
@@ -0,0 +1,2 @@
1
+ import{createAgent as p}from"../agents/adapter.js";import{AcpAgentAdapter as u}from"../agents/acp/adapter.js";import{discoverAcpAgents as h}from"../agents/acp/discovery.js";import{callDaemonIpc as A}from"../daemon-ipc/client.js";import{readAcpManifests as f,removeAcpManifest as w,validateAcpId as s,writeAcpManifest as C}from"../agents/acp/manifests.js";function x(i){const o=i.command("agent").description("Manage local ACP Agent integrations");o.command("add <id>").description("Register an installed ACP v1 Agent (does not install it)").requiredOption("--name <name>","Display name").requiredOption("--command <command>","Absolute executable path or command name").option("--arg <args...>","Arguments passed to the ACP Agent").action((a,e)=>{try{const t=s(a);C({schemaVersion:1,id:t,name:e.name,protocol:"acp",command:e.command,args:e.arg??[],enabled:!0}),r(!0,"agent.add",{agentRef:`acp:${t}`,installedByShennian:!1})}catch(t){r(!1,"agent.add",void 0,t instanceof Error?t.message:String(t))}}),o.command("list").description("List locally registered ACP Agents").action(()=>{r(!0,"agent.list",{agents:f().map(a=>({agentRef:`acp:${a.id}`,name:a.name,command:a.launch.command,args:a.launch.args??[]}))})}),o.command("remove <id>").description("Remove a local ACP Agent registration (does not uninstall it)").action(a=>{try{const e=s(a),t=w(e);r(t,"agent.remove",t?{agentRef:`acp:${e}`}:void 0,t?void 0:`No local ACP Agent named "${e}" exists.`)}catch(e){r(!1,"agent.remove",void 0,e instanceof Error?e.message:String(e))}}),o.command("auth <id>").description("Authenticate an installed ACP Agent on this computer").option("--method <method>","Authentication method id").action(async(a,e)=>{let t=null;try{const n=s(a.replace(/^acp:/,""));if(!(await h()).some(d=>d.type===`acp:${n}`))throw new Error(`Installed ACP Agent "${n}" was not found.`);const m=p(`acp:${n}`);if(!(m instanceof u))throw new Error("ACP Agent adapter is unavailable");t=m,await t.start(`acp-auth-${process.pid}`,process.cwd());const c=t.getAuthMethods(),l=e.method?c.find(d=>d.id===e.method):c.length===1?c[0]:void 0;if(!l)throw new Error(c.length?`Choose an authentication method with --method: ${c.map(d=>d.id).join(", ")}`:"This ACP Agent did not advertise an authentication method.");await t.authenticate(l.id,{interactiveTerminal:!0});let g=!1;try{g=(await A({method:"agent.auth.completed",params:{agentType:`acp:${n}`}})).resumed}catch{}r(!0,"agent.auth",{agentRef:`acp:${n}`,authenticated:!0,resumed:g})}catch(n){r(!1,"agent.auth",void 0,n instanceof Error?n.message:String(n))}finally{await t?.stop().catch(()=>{})}})}function r(i,o,a,e){process.stdout.write(`${JSON.stringify(i?{ok:i,command:o,result:a}:{ok:i,command:o,error:"acp_agent_manifest_error",message:e,nextAction:null})}
2
+ `),i||(process.exitCode=1)}export{x as registerAgentCommand};
@@ -38,6 +38,7 @@ export declare function resolveServiceLaunchSpec(input: {
38
38
  scriptPath: string;
39
39
  shennianCommandPath?: string | null;
40
40
  npxPath?: string | null;
41
+ desktopBridgePath?: string | null;
41
42
  }): ServiceLaunchSpec;
42
43
  export declare function isRemoteAccessDisabled(): boolean;
43
44
  export declare function clearRemoteAccessDisabled(): void;
@@ -90,8 +91,9 @@ export declare function buildDaemonSpawnOptions(launch: DetachedLaunchSpec, logF
90
91
  export declare function captureEnvForService(): Record<string, string>;
91
92
  export declare function buildMacLaunchdPlist(input?: {
92
93
  launch?: ServiceLaunchSpec;
93
- brandedLauncherPath?: string | null;
94
94
  }): string;
95
+ export declare function removeLegacyMacBackgroundApp(appPath?: string): void;
96
+ export declare function getMacBackgroundItemNotice(platform?: Platform): string | null;
95
97
  export declare function buildSystemdUnit(): string;
96
98
  type LaunchdCommandExecutor = (command: string) => void;
97
99
  export declare function getLaunchdServiceTarget(uid?: number | string): string;
@@ -117,5 +119,7 @@ export declare function canReuseRunningDaemonForStart(status: Pick<DaemonStatus,
117
119
  * Install the platform-native auto-start service.
118
120
  * Returns true if the service was immediately started (so caller can skip startDaemonProcess).
119
121
  */
120
- export declare function installService(): boolean;
122
+ export declare function installService(options?: {
123
+ quiet?: boolean;
124
+ }): boolean;
121
125
  export declare function registerDaemonCommand(program: Command): void;
@@ -1,24 +1,20 @@
1
- import c from"chalk";import i from"node:fs";import s from"node:path";import d from"node:os";import{execSync as u,spawn as he,spawnSync as ye}from"node:child_process";import{randomUUID as V}from"node:crypto";import{fileURLToPath as ge}from"node:url";import{getShennianDir as we,loadConfig as G,resolveShennianPath as y,saveConfig as ve}from"../config/index.js";import{buildAugmentedPath as Ee}from"../env-path.js";import{buildWindowsLauncherCommand as Ae,buildWindowsScheduledTaskCommands as Pe,buildWindowsScheduledTaskXml as ke,buildWindowsStartupVbs as De,escapeXml as T,isWindowsCmdScript as Ie,quoteCmdArg as J,quoteSystemdArg as Ne,suspendWindowsScheduledTask as be}from"./daemon-windows.js";import{waitForDaemonReadyStatus as $}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as Re,isShennianRunServiceCommand as xe}from"./daemon-process.js";import{getCurrentVersion as K}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as _n,buildWindowsScheduledTaskCommands as On,buildWindowsScheduledTaskSuspendCommands as Tn,buildWindowsScheduledTaskXml as $n,buildWindowsStartupVbs as Cn,suspendWindowsScheduledTask as Hn}from"./daemon-windows.js";import{isShennianRunServiceCommand as Un}from"./daemon-process.js";const X=s.dirname(ge(import.meta.url)),w=we(),v=y("daemon.pid"),p=y("daemon.log"),C=y("remote-access.disabled"),x=y("daemon-launcher.json"),q=s.resolve(X,"../../bin/shennian.js"),Y=process.execPath,z=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_ACCOUNT_NAMESPACE_ID","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function Le(e){return z.has(e)}function m(){const e=d.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function H(e){const n=e.replace(/\\/g,"/").toLowerCase(),t=d.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(t.endsWith("/")?t:`${t}/`)}function _e(e){return i.existsSync(e.scriptPath)&&!H(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!H(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function Oe(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const r=JSON.parse(n),o=Number(r.pid);return!Number.isInteger(o)||o<=0?null:{pid:o,...typeof r.instanceId=="string"?{instanceId:r.instanceId}:{},...typeof r.version=="string"?{version:r.version}:{},...r.launcher==="desktop-managed"||r.launcher==="global-cli"||r.launcher==="unknown"?{launcher:r.launcher}:{},...typeof r.startedAt=="string"?{startedAt:r.startedAt}:{},...r.adopted===!0?{adopted:!0}:{}}}catch{return null}const t=parseInt(n,10);return isNaN(t)?null:{pid:t}}function D(){try{return Oe(i.readFileSync(v,"utf-8"))}catch{return null}}function A(e){try{return process.kill(e,0),!0}catch{return!1}}function Q(e,n=3e3){const t=Date.now();for(;Date.now()-t<n;){if(!A(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!A(e)}function Te(){return i.existsSync(C)}function $e(){try{i.unlinkSync(C)}catch{}}function Ce(e,n){const t=s.dirname(e),r=`${e}.${process.pid}.${V()}.tmp`;i.mkdirSync(t,{recursive:!0});let o=null;try{o=i.openSync(r,"w",384),i.writeSync(o,n,void 0,"utf8"),i.fsyncSync(o),i.closeSync(o),o=null,i.renameSync(r,e);try{const a=i.openSync(t,"r");try{i.fsyncSync(a)}finally{i.closeSync(a)}}catch{}}finally{o!==null&&i.closeSync(o);try{i.unlinkSync(r)}catch{}}}function He(e={}){const n=e.disabledAt??new Date().toISOString(),t=e.markerFile??C,r=e.platform??m(),o=e.servicePresent??W(r),a=e.execCommand??(l=>{u(l,{stdio:"pipe",windowsHide:!0})});if(Ce(t,n),!o)return{disabledAt:n,autoStartDisabled:!0};try{return a(r==="darwin"?`launchctl disable "${k()}"`:r==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${b}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(l){return{disabledAt:n,autoStartDisabled:!1,autoStartError:l instanceof Error?l.message:String(l)}}}function Me(e={}){const n=e.platform??m();if(!(e.servicePresent??W(n)))return{serviceStopped:!0};const r=e.execCommand??(o=>{u(o,{stdio:"pipe",windowsHide:!0})});try{return r(n==="darwin"?`launchctl bootout "${k()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${b}"`),{serviceStopped:!0}}catch(o){return{serviceStopped:!1,error:o instanceof Error?o.message:String(o)}}}function Z(){return"global-cli"}function Ue(){return`${process.pid}-${Date.now()}-${V()}`}function je(e,n,t={}){i.mkdirSync(w,{recursive:!0}),i.writeFileSync(v,JSON.stringify({pid:e,instanceId:n,...t.version?{version:t.version}:{},launcher:t.launcher??Z(),...t.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function bn(e,n){const t=D();if(!(t?.pid!==e||t.instanceId!==n))try{i.unlinkSync(v)}catch{}}function ee(e,n=Z(),t){i.mkdirSync(w,{recursive:!0}),i.writeFileSync(x,JSON.stringify({pid:e,launcher:n,...t?{instanceId:t}:{},updatedAt:new Date().toISOString()},null,2))}function We(e){if(e)try{if(JSON.parse(i.readFileSync(x,"utf-8")).instanceId!==e)return}catch{return}try{i.unlinkSync(x)}catch{}}function Fe(e){e&&ee(e)}function Be(e,n){if(!e||!n)return"unknown";try{const t=JSON.parse(i.readFileSync(x,"utf-8"));if(t.pid!==e)return"unknown";if(t.launcher==="desktop-managed"||t.launcher==="global-cli")return t.launcher}catch{}return M(e)}function M(e){if(m()==="win32")return"unknown";try{const n=u(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function ne(e=process.pid){if(m()==="win32")return Re(e);try{return u("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(t=>{const r=t.trim().match(/^(\d+)\s+(.+)$/);if(!r)return null;const o=Number(r[1]),a=r[2];return!Number.isInteger(o)||o===e?null:xe(a)?o:null}).filter(t=>typeof t=="number")}catch{return[]}}function Ve(e=process.env,n=w,t=d.tmpdir(),r=s.join(d.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||s.resolve(n)===s.resolve(r))return!1;const o=s.relative(s.resolve(t),s.resolve(n));return o!==""&&o!==".."&&!o.startsWith(`..${s.sep}`)&&!s.isAbsolute(o)||s.resolve(n)===s.resolve(e.SHENNIAN_HOME)}function Ge(e=process.env,n=w){return!Ve(e,n)}function S(e={}){let n=D(),t=n?.pid??null,r=t!==null&&A(t),o=t!==null&&!r,a=n?.adopted===!0;const l=G();if(o&&e.cleanupStale){try{i.unlinkSync(v)}catch{}n=null,t=null,r=!1,o=!1}if(!r&&Ge()){const f=ne()[0];if(f&&(t=f,r=!0,o=!1,a=!0,e.cleanupStale)){const R=Ue();je(f,R,{launcher:M(f),adopted:!0}),ee(f,M(f)),n={pid:f,instanceId:R,adopted:!0}}}return{running:r,pid:t,stale:o,remoteAccessDisabled:Te(),launcher:Be(t,r),platform:m(),shennianDir:w,pidFile:v,logFile:p,...n?.instanceId?{instanceId:n.instanceId}:{},...a?{adopted:!0}:{},...l.machineId?{machineId:l.machineId}:{},paired:!!(l.machineToken&&l.machineId),...l.serverUrl?{serverUrl:l.serverUrl}:{}}}function E(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
- `)}function te(e){const n=e?.trim();if(!n)return;const t=G();t.serverUrl=n,ve(t)}function re(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const U="com.shennian.agent",g=s.join(d.homedir(),"Library/LaunchAgents",`${U}.plist`),Je="net.shennian.client.background-service",ie=s.resolve(X,"../../native/macos-background-client/Shennian.app"),h=y("Shennian.app"),P=s.join(h,"Contents","MacOS","ShennianClientLauncher"),Ke=s.join(d.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),L=y("autostart.cmd"),_=y("autostart.vbs"),O=y("autostart.xml"),Xe=s.join(Ke,"ShennianAgent.vbs");function oe(e){const n=m()==="win32"?`where ${e}`:`command -v ${e}`;try{return u(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(o=>o.trim()).find(Boolean)??null}catch{return null}}function qe(){const e=m()==="win32"?"npx.cmd":"npx",n=s.join(s.dirname(Y),e);return i.existsSync(n)?n:oe("npx")}function I(){return _e({nodeExec:Y,scriptPath:q,shennianCommandPath:oe("shennian"),npxPath:qe()})}function se(){try{u(`schtasks /delete /tn "${b}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function ce(){try{i.unlinkSync(Xe)}catch{}}function Ye(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),t=process.env.USERNAME?.trim()||d.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${t}`:n?`${n}\\${t}`:t}function ze(e){if(m()==="win32"&&Ie(e.command)){const n=[e.command,...e.args].map(J).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Qe(e,n,t=process.env){return{detached:!0,stdio:["ignore",n,n],env:t,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ze(){const e=I();i.writeFileSync(L,Ae(e,p)),i.writeFileSync(_,De(L)),ce(),se();const n=ke({userId:Ye(),command:s.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[J(_)]});i.writeFileSync(O,n,"utf8");const t=Pe({taskName:b,xmlPath:O});try{return u(t.create,{stdio:"pipe",windowsHide:!0}),u(t.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function en(){be(b,e=>{u(e,{stdio:"pipe",windowsHide:!0})})}function ae(){const e={};for(const n of z)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=d.homedir(),e.PATH=Ee({pathValue:e.PATH,env:e}),e.USER??=d.userInfo().username,m()==="win32"){const n=process.env.TEMP||process.env.TMP||d.tmpdir(),t=d.homedir();e.USERPROFILE??=t;const r=s.win32.parse(t);e.HOMEDRIVE??=r.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=t.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||d.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function nn(e={}){const n=ae(),t=e.launch??I(),r=e.brandedLauncherPath===void 0?i.existsSync(P)?P:null:e.brandedLauncherPath,o=Object.entries(n).map(([f,R])=>` <key>${T(f)}</key>
3
- <string>${T(R)}</string>`).join(`
4
- `),a=[...r?[r,"--exec"]:[],t.command,...t.args].map(f=>` <string>${T(f)}</string>`).join(`
5
- `),l=r?` <key>AssociatedBundleIdentifiers</key>
6
- <array>
7
- <string>${Je}</string>
8
- </array>
9
- `:"";return`<?xml version="1.0" encoding="UTF-8"?>
1
+ import c from"chalk";import o from"node:fs";import a from"node:path";import d from"node:os";import{execSync as u,spawn as fe}from"node:child_process";import{randomUUID as F}from"node:crypto";import{fileURLToPath as Se}from"node:url";import{getShennianDir as he,loadConfig as B,resolveShennianPath as S,saveConfig as ge}from"../config/index.js";import{buildAugmentedPath as ye}from"../env-path.js";import{buildWindowsLauncherCommand as we,buildWindowsScheduledTaskCommands as Ee,buildWindowsScheduledTaskXml as ve,buildWindowsStartupVbs as Pe,escapeXml as x,isWindowsCmdScript as Ae,quoteCmdArg as V,quoteSystemdArg as Ie,suspendWindowsScheduledTask as Ne}from"./daemon-windows.js";import{waitForDaemonReadyStatus as T}from"./daemon-start-wait.js";import{findRunningWindowsDaemonProcessIds as De,isShennianRunServiceCommand as ke}from"./daemon-process.js";import{getCurrentVersion as G}from"../upgrade/engine.js";import{buildWindowsLauncherCommand as Rn,buildWindowsScheduledTaskCommands as _n,buildWindowsScheduledTaskSuspendCommands as xn,buildWindowsScheduledTaskXml as Tn,buildWindowsStartupVbs as Ln,suspendWindowsScheduledTask as On}from"./daemon-windows.js";import{isShennianRunServiceCommand as Hn}from"./daemon-process.js";const be=a.dirname(Se(import.meta.url)),g=he(),y=S("daemon.pid"),m=S("daemon.log"),L=S("remote-access.disabled"),k=S("daemon-launcher.json"),K=a.resolve(be,"../../bin/shennian.js"),J=process.execPath,q=new Set(["PATH","HOME","USERPROFILE","HOMEDRIVE","HOMEPATH","USERNAME","USERDOMAIN","COMPUTERNAME","USER","LOGNAME","SHELL","TMPDIR","LANG","LC_ALL","LC_CTYPE","SSH_AUTH_SOCK","XDG_CONFIG_HOME","XDG_DATA_HOME","TEMP","TMP","APPDATA","LOCALAPPDATA","ELECTRON_RUN_AS_NODE","SHENNIAN_DESKTOP_CLI_SCRIPT","SHENNIAN_DESKTOP_CLI_BRIDGE","SHENNIAN_DESKTOP_SERVER_URL","SHENNIAN_WECHAT_CHANNEL_HELPER_DIR","SHENNIAN_HOME","SHENNIAN_ACCOUNT_NAMESPACE_ID","SHENNIAN_NATIVE_FUSION_DISABLED","SHENNIAN_WECHAT_DELIVERABILITY_TRACE"]);function Re(e){return q.has(e)}function p(){const e=d.platform();return e==="darwin"||e==="linux"||e==="win32"?e:"linux"}function O(e){const n=e.replace(/\\/g,"/").toLowerCase(),r=d.tmpdir().replace(/\\/g,"/").toLowerCase();return n.includes("/_npx/")||n.includes("/npm-cache/_npx/")||n.includes("/pnpm/dlx/")||n.startsWith(r.endsWith("/")?r:`${r}/`)}function _e(e){return process.env.ELECTRON_RUN_AS_NODE==="1"&&e.desktopBridgePath&&o.existsSync(e.scriptPath)&&o.existsSync(e.desktopBridgePath)?{command:e.nodeExec,args:[e.desktopBridgePath,e.scriptPath,"run-service"],mode:"direct"}:o.existsSync(e.scriptPath)&&!O(e.scriptPath)?{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}:e.shennianCommandPath&&!O(e.shennianCommandPath)?{command:e.shennianCommandPath,args:["run-service"],mode:"global-shim"}:e.npxPath?{command:e.npxPath,args:["--yes","shennian","run-service"],mode:"npx"}:{command:e.nodeExec,args:[e.scriptPath,"run-service"],mode:"direct"}}function xe(e){const n=e.trim();if(!n)return null;if(n.startsWith("{"))try{const t=JSON.parse(n),s=Number(t.pid);return!Number.isInteger(s)||s<=0?null:{pid:s,...typeof t.instanceId=="string"?{instanceId:t.instanceId}:{},...typeof t.version=="string"?{version:t.version}:{},...t.launcher==="desktop-managed"||t.launcher==="global-cli"||t.launcher==="unknown"?{launcher:t.launcher}:{},...typeof t.startedAt=="string"?{startedAt:t.startedAt}:{},...t.adopted===!0?{adopted:!0}:{}}}catch{return null}const r=parseInt(n,10);return isNaN(r)?null:{pid:r}}function A(){try{return xe(o.readFileSync(y,"utf-8"))}catch{return null}}function v(e){try{return process.kill(e,0),!0}catch{return!1}}function X(e,n=3e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,100)}return!v(e)}function Te(){return o.existsSync(L)}function Le(){try{o.unlinkSync(L)}catch{}}function Oe(e,n){const r=a.dirname(e),t=`${e}.${process.pid}.${F()}.tmp`;o.mkdirSync(r,{recursive:!0});let s=null;try{s=o.openSync(t,"w",384),o.writeSync(s,n,void 0,"utf8"),o.fsyncSync(s),o.closeSync(s),s=null,o.renameSync(t,e);try{const i=o.openSync(r,"r");try{o.fsyncSync(i)}finally{o.closeSync(i)}}catch{}}finally{s!==null&&o.closeSync(s);try{o.unlinkSync(t)}catch{}}}function Ce(e={}){const n=e.disabledAt??new Date().toISOString(),r=e.markerFile??L,t=e.platform??p(),s=e.servicePresent??M(t),i=e.execCommand??(l=>{u(l,{stdio:"pipe",windowsHide:!0})});if(Oe(r,n),!s)return{disabledAt:n,autoStartDisabled:!0};try{return i(t==="darwin"?`launchctl disable "${P()}"`:t==="linux"?"systemctl --user disable shennian":`schtasks /change /tn "${D}" /disable`),{disabledAt:n,autoStartDisabled:!0}}catch(l){return{disabledAt:n,autoStartDisabled:!1,autoStartError:l instanceof Error?l.message:String(l)}}}function He(e={}){const n=e.platform??p();if(!(e.servicePresent??M(n)))return{serviceStopped:!0};const t=e.execCommand??(s=>{u(s,{stdio:"pipe",windowsHide:!0})});try{return t(n==="darwin"?`launchctl bootout "${P()}"`:n==="linux"?"systemctl --user stop shennian":`schtasks /end /tn "${D}"`),{serviceStopped:!0}}catch(s){return{serviceStopped:!1,error:s instanceof Error?s.message:String(s)}}}function Y(){return process.env.SHENNIAN_DESKTOP_CLI_SCRIPT?"desktop-managed":"global-cli"}function $e(){return`${process.pid}-${Date.now()}-${F()}`}function Me(e,n,r={}){o.mkdirSync(g,{recursive:!0}),o.writeFileSync(y,JSON.stringify({pid:e,instanceId:n,...r.version?{version:r.version}:{},launcher:r.launcher??Y(),...r.adopted?{adopted:!0}:{},startedAt:new Date().toISOString()},null,2))}function Nn(e,n){const r=A();if(!(r?.pid!==e||r.instanceId!==n))try{o.unlinkSync(y)}catch{}}function z(e,n=Y(),r){o.mkdirSync(g,{recursive:!0}),o.writeFileSync(k,JSON.stringify({pid:e,launcher:n,...r?{instanceId:r}:{},updatedAt:new Date().toISOString()},null,2))}function Ue(e){if(e)try{if(JSON.parse(o.readFileSync(k,"utf-8")).instanceId!==e)return}catch{return}try{o.unlinkSync(k)}catch{}}function je(e){e&&z(e)}function We(e,n){if(!e||!n)return"unknown";try{const r=JSON.parse(o.readFileSync(k,"utf-8"));if(r.pid!==e)return"unknown";if(r.launcher==="desktop-managed"||r.launcher==="global-cli")return r.launcher}catch{}return C(e)}function C(e){if(p()==="win32")return"unknown";try{const n=u(`ps -p ${e} -o command=`,{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).replace(/\\/g,"/");if(n.includes("/cli-bridge.js")||n.includes("/Resources/daemon/")||n.includes("\\resources\\daemon\\"))return"desktop-managed";if(n.includes("/node_modules/shennian/")||n.includes("/bin/shennian")||n.includes(" shennian run-service")||n.includes(" shennian.js run-service"))return"global-cli"}catch{}return"unknown"}function Q(e=process.pid){if(p()==="win32")return De(e);try{return u("ps -axo pid=,command=",{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:1e3,windowsHide:!0}).split(/\r?\n/).map(r=>{const t=r.trim().match(/^(\d+)\s+(.+)$/);if(!t)return null;const s=Number(t[1]),i=t[2];return!Number.isInteger(s)||s===e?null:ke(i)?s:null}).filter(r=>typeof r=="number")}catch{return[]}}function Fe(e=process.env,n=g,r=d.tmpdir(),t=a.join(d.homedir(),".shennian")){if(e.SHENNIAN_E2E_ISOLATED_DAEMON!=="1"||!e.SHENNIAN_HOME?.trim()||a.resolve(n)===a.resolve(t))return!1;const s=a.relative(a.resolve(r),a.resolve(n));return s!==""&&s!==".."&&!s.startsWith(`..${a.sep}`)&&!a.isAbsolute(s)||a.resolve(n)===a.resolve(e.SHENNIAN_HOME)}function Be(e=process.env,n=g){return!Fe(e,n)}function f(e={}){let n=A(),r=n?.pid??null,t=r!==null&&v(r),s=r!==null&&!t,i=n?.adopted===!0;const l=B();if(s&&e.cleanupStale){try{o.unlinkSync(y)}catch{}n=null,r=null,t=!1,s=!1}if(!t&&Be()){const E=Q()[0];if(E&&(r=E,t=!0,s=!1,i=!0,e.cleanupStale)){const W=$e();Me(E,W,{launcher:C(E),adopted:!0}),z(E,C(E)),n={pid:E,instanceId:W,adopted:!0}}}return{running:t,pid:r,stale:s,remoteAccessDisabled:Te(),launcher:We(r,t),platform:p(),shennianDir:g,pidFile:y,logFile:m,...n?.instanceId?{instanceId:n.instanceId}:{},...i?{adopted:!0}:{},...l.machineId?{machineId:l.machineId}:{},paired:!!(l.machineToken&&l.machineId),...l.serverUrl?{serverUrl:l.serverUrl}:{}}}function w(e){process.stdout.write(`${JSON.stringify(e,null,2)}
2
+ `)}function Z(e){const n=e?.trim();if(!n)return;const r=B();r.serverUrl=n,ge(r)}function ee(e){return e?.trim()||process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim()||void 0}const H="com.shennian.agent",h=a.join(d.homedir(),"Library/LaunchAgents",`${H}.plist`),ne=S("Shennian.app"),Ve=a.join(d.homedir(),"AppData","Roaming","Microsoft","Windows","Start Menu","Programs","Startup"),b=S("autostart.cmd"),R=S("autostart.vbs"),_=S("autostart.xml"),Ge=a.join(Ve,"ShennianAgent.vbs");function te(e){const n=p()==="win32"?`where ${e}`:`command -v ${e}`;try{return u(n,{stdio:["ignore","pipe","pipe"],encoding:"utf-8",windowsHide:!0}).split(/\r?\n/).map(s=>s.trim()).find(Boolean)??null}catch{return null}}function Ke(){const e=p()==="win32"?"npx.cmd":"npx",n=a.join(a.dirname(J),e);return o.existsSync(n)?n:te("npx")}function I(){return _e({nodeExec:J,scriptPath:K,shennianCommandPath:te("shennian"),npxPath:Ke(),desktopBridgePath:process.env.SHENNIAN_DESKTOP_CLI_BRIDGE})}function re(){try{u(`schtasks /delete /tn "${D}" /f`,{stdio:"pipe",windowsHide:!0})}catch{}}function oe(){try{o.unlinkSync(Ge)}catch{}}function Je(){const e=process.env.USERDOMAIN?.trim(),n=process.env.COMPUTERNAME?.trim(),r=process.env.USERNAME?.trim()||d.userInfo().username;return e&&e.toUpperCase()!=="WORKGROUP"?`${e}\\${r}`:n?`${n}\\${r}`:r}function qe(e){if(p()==="win32"&&Ae(e.command)){const n=[e.command,...e.args].map(V).join(" ");return{command:process.env.ComSpec||"cmd.exe",args:["/d","/s","/c",`"${n}"`],windowsVerbatimArguments:!0}}return{command:e.command,args:e.args}}function Xe(e,n,r=process.env){return{detached:!0,stdio:["ignore",n,n],env:r,windowsHide:!0,...e.windowsVerbatimArguments?{windowsVerbatimArguments:!0}:{}}}function Ye(){const e=I();o.writeFileSync(b,we(e,m)),o.writeFileSync(R,Pe(b)),oe(),re();const n=ve({userId:Je(),command:a.join(process.env.SystemRoot||"C:\\Windows","System32","wscript.exe"),arguments:[V(R)]});o.writeFileSync(_,n,"utf8");const r=Ee({taskName:D,xmlPath:_});try{return u(r.create,{stdio:"pipe",windowsHide:!0}),u(r.run,{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}function ze(){Ne(D,e=>{u(e,{stdio:"pipe",windowsHide:!0})})}function se(){const e={};for(const n of q)process.env[n]&&(e[n]=process.env[n]);if(e.HOME??=d.homedir(),e.PATH=ye({pathValue:e.PATH,env:e}),e.USER??=d.userInfo().username,p()==="win32"){const n=process.env.TEMP||process.env.TMP||d.tmpdir(),r=d.homedir();e.USERPROFILE??=r;const t=a.win32.parse(r);e.HOMEDRIVE??=t.root.replace(/[\\/]$/,"")||process.env.HOMEDRIVE||"",e.HOMEPATH??=r.slice((e.HOMEDRIVE||"").length)||process.env.HOMEPATH||"",e.USERNAME??=process.env.USERNAME||d.userInfo().username,e.COMPUTERNAME??=process.env.COMPUTERNAME||"",e.TMPDIR??=n,e.TEMP??=n,e.TMP??=n}else e.TMPDIR??="/tmp";return e}function Qe(e={}){const n=se(),r=e.launch??I(),t=Object.entries(n).map(([i,l])=>` <key>${x(i)}</key>
3
+ <string>${x(l)}</string>`).join(`
4
+ `),s=[r.command,...r.args].map(i=>` <string>${x(i)}</string>`).join(`
5
+ `);return`<?xml version="1.0" encoding="UTF-8"?>
10
6
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
11
7
  <plist version="1.0">
12
8
  <dict>
13
9
  <key>Label</key>
14
- <string>${U}</string>
15
- ${l} <key>ProgramArguments</key>
10
+ <string>${H}</string>
11
+ <key>ProgramArguments</key>
16
12
  <array>
17
- ${a}
13
+ ${s}
18
14
  </array>
19
15
  <key>EnvironmentVariables</key>
20
16
  <dict>
21
- ${o}
17
+ ${t}
22
18
  </dict>
23
19
  <key>RunAtLoad</key>
24
20
  <true/>
@@ -27,24 +23,24 @@ ${o}
27
23
  <key>ThrottleInterval</key>
28
24
  <integer>5</integer>
29
25
  <key>StandardOutPath</key>
30
- <string>${p}</string>
26
+ <string>${m}</string>
31
27
  <key>StandardErrorPath</key>
32
- <string>${p}</string>
28
+ <string>${m}</string>
33
29
  </dict>
34
- </plist>`}function tn(){if(!i.existsSync(ie))return console.warn(c.yellow("\u26A0 Shennian background identity bundle is missing; using the legacy launcher")),null;const e=`${h}.installing-${process.pid}`,n=`${h}.previous-${process.pid}`;try{if(i.rmSync(e,{recursive:!0,force:!0}),i.rmSync(n,{recursive:!0,force:!0}),i.cpSync(ie,e,{recursive:!0,preserveTimestamps:!0,verbatimSymlinks:!0}),i.existsSync(h)&&i.renameSync(h,n),i.renameSync(e,h),i.rmSync(n,{recursive:!0,force:!0}),i.chmodSync(P,493),ye(P,["--register-bundle",h],{stdio:"ignore",windowsHide:!0}).status!==0)throw new Error("Launch Services registration failed");return P}catch(t){try{i.rmSync(e,{recursive:!0,force:!0}),!i.existsSync(h)&&i.existsSync(n)&&i.renameSync(n,h)}catch{}return console.warn(c.yellow(`\u26A0 Unable to install the Shennian background identity; using the legacy launcher: ${t instanceof Error?t.message:String(t)}`)),null}}const N=s.join(d.homedir(),".config/systemd/user/shennian.service");function rn(){const e=ae(),n=I(),t=Object.entries(e).map(([o,a])=>`Environment=${o}=${a}`).join(`
35
- `),r=[n.command,...n.args].map(Ne).join(" ");return`[Unit]
30
+ </plist>`}function ie(e=ne){o.rmSync(e,{recursive:!0,force:!0})}function Ze(e=p()){return e!=="darwin"?null:"macOS may show \u201CNode.js Foundation\u201D under Allow in the Background. This is the Node.js runtime used by Shennian Client."}const N=a.join(d.homedir(),".config/systemd/user/shennian.service");function en(){const e=se(),n=I(),r=Object.entries(e).map(([s,i])=>`Environment=${s}=${i}`).join(`
31
+ `),t=[n.command,...n.args].map(Ie).join(" ");return`[Unit]
36
32
  Description=Shennian Agent Daemon
37
33
  After=network.target
38
34
 
39
35
  [Service]
40
- ${t}
41
- ExecStart=${r}
36
+ ${r}
37
+ ExecStart=${t}
42
38
  Restart=on-failure
43
39
  RestartSec=10
44
- StandardOutput=append:${p}
45
- StandardError=append:${p}
40
+ StandardOutput=append:${m}
41
+ StandardError=append:${m}
46
42
 
47
43
  [Install]
48
- WantedBy=default.target`}const b="ShennianAgent";function on(e){u(e,{stdio:"pipe",windowsHide:!0})}function sn(){const e=process.getuid?.();if(typeof e=="number")return e;const n=d.userInfo();return typeof n.uid=="number"?n.uid:n.username}function k(e=sn()){return`gui/${e}/${U}`}function cn(e=on){try{return e(`launchctl enable "${k()}"; launchctl unload "${g}" 2>/dev/null; launchctl load -w "${g}"`),e(`launchctl kickstart -k "${k()}"`),!0}catch{try{return e(`launchctl enable "${k()}"`),e(`launchctl load -w "${g}"`),e(`launchctl kickstart -k "${k()}"`),!0}catch{return!1}}}function an(){i.mkdirSync(w,{recursive:!0});const e={};for(const[n,t]of Object.entries(process.env))t!==void 0&&Le(n)&&(e[n]=t);i.writeFileSync(y("env.json"),JSON.stringify(e,null,2))}function ue(e={}){i.mkdirSync(w,{recursive:!0});const n=S({cleanupStale:!0});if(n.pid!==null&&n.running&&!j(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}de(n);const t=i.openSync(p,"a"),r=ze(I()),o=he(r.command,r.args,Qe(r,t));o.unref(),i.closeSync(t),Fe(o.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${o.pid})`)),console.log(c.gray(` Logs: ${p}`)))}function j(e,n=D(),t=K()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==t}function W(e=m()){switch(e){case"darwin":return i.existsSync(g)&&i.existsSync(P);case"linux":return i.existsSync(N);case"win32":return i.existsSync(L)&&i.existsSync(_)&&i.existsSync(O)}}function un(e,n,t,r=D(),o=K()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&t&&!j(e,r,o)}function de(e=S({cleanupStale:!0})){if(!(!j(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),Q(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),Q(e.pid,2e3));try{D()?.pid===e.pid&&i.unlinkSync(v)}catch{}}catch{}}function le(){i.mkdirSync(w,{recursive:!0});const e=m();switch(I().mode==="direct"&&H(q)&&console.warn(c.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),e){case"darwin":{const t=s.dirname(g);i.mkdirSync(t,{recursive:!0});const r=tn();return i.writeFileSync(g,nn({brandedLauncherPath:r})),cn()}case"linux":{const t=s.dirname(N);i.mkdirSync(t,{recursive:!0}),i.writeFileSync(N,rn());try{u("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{u(`loginctl enable-linger ${d.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return u("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ze()}}function dn(){const n=S({cleanupStale:!0}).pid;if(n===null)return{};if(!A(n)){try{i.unlinkSync(v)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(t){return{error:t instanceof Error?t.message:String(t)}}}async function pe(e=5e3){const n=dn();if(!n.stoppedPid)return n;if(!await B(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await B(n.stoppedPid,2e3)}catch(r){return{...n,error:r instanceof Error?r.message:String(r)}}try{i.unlinkSync(v)}catch{}We();for(const r of ne())if(r!==n.stoppedPid)try{process.kill(r,"SIGTERM"),await B(r,2e3)}catch{}return n}function ln(e={}){te(re(e.api)),$e(),de(),le()||ue({quiet:e.json}),e.json?E($(()=>S({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function pn(e={}){const n=He(),t=Me(),r=await pe(),o=S({cleanupStale:!0}),a=r.error??n.autoStartError??t.error;if(e.json){E(a?{...o,error:a}:o);return}a?console.error(c.red(`\u2717 Failed to stop completely: ${a}`)):console.log(c.green("\u2713 Background service stopped"))}function me(e){const n=S({cleanupStale:!0});if(un(n,e,W())){e.json?E(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}an(),ln(e)}async function F(e={}){await pn(e)}async function B(e,n=5e3){const t=Date.now();for(;Date.now()-t<n;){if(!A(e))return!0;await new Promise(r=>setTimeout(r,100))}return!A(e)}async function mn(e={}){const n=S({cleanupStale:!0});if(n.remoteAccessDisabled){e.json?E({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(c.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}te(re(e.api));const t=m();t==="win32"&&en();const r=await pe();if(r.error){e.json?E({...S(),error:r.error}):console.error(c.red(`\u2717 Failed to stop: ${r.error}`));return}if(r.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${r.stoppedPid})`)):r.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${r.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(t==="linux"||t==="win32")&&le()){e.json&&E($(()=>S({cleanupStale:!0})));return}ue({quiet:e.json}),e.json&&E($(()=>S({cleanupStale:!0})))}function fe(e={}){const n=S({cleanupStale:!0});if(e.json){E(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${p}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function Se(e){if(!i.existsSync(p)){console.log(c.gray("No logs yet"));return}try{const n=u(d.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${p}"`:`tail -n ${e.lines} "${p}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=i.readFileSync(p,"utf-8").split(`
44
+ WantedBy=default.target`}const D="ShennianAgent";function nn(e){u(e,{stdio:"pipe",windowsHide:!0})}function tn(){const e=process.getuid?.();if(typeof e=="number")return e;const n=d.userInfo();return typeof n.uid=="number"?n.uid:n.username}function P(e=tn()){return`gui/${e}/${H}`}function rn(e=nn){try{return e(`launchctl enable "${P()}"; launchctl unload "${h}" 2>/dev/null; launchctl load -w "${h}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{try{return e(`launchctl enable "${P()}"`),e(`launchctl load -w "${h}"`),e(`launchctl kickstart -k "${P()}"`),!0}catch{return!1}}}function on(){o.mkdirSync(g,{recursive:!0});const e={};for(const[n,r]of Object.entries(process.env))r!==void 0&&Re(n)&&(e[n]=r);o.writeFileSync(S("env.json"),JSON.stringify(e,null,2))}function ce(e={}){o.mkdirSync(g,{recursive:!0});const n=f({cleanupStale:!0});if(n.pid!==null&&n.running&&!$(n)){e.quiet||console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}ae(n);const r=o.openSync(m,"a"),t=qe(I()),s=fe(t.command,t.args,Xe(t,r));s.unref(),o.closeSync(r),je(s.pid),e.quiet||(console.log(c.green(`\u2713 Background service started (PID ${s.pid})`)),console.log(c.gray(` Logs: ${m}`)))}function $(e,n=A(),r=G()){return e.pid===null||!e.running?!1:e.adopted===!0||n?.version!==r}function M(e=p()){switch(e){case"darwin":return o.existsSync(h)&&!o.existsSync(ne);case"linux":return o.existsSync(N);case"win32":return o.existsSync(b)&&o.existsSync(R)&&o.existsSync(_)}}function sn(e,n,r,t=A(),s=G()){return!n.api?.trim()&&e.pid!==null&&e.running&&!e.stale&&!e.remoteAccessDisabled&&r&&!$(e,t,s)}function ae(e=f({cleanupStale:!0})){if(!(!$(e)||e.pid===null))try{process.kill(e.pid,"SIGTERM"),X(e.pid,5e3)||(process.kill(e.pid,"SIGKILL"),X(e.pid,2e3));try{A()?.pid===e.pid&&o.unlinkSync(y)}catch{}}catch{}}function ue(e={}){o.mkdirSync(g,{recursive:!0});const n=p();switch(I().mode==="direct"&&O(K)&&console.warn(c.yellow("\u26A0 Warning: Current CLI path is temporary (npx). Auto-start may not work after reboot.\n Run `npm install -g shennian@latest` for reliable auto-start.")),n){case"darwin":{const t=a.dirname(h);o.mkdirSync(t,{recursive:!0}),ie(),o.writeFileSync(h,Qe());const s=rn(),i=Ze(n);return!e.quiet&&i&&console.log(c.gray(`\u2139 ${i}`)),s}case"linux":{const t=a.dirname(N);o.mkdirSync(t,{recursive:!0}),o.writeFileSync(N,en());try{u("systemctl --user daemon-reload && systemctl --user enable shennian",{stdio:"pipe",windowsHide:!0})}catch{return!1}try{u(`loginctl enable-linger ${d.userInfo().username}`,{stdio:"pipe",windowsHide:!0})}catch{}try{return u("systemctl --user restart shennian",{stdio:"pipe",windowsHide:!0}),!0}catch{return!1}}case"win32":return Ye()}}function cn(){const n=f({cleanupStale:!0}).pid;if(n===null)return{};if(!v(n)){try{o.unlinkSync(y)}catch{}return{stalePid:n}}try{return process.kill(n,"SIGTERM"),{stoppedPid:n}}catch(r){return{error:r instanceof Error?r.message:String(r)}}}async function de(e=5e3){const n=cn();if(!n.stoppedPid)return n;if(!await j(n.stoppedPid,e))try{process.kill(n.stoppedPid,"SIGKILL"),await j(n.stoppedPid,2e3)}catch(t){return{...n,error:t instanceof Error?t.message:String(t)}}try{o.unlinkSync(y)}catch{}Ue();for(const t of Q())if(t!==n.stoppedPid)try{process.kill(t,"SIGTERM"),await j(t,2e3)}catch{}return n}function an(e={}){Z(ee(e.api)),Le(),ae(),ue({quiet:e.json})||ce({quiet:e.json}),e.json?w(T(()=>f({cleanupStale:!0}))):console.log(c.green("\u2713 Background service started"))}async function un(e={}){const n=Ce(),r=He(),t=await de(),s=f({cleanupStale:!0}),i=t.error??n.autoStartError??r.error;if(e.json){w(i?{...s,error:i}:s);return}i?console.error(c.red(`\u2717 Failed to stop completely: ${i}`)):console.log(c.green("\u2713 Background service stopped"))}function le(e){const n=f({cleanupStale:!0});if(sn(n,e,M())){e.json?w(n):console.log(c.green(`\u2713 Background service already running (PID ${n.pid})`));return}on(),an(e)}async function U(e={}){await un(e)}async function j(e,n=5e3){const r=Date.now();for(;Date.now()-r<n;){if(!v(e))return!0;await new Promise(t=>setTimeout(t,100))}return!v(e)}async function dn(e={}){const n=f({cleanupStale:!0});if(n.remoteAccessDisabled){e.json?w({...n,error:"remote_access_disabled_use_shennian_start"}):console.error(c.yellow("Remote access is paused. Run `shennian start` on this computer to resume."));return}Z(ee(e.api));const r=p();r==="win32"&&ze();const t=await de();if(t.error){e.json?w({...f(),error:t.error}):console.error(c.red(`\u2717 Failed to stop: ${t.error}`));return}if(t.stoppedPid!==void 0?e.json||console.log(c.green(`\u2713 Background service stopped (PID ${t.stoppedPid})`)):t.stalePid!==void 0?e.json||console.log(c.yellow(`\u26A0 Service process no longer exists, cleaned up stale PID ${t.stalePid}`)):e.json||console.log(c.gray("\u25CF Background service not running, starting it now")),(r==="linux"||r==="win32")&&ue()){e.json&&w(T(()=>f({cleanupStale:!0})));return}ce({quiet:e.json}),e.json&&w(T(()=>f({cleanupStale:!0})))}function pe(e={}){const n=f({cleanupStale:!0});if(e.json){w(n);return}if(n.pid===null){console.log(c.gray("\u25CF Background service not running"));return}n.running?(console.log(c.green(`\u25CF Background service running (PID ${n.pid})`)),console.log(c.gray(` Logs: ${m}`))):console.log(c.yellow(`\u25CF Background service stopped (PID ${n.pid} is stale)`))}function me(e){if(!o.existsSync(m)){console.log(c.gray("No logs yet"));return}try{const n=u(d.platform()==="win32"?`powershell Get-Content -Tail ${e.lines} "${m}"`:`tail -n ${e.lines} "${m}"`,{encoding:"utf-8",windowsHide:!0});process.stdout.write(n)}catch{const n=o.readFileSync(m,"utf-8").split(`
49
45
  `).slice(-e.lines);console.log(n.join(`
50
- `))}}async function fn(){switch(await F(),m()){case"darwin":{if(i.existsSync(g)){try{u(`launchctl unload -w "${g}"`,{stdio:"pipe",windowsHide:!0})}catch{}i.unlinkSync(g)}i.rmSync(h,{recursive:!0,force:!0}),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"));break}case"linux":{try{u("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(i.existsSync(N)){i.unlinkSync(N);try{u("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{se();try{i.unlinkSync(L)}catch{}try{i.unlinkSync(_)}catch{}try{i.unlinkSync(O)}catch{}ce(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function Rn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>me(r)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(r=>F(r)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>fe(r)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>Se({lines:parseInt(r.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),t=(r,o)=>{o||console.error(c.yellow(`\u26A0 Deprecated command. Use \`shennian ${r}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("start",r.json),me(r)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async r=>{t("stop",r.json),await F(r)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(r=>{t("stop && shennian start",r.json),mn(r)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(r=>{t("status",r.json),fe(r)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(r=>{t("logs"),Se({lines:parseInt(r.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(fn)}export{Qe as buildDaemonSpawnOptions,nn as buildMacLaunchdPlist,rn as buildSystemdUnit,_n as buildWindowsLauncherCommand,On as buildWindowsScheduledTaskCommands,Tn as buildWindowsScheduledTaskSuspendCommands,$n as buildWindowsScheduledTaskXml,Cn as buildWindowsStartupVbs,un as canReuseRunningDaemonForStart,ae as captureEnvForService,We as clearDaemonLauncher,bn as clearDaemonPidIfOwner,$e as clearRemoteAccessDisabled,Ue as createDaemonInstanceId,ne as findRunningDaemonProcessIds,Z as getCurrentProcessDaemonLauncher,S as getDaemonStatus,k as getLaunchdServiceTarget,le as installService,W as isAutoStartServicePresent,H as isEphemeralCliPath,Ve as isIsolatedDaemonE2eInstance,Te as isRemoteAccessDisabled,Le as isSafeSnapshotEnvKey,Un as isShennianRunServiceCommand,He as prepareRemoteAccessPause,Fe as recordStartedDaemon,Rn as registerDaemonCommand,cn as reloadLaunchdAgent,_e as resolveServiceLaunchSpec,an as saveEnvSnapshot,Ge as shouldDiscoverGlobalDaemonProcesses,j as shouldReplaceRunningDaemon,ue as startDaemonProcess,Me as stopPreparedRemoteAccessService,Hn as suspendWindowsScheduledTask,ee as writeDaemonLauncher,je as writeDaemonPid};
46
+ `))}}async function ln(){switch(await U(),p()){case"darwin":{if(o.existsSync(h)){try{u(`launchctl unload -w "${h}"`,{stdio:"pipe",windowsHide:!0})}catch{}o.unlinkSync(h)}ie(),console.log(c.green("\u2713 Auto-start uninstalled (launchd)"));break}case"linux":{try{u("systemctl --user disable --now shennian",{stdio:"pipe",windowsHide:!0})}catch{}if(o.existsSync(N)){o.unlinkSync(N);try{u("systemctl --user daemon-reload",{stdio:"pipe",windowsHide:!0})}catch{}}console.log(c.green("\u2713 Auto-start uninstalled (systemd)"));break}case"win32":{re();try{o.unlinkSync(b)}catch{}try{o.unlinkSync(R)}catch{}try{o.unlinkSync(_)}catch{}oe(),console.log(c.green("\u2713 Auto-start uninstalled (Task Scheduler)"));break}}}function Dn(e){e.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>le(t)),e.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(t=>U(t)),e.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>pe(t)),e.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>me({lines:parseInt(t.lines,10)}));const n=e.command("daemon",{hidden:!0}).description("Deprecated: use top-level start/stop/status/logs"),r=(t,s)=>{s||console.error(c.yellow(`\u26A0 Deprecated command. Use \`shennian ${t}\` instead.`))};n.command("start").description("Start the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>{r("start",t.json),le(t)}),n.command("stop").description("Stop the background service").option("--json","Print machine-readable status JSON").action(async t=>{r("stop",t.json),await U(t)}),n.command("restart").description("Restart the background service").option("--json","Print machine-readable status JSON").option("--api <url>","Server URL override").action(t=>{r("stop && shennian start",t.json),dn(t)}),n.command("status").description("Show background service status").option("--json","Print machine-readable status JSON").action(t=>{r("status",t.json),pe(t)}),n.command("logs").description("Show recent logs").option("-n, --lines <n>","Number of lines","50").action(t=>{r("logs"),me({lines:parseInt(t.lines,10)})}),n.command("uninstall").description("Uninstall auto-start service").action(ln)}export{Xe as buildDaemonSpawnOptions,Qe as buildMacLaunchdPlist,en as buildSystemdUnit,Rn as buildWindowsLauncherCommand,_n as buildWindowsScheduledTaskCommands,xn as buildWindowsScheduledTaskSuspendCommands,Tn as buildWindowsScheduledTaskXml,Ln as buildWindowsStartupVbs,sn as canReuseRunningDaemonForStart,se as captureEnvForService,Ue as clearDaemonLauncher,Nn as clearDaemonPidIfOwner,Le as clearRemoteAccessDisabled,$e as createDaemonInstanceId,Q as findRunningDaemonProcessIds,Y as getCurrentProcessDaemonLauncher,f as getDaemonStatus,P as getLaunchdServiceTarget,Ze as getMacBackgroundItemNotice,ue as installService,M as isAutoStartServicePresent,O as isEphemeralCliPath,Fe as isIsolatedDaemonE2eInstance,Te as isRemoteAccessDisabled,Re as isSafeSnapshotEnvKey,Hn as isShennianRunServiceCommand,Ce as prepareRemoteAccessPause,je as recordStartedDaemon,Dn as registerDaemonCommand,rn as reloadLaunchdAgent,ie as removeLegacyMacBackgroundApp,_e as resolveServiceLaunchSpec,on as saveEnvSnapshot,Be as shouldDiscoverGlobalDaemonProcesses,$ as shouldReplaceRunningDaemon,ce as startDaemonProcess,He as stopPreparedRemoteAccessService,On as suspendWindowsScheduledTask,z as writeDaemonLauncher,Me as writeDaemonPid};
@@ -1,12 +1,12 @@
1
- import r from"chalk";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import k from"node:os";import R from"node:readline";import{loadConfig as u,saveConfig as N}from"../config/index.js";import{detectAgents as E}from"../agents/detect.js";import{clearRemoteAccessDisabled as A,getDaemonStatus as L,startDaemonProcess as b,installService as x,saveEnvSnapshot as O}from"./daemon.js";import{waitForDaemonReadyStatus as B}from"./daemon-start-wait.js";import{detectAndChooseServer as v}from"../region.js";import{buildPairQrPayload as M,PAIR_QR_RENDER_OPTIONS as F}from"./pair-qr.js";import{BrowserPairError as J,parsePairModeChoice as Q,resolvePairMode as C,runBrowserPairFlow as q}from"./pair-browser.js";import{markAllSessionIndexesForRepublish as z}from"../session/store.js";const K=3e3,U=600*1e3;function l(o){process.stdout.write(`${JSON.stringify(o)}
2
- `)}function f(o,e){l({type:"error",code:o,message:e}),process.exit(1)}function W(o){const e="\u2500".repeat(o.length+4);console.log(r.cyan(`\u250C${e}\u2510`)),console.log(r.cyan("\u2502 ")+r.bold.yellow(o)+r.cyan(" \u2502")),console.log(r.cyan(`\u2514${e}\u2518`))}async function Y(o){const e=(await import("qrcode-terminal")).default;return new Promise(n=>{e.generate(o,F,i=>{console.log(i),n()})})}async function H(o,e,n={}){const i=Date.now()+U;for(n.json&&l({type:"pair.waiting"});Date.now()<i;){await new Promise(d=>setTimeout(d,K));let a;try{a=await fetch(`${o}/api/machines/pair/status?token=${encodeURIComponent(e)}`)}catch{continue}if(a.status===410&&(n.json?f("PAIR_EXPIRED","Pairing code expired"):(console.error(r.red(`
3
- \u2717 Pairing code expired. Please re-run: shennian pair`)),process.exit(1))),!a.ok)continue;const t=await a.json();if(t.status==="claimed"&&t.machineToken&&t.machineId&&t.accountNamespaceId)return n.json&&l({type:"pair.claimed"}),{machineToken:t.machineToken,machineId:t.machineId,machineName:t.machineName??"",accountNamespaceId:t.accountNamespaceId}}return null}function G(o){const e=R.createInterface({input:process.stdin,output:process.stdout});return new Promise(n=>{e.question(o,i=>{e.close(),n(i.trim().toLowerCase()==="y")})})}function j(){const o=R.createInterface({input:process.stdin,output:process.stdout});return console.log(r.white("Choose how to pair this machine:")),console.log(r.cyan(" 1. Browser authorization (recommended)")),console.log(r.gray(" 2. QR code / pairing token (servers and remote terminals)")),new Promise(e=>{o.question("Selection (1/2) [1]: ",n=>{o.close(),e(Q(n))})})}async function $(o){const{serverUrl:e,machineName:n,force:i,json:a}=o,t=u();if(t.machineToken&&t.machineId&&!i){a?l({type:"done",status:"already-paired",machineId:t.machineId}):(console.log(r.green(`\u2713 Already paired (machine ID: ${t.machineId})`)),console.log(r.gray(' Run "shennian pair" to re-pair to another account')));return}const c=E().map(p=>p.type),m=k.platform(),g=m==="win32"?"windows":m==="darwin"?"macos":"linux";a||(console.log(r.bold(`
1
+ import r from"chalk";import{getDaemonInstallationId as D}from"../personal-sync/identity.js";import k from"node:os";import R from"node:readline";import{loadConfig as u,saveConfig as v}from"../config/index.js";import{detectAgents as E}from"../agents/detect.js";import{clearRemoteAccessDisabled as A,getDaemonStatus as B,startDaemonProcess as b,installService as U,saveEnvSnapshot as x}from"./daemon.js";import{waitForDaemonReadyStatus as L}from"./daemon-start-wait.js";import{detectAndChooseServer as N}from"../region.js";import{buildPairQrPayload as M,PAIR_QR_RENDER_OPTIONS as F}from"./pair-qr.js";import{BrowserPairError as J,parsePairModeChoice as q,resolvePairMode as O,runBrowserPairFlow as Q}from"./pair-browser.js";import{markAllSessionIndexesForRepublish as z}from"../session/store.js";const K=3e3,j=600*1e3;function l(o){process.stdout.write(`${JSON.stringify(o)}
2
+ `)}function f(o,e){l({type:"error",code:o,message:e}),process.exit(1)}function W(o){const e="\u2500".repeat(o.length+4);console.log(r.cyan(`\u250C${e}\u2510`)),console.log(r.cyan("\u2502 ")+r.bold.yellow(o)+r.cyan(" \u2502")),console.log(r.cyan(`\u2514${e}\u2518`))}async function Y(o){const e=(await import("qrcode-terminal")).default;return new Promise(n=>{e.generate(o,F,i=>{console.log(i),n()})})}async function H(o,e,n={}){const i=Date.now()+j;for(n.json&&l({type:"pair.waiting"});Date.now()<i;){await new Promise(d=>setTimeout(d,K));let a;try{a=await fetch(`${o}/api/machines/pair/status?token=${encodeURIComponent(e)}`)}catch{continue}if(a.status===410&&(n.json?f("PAIR_EXPIRED","Pairing code expired"):(console.error(r.red(`
3
+ \u2717 Pairing code expired. Please re-run: shennian pair`)),process.exit(1))),!a.ok)continue;const t=await a.json();if(t.status==="claimed"&&t.machineToken&&t.machineId&&t.accountNamespaceId)return n.json&&l({type:"pair.claimed"}),{machineToken:t.machineToken,machineId:t.machineId,machineName:t.machineName??"",accountNamespaceId:t.accountNamespaceId}}return null}function G(o){const e=R.createInterface({input:process.stdin,output:process.stdout});return new Promise(n=>{e.question(o,i=>{e.close(),n(i.trim().toLowerCase()==="y")})})}function C(){const o=R.createInterface({input:process.stdin,output:process.stdout});return console.log(r.white("Choose how to pair this machine:")),console.log(r.cyan(" 1. Browser authorization (recommended)")),console.log(r.gray(" 2. QR code / pairing token (servers and remote terminals)")),new Promise(e=>{o.question("Selection (1/2) [1]: ",n=>{o.close(),e(q(n))})})}async function $(o){const{serverUrl:e,machineName:n,force:i,json:a}=o,t=u();if(t.machineToken&&t.machineId&&!i){a?l({type:"done",status:"already-paired",machineId:t.machineId}):(console.log(r.green(`\u2713 Already paired (machine ID: ${t.machineId})`)),console.log(r.gray(' Run "shennian pair" to re-pair to another account')));return}const c=(await E()).map(p=>p.type),m=k.platform(),g=m==="win32"?"windows":m==="darwin"?"macos":"linux";a||(console.log(r.bold(`
4
4
  Shennian CLI \u2014 Pairing
5
- `)),console.log(r.gray(` Machine: ${n} | OS: ${g} | Agents: ${c.join(", ")||"none"}`)),console.log());let S=o.mode??C({json:a,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY});if(S==="prompt"&&(S=await j()),S==="browser"){const p=new AbortController,w=()=>p.abort();process.once("SIGINT",w);try{const s=await q({serverUrl:e,machineName:n,machineOs:g,agentList:c,signal:p.signal,openUrl:o.openUrl,emit:T=>{a?l(T):T.type==="pair.browser.created"&&(console.log(r.white("Approve this machine in your browser:")),console.log(r.cyan.underline(T.url)),console.log(r.gray(`
6
- If the browser did not open, copy this URL to any signed-in device.`)),console.log(r.gray("Waiting for authorization... (Ctrl+C to cancel)")))}});N({...t,machineToken:s.machineToken,machineId:s.machineId,accountNamespaceId:s.accountNamespaceId,serverUrl:e}),a?l({type:"pair.saved",machineId:s.machineId}):console.log(r.green(`
5
+ `)),console.log(r.gray(` Machine: ${n} | OS: ${g} | Agents: ${c.join(", ")||"none"}`)),console.log());let S=o.mode??O({json:a,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY});if(S==="prompt"&&(S=await C()),S==="browser"){const p=new AbortController,I=()=>p.abort();process.once("SIGINT",I);try{const s=await Q({serverUrl:e,machineName:n,machineOs:g,agentList:c,signal:p.signal,openUrl:o.openUrl,emit:T=>{a?l(T):T.type==="pair.browser.created"&&(console.log(r.white("Approve this machine in your browser:")),console.log(r.cyan.underline(T.url)),console.log(r.gray(`
6
+ If the browser did not open, copy this URL to any signed-in device.`)),console.log(r.gray("Waiting for authorization... (Ctrl+C to cancel)")))}});v({...t,machineToken:s.machineToken,machineId:s.machineId,accountNamespaceId:s.accountNamespaceId,serverUrl:e}),a?l({type:"pair.saved",machineId:s.machineId}):console.log(r.green(`
7
7
  \u2713 Paired successfully! Machine "${s.machineName}" is now linked.`));return}catch(s){const T=s instanceof J||typeof s=="object"&&s&&"code"in s&&typeof s.code=="string"?s.code:"PAIR_BROWSER_FAILED",P=s instanceof Error?s.message:"Browser pairing failed";a&&f(T,P),console.error(r.red(`
8
- \u2717 ${P}`)),process.exit(1)}finally{process.removeListener("SIGINT",w)}}let y;try{y=await fetch(`${e}/api/machines/pair/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,os:g,agentList:c,daemonInstallationId:D()})})}catch{const p=`Network error: cannot connect to ${e}`;a&&f("NETWORK_ERROR",p),console.error(r.red(`\u2717 ${p}`)),process.exit(1)}if(!y.ok){const w=`Failed to create machine: ${(await y.json().catch(()=>({}))).error??y.statusText}`;a&&f("PAIR_CREATE_FAILED",w),console.error(r.red(`\u2717 ${w}`)),process.exit(1)}const I=await y.json(),_=M(I.pairToken);a?l({type:"pair.created",code:I.pairToken,machineId:I.machineId,expiresAt:new Date(Date.now()+U).toISOString()}):(console.log(r.white("Scan QR code or enter the pairing code in the Shennian app:")),console.log(),await Y(_),W(I.pairToken),console.log(),console.log(r.gray("Waiting for pairing... (Ctrl+C to cancel)")));const h=await H(e,I.pairToken,{json:a});h||(a&&f("PAIR_TIMEOUT","Timed out waiting for pairing"),console.error(r.red(`
9
- \u2717 Timed out. Pairing code expired, please try again.`)),process.exit(1)),N({...t,machineToken:h.machineToken,machineId:h.machineId,accountNamespaceId:h.accountNamespaceId,serverUrl:e}),o.force&&z(),a&&l({type:"pair.saved",machineId:h.machineId}),a||console.log(r.green(`
10
- \u2713 Paired successfully! Machine "${h.machineName}" is now linked.`))}async function V(o){const e=u(),i=E().map(m=>m.type),a=k.platform(),t=a==="win32"?"windows":a==="darwin"?"macos":"linux",d=await fetch(`${o.serverUrl}/api/machines/desktop/register`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o.authToken}`},body:JSON.stringify({machineId:e.machineId,name:o.machineName,os:t,agentList:i})});if(!d.ok){const g=(await d.json().catch(()=>({}))).error??d.statusText;throw new Error(`Failed to register desktop machine: ${g}`)}const c=await d.json();if(!c.machine?.id||!c.machineToken||!c.accountNamespaceId)throw new Error("Failed to register desktop machine: missing machine token");return e.machineId=c.machine.id,e.machineToken=c.machineToken,e.accountNamespaceId=c.accountNamespaceId,e.serverUrl=o.serverUrl,N(e),{machineId:c.machine.id,machineName:c.machine.name}}async function X(o){const e=o?.trim();if(e)return e;const n=process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim();if(n)return n;const i=u().serverUrl?.trim();return i||v()}async function me(o,e){const n=u();if(!n.machineToken||!n.machineId){const a=o??await v();await $({serverUrl:a,machineName:e,force:!1})}else console.log(r.green(`\u2713 Already paired (machine ID: ${n.machineId})`));O(),A(),console.log(r.gray(`
11
- Starting background service...`)),x()||b()}function pe(o){o.command("desktop-register",{hidden:!0}).description("Register this desktop-managed machine to the authenticated account").option("--api <url>","Server URL").option("--name <name>","Machine name",k.hostname()).action(async e=>{try{const n=process.env.SHENNIAN_DESKTOP_AUTH_TOKEN;if(!n)throw new Error("Missing desktop auth token");const i=await X(e.api),a=await V({serverUrl:i,authToken:n,machineName:e.name});console.log(JSON.stringify({ok:!0,...a}))}catch(n){console.error(n instanceof Error?n.message:String(n)),process.exit(1)}}),o.command("pair").description("Pair this machine to a Shennian account (re-pair if already paired)").option("--api <url>","Server URL override").option("--server <url>","Server URL override").option("--name <name>","Machine name",k.hostname()).option("--json","Emit machine-readable JSONL events").option("--browser","Pair by approving a browser authorization request").option("--token","Pair with the existing QR code / token flow").option("--force","Force re-pair even when a machine token exists").action(async e=>{const n=u();let i;try{i=C({browser:e.browser,token:e.token,json:e.json,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY})}catch(c){const m=c instanceof Error?c.message:"Invalid pairing mode";e.json&&f("PAIR_MODE_CONFLICT",m),console.error(r.red(`\u2717 ${m}`)),process.exit(1)}if(i==="prompt"&&(i=await j()),n.machineToken&&n.machineId&&!e.force){if(e.json){l({type:"done",status:"already-paired",machineId:n.machineId});return}if(console.log(r.yellow(`Currently paired (machine ID: ${n.machineId})`)),!await G("Re-pairing will unlink the current account. Continue? (y/N) ")){console.log(r.gray("Cancelled"));return}}const a=e.server??e.api??await v(),t=!!(e.force||n.machineToken&&n.machineId);await $({serverUrl:a,machineName:e.name,force:t,json:!!e.json,mode:i}),O(),A(),e.json?l({type:"daemon.starting"}):console.log(r.gray(`
12
- Starting background service...`)),x()||b({quiet:!!e.json}),e.json&&(B(()=>L({cleanupStale:!0})),l({type:"done",status:"paired",machineId:u().machineId}))})}export{V as registerDesktopMachine,pe as registerPairCommand,$ as runPairFlow,me as runSmartStart};
8
+ \u2717 ${P}`)),process.exit(1)}finally{process.removeListener("SIGINT",I)}}let y;try{y=await fetch(`${e}/api/machines/pair/create`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:n,os:g,agentList:c,daemonInstallationId:D()})})}catch{const p=`Network error: cannot connect to ${e}`;a&&f("NETWORK_ERROR",p),console.error(r.red(`\u2717 ${p}`)),process.exit(1)}if(!y.ok){const I=`Failed to create machine: ${(await y.json().catch(()=>({}))).error??y.statusText}`;a&&f("PAIR_CREATE_FAILED",I),console.error(r.red(`\u2717 ${I}`)),process.exit(1)}const w=await y.json(),_=M(w.pairToken);a?l({type:"pair.created",code:w.pairToken,machineId:w.machineId,expiresAt:new Date(Date.now()+j).toISOString()}):(console.log(r.white("Scan QR code or enter the pairing code in the Shennian app:")),console.log(),await Y(_),W(w.pairToken),console.log(),console.log(r.gray("Waiting for pairing... (Ctrl+C to cancel)")));const h=await H(e,w.pairToken,{json:a});h||(a&&f("PAIR_TIMEOUT","Timed out waiting for pairing"),console.error(r.red(`
9
+ \u2717 Timed out. Pairing code expired, please try again.`)),process.exit(1)),v({...t,machineToken:h.machineToken,machineId:h.machineId,accountNamespaceId:h.accountNamespaceId,serverUrl:e}),o.force&&z(),a&&l({type:"pair.saved",machineId:h.machineId}),a||console.log(r.green(`
10
+ \u2713 Paired successfully! Machine "${h.machineName}" is now linked.`))}async function V(o){const e=u(),i=(await E({serverUrl:o.serverUrl,authToken:o.authToken})).map(m=>m.type),a=k.platform(),t=a==="win32"?"windows":a==="darwin"?"macos":"linux",d=await fetch(`${o.serverUrl}/api/machines/desktop/register`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o.authToken}`},body:JSON.stringify({machineId:e.machineId,name:o.machineName,os:t,agentList:i})});if(!d.ok){const g=(await d.json().catch(()=>({}))).error??d.statusText;throw new Error(`Failed to register desktop machine: ${g}`)}const c=await d.json();if(!c.machine?.id||!c.machineToken||!c.accountNamespaceId)throw new Error("Failed to register desktop machine: missing machine token");return e.machineId=c.machine.id,e.machineToken=c.machineToken,e.accountNamespaceId=c.accountNamespaceId,e.serverUrl=o.serverUrl,v(e),{machineId:c.machine.id,machineName:c.machine.name}}async function X(o){const e=o?.trim();if(e)return e;const n=process.env.SHENNIAN_DESKTOP_SERVER_URL?.trim();if(n)return n;const i=u().serverUrl?.trim();return i||N()}async function me(o,e){const n=u();if(!n.machineToken||!n.machineId){const a=o??await N();await $({serverUrl:a,machineName:e,force:!1})}else console.log(r.green(`\u2713 Already paired (machine ID: ${n.machineId})`));x(),A(),console.log(r.gray(`
11
+ Starting background service...`)),U()||b()}function pe(o){o.command("desktop-register",{hidden:!0}).description("Register this desktop-managed machine to the authenticated account").option("--api <url>","Server URL").option("--name <name>","Machine name",k.hostname()).action(async e=>{try{const n=process.env.SHENNIAN_DESKTOP_AUTH_TOKEN;if(!n)throw new Error("Missing desktop auth token");const i=await X(e.api),a=await V({serverUrl:i,authToken:n,machineName:e.name});console.log(JSON.stringify({ok:!0,...a}))}catch(n){console.error(n instanceof Error?n.message:String(n)),process.exit(1)}}),o.command("pair").description("Pair this machine to a Shennian account (re-pair if already paired)").option("--api <url>","Server URL override").option("--server <url>","Server URL override").option("--name <name>","Machine name",k.hostname()).option("--json","Emit machine-readable JSONL events").option("--browser","Pair by approving a browser authorization request").option("--token","Pair with the existing QR code / token flow").option("--force","Force re-pair even when a machine token exists").action(async e=>{const n=u();let i;try{i=O({browser:e.browser,token:e.token,json:e.json,stdinIsTty:!!process.stdin.isTTY,stdoutIsTty:!!process.stdout.isTTY})}catch(c){const m=c instanceof Error?c.message:"Invalid pairing mode";e.json&&f("PAIR_MODE_CONFLICT",m),console.error(r.red(`\u2717 ${m}`)),process.exit(1)}if(i==="prompt"&&(i=await C()),n.machineToken&&n.machineId&&!e.force){if(e.json){l({type:"done",status:"already-paired",machineId:n.machineId});return}if(console.log(r.yellow(`Currently paired (machine ID: ${n.machineId})`)),!await G("Re-pairing will unlink the current account. Continue? (y/N) ")){console.log(r.gray("Cancelled"));return}}const a=e.server??e.api??await N(),t=!!(e.force||n.machineToken&&n.machineId);await $({serverUrl:a,machineName:e.name,force:t,json:!!e.json,mode:i}),x(),A(),e.json?l({type:"daemon.starting"}):console.log(r.gray(`
12
+ Starting background service...`)),U({quiet:!!e.json})||b({quiet:!!e.json}),e.json&&(L(()=>B({cleanupStale:!0})),l({type:"done",status:"paired",machineId:u().machineId}))})}export{V as registerDesktopMachine,pe as registerPairCommand,$ as runPairFlow,me as runSmartStart};
@@ -16,7 +16,13 @@ export declare function registerUpgradeCommand(program: Command): void;
16
16
  export declare function handleUpgradeStart(client: CliRelayClient, reqId: string, targetVersion?: string, opts?: {
17
17
  currentVersion?: string;
18
18
  confirmedMajor?: boolean;
19
- }): Promise<void>;
19
+ prepareForUpgrade?: (jobId: string) => {
20
+ activeRunCount: number;
21
+ };
22
+ cancelUpgradePreparation?: () => void;
23
+ shutdownAfterHandoff?: () => void;
24
+ suppressResponse?: boolean;
25
+ }): Promise<Record<string, unknown>>;
20
26
  export declare function handleUpgradeStatus(client: CliRelayClient, reqId: string, opts?: {
21
27
  currentVersion?: string;
22
28
  }): Promise<void>;
@@ -1 +1 @@
1
- import r from"chalk";import{execSync as k,spawn as w}from"node:child_process";import v from"node:os";import{createInterface as $}from"node:readline/promises";import{getCurrentVersion as g,fetchLatestVersion as p,compareVersions as u,performUpgrade as f,checkForUpdate as S,recordUpgradeFailure as m}from"../upgrade/engine.js";const h=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function U(o,a){return o!=="major"||a}function y(o={}){const a=o.platform??v.platform(),c=o.kill??process.kill,s=o.pid??process.pid,l=o.env??process.env,t=o.spawn??w,n=o.execSync??k;if(a==="win32"){const d=l.ComSpec||"cmd.exe";try{t(d,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),c(s,"SIGTERM");return}catch{try{n('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{t("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:l}).unref()}catch{}c(s,"SIGTERM")}function R(o){o.command("upgrade").description("Upgrade shennian CLI to the latest version").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 a=>{const c=g();console.log(r.gray(`Current version: ${c}`)),console.log();let s=0;const l=setInterval(()=>{process.stdout.write(`\r ${h[s%h.length]} Checking for updates...`),s++},80);let t;try{t=a.to??await p(),clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(e){clearInterval(l),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(r.red(`\u2717 Failed to check for updates: ${e instanceof Error?e.message:String(e)}`)),process.exit(1)}const n=u(c,t);if(n==="none"&&!a.to){console.log(r.green(`\u2713 Already up to date (${c})`));return}if(console.log(` Available: ${r.green(t)} ${n==="major"?r.red("(major)"):n==="minor"?r.yellow("(minor)"):r.green("(patch)")}`),console.log(),a.check)return;if(n==="major"&&(console.log(r.yellow(" \u26A0 This is a major version update and may contain breaking changes.")),console.log(r.gray(" Please review the changelog before upgrading.")),console.log(),!a.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(r.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const e=$({input:process.stdin,output:process.stdout}),i=await e.question(` Type ${t} to confirm this major upgrade: `);if(e.close(),i.trim()!==t){console.log(r.gray(" Upgrade cancelled."));return}}const d=await f(t,e=>{switch(e.step){case"checking":console.log(r.gray(" \u2192 Checking npm..."));break;case"backing-up":console.log(r.gray(" \u2192 Backing up current version..."));break;case"installing":console.log(r.gray(` \u2192 Installing ${e.version}...`));break;case"verifying":console.log(r.gray(` \u2192 Verifying ${e.version}...`));break;case"restarting":console.log(r.gray(" \u2192 Restarting daemon..."));break}});d.ok?(console.log(),console.log(r.green(`\u2713 Upgraded ${d.from} \u2192 ${d.to}`)),console.log(r.gray(" Daemon is restarting...")),y()):(m(t,d.error),console.log(),d.rolledBack?console.error(r.red(`\u2717 Upgrade failed (rolled back to ${c})`)):console.error(r.red(`\u2717 Upgrade failed: ${d.error}`)),process.exit(1))})}async function C(o,a,c,s={}){const l=i=>{o.sendEvent({type:"event",event:"upgrade",payload:i})},t=s.currentVersion??g();let n;try{l({state:"checking",machineId:"self"}),n=c??await p()}catch(i){o.sendRes({type:"res",id:a,ok:!1,error:`Version check failed: ${i instanceof Error?i.message:String(i)}`});return}const d=u(t,n);if(d==="none"){o.sendRes({type:"res",id:a,ok:!0,payload:{alreadyLatest:!0,version:t}});return}if(!U(d,s.confirmedMajor===!0)){o.sendRes({type:"res",id:a,ok:!1,error:"Major upgrades require explicit second confirmation"});return}o.sendRes({type:"res",id:a,ok:!0,payload:{from:t,to:n}});const e=await f(n,i=>{switch(i.step){case"backing-up":l({state:"downloading",machineId:"self",version:n});break;case"installing":l({state:"installing",machineId:"self",version:n});break;case"restarting":l({state:"restarting",machineId:"self",from:t,to:n});break}},{currentVersion:t});if(e.ok)l({state:"restarting",machineId:"self",from:e.from,to:e.to}),await new Promise(i=>setTimeout(i,500)),y();else{const i=m(n,e.error);console.log(`[${new Date().toISOString()}] [upgrade] ${n} failed, retry after ${new Date(i.nextRetryAt).toISOString()}: ${e.error}`),l({state:e.rolledBack?"rolledback":"failed",machineId:"self",...e.rolledBack?{to:t,reason:e.error}:{error:e.error}})}}async function V(o,a,c={}){const s=await S(c.currentVersion);o.sendRes({type:"res",id:a,ok:!0,payload:s.hasUpdate?{hasUpdate:!0,current:s.current,latest:s.latest,changeType:s.changeType}:{hasUpdate:!1,current:c.currentVersion??g()}})}export{C as handleUpgradeStart,V as handleUpgradeStatus,U as hasMajorUpgradeConfirmation,R as registerUpgradeCommand,y as restartCurrentDaemonAfterUpgrade};
1
+ import c from"chalk";import{execSync as w,spawn as R}from"node:child_process";import{randomUUID as U}from"node:crypto";import S from"node:os";import{createInterface as j}from"node:readline/promises";import{getCurrentVersion as u,fetchLatestVersion as f,compareVersions as g,checkForUpdate as x}from"../upgrade/engine.js";import{callDaemonIpc as T}from"../daemon-ipc/client.js";import{getDaemonStatus as C}from"./daemon.js";import{ensureUpdaterInstalled as h}from"../upgrade/updater-bootstrap.js";import{launchUpdater as y}from"../upgrade/updater-launcher.js";const v=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"];function I(r,n){return r!=="major"||n}function H(r={}){const n=r.platform??S.platform(),a=r.kill??process.kill,e=r.pid??process.pid,d=r.env??process.env,t=r.spawn??R,o=r.execSync??w;if(n==="win32"){const s=d.ComSpec||"cmd.exe";try{t(s,["/d","/s","/c","timeout /t 2 /nobreak >nul & schtasks /Run /TN ShennianAgent >nul"],{detached:!0,stdio:"ignore",windowsHide:!0}).unref(),a(e,"SIGTERM");return}catch{try{o('schtasks /run /tn "ShennianAgent"',{stdio:"pipe",windowsHide:!0})}catch{}}}else try{t("sh",["-c","sleep 2; shennian start >/dev/null 2>&1"],{detached:!0,stdio:"ignore",env:d}).unref()}catch{}a(e,"SIGTERM")}function N(r){r.command("upgrade").description("Upgrade shennian CLI to the latest version").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 n=>{const a=u();console.log(c.gray(`Current version: ${a}`)),console.log();let e=0;const d=setInterval(()=>{process.stdout.write(`\r ${v[e%v.length]} Checking for updates...`),e++},80);let t;try{t=n.to??await f(),clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r")}catch(s){clearInterval(d),process.stdout.write("\r"+" ".repeat(40)+"\r"),console.error(c.red(`\u2717 Failed to check for updates: ${s instanceof Error?s.message:String(s)}`)),process.exit(1)}const o=g(a,t);if(o==="none"&&!n.to){console.log(c.green(`\u2713 Already up to date (${a})`));return}if(console.log(` Available: ${c.green(t)} ${o==="major"?c.red("(major)"):o==="minor"?c.yellow("(minor)"):c.green("(patch)")}`),console.log(),!n.check){if(o==="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(),!n.yes)){(!process.stdin.isTTY||!process.stdout.isTTY)&&(console.error(c.red("\u2717 Major upgrades require explicit confirmation; rerun with --yes.")),process.exit(1));const s=j({input:process.stdin,output:process.stdout}),l=await s.question(` Type ${t} to confirm this major upgrade: `);if(s.close(),l.trim()!==t){console.log(c.gray(" Upgrade cancelled."));return}}try{await T({method:"upgrade.start",params:{targetVersion:t,confirmedMajor:o==="major"||n.yes===!0},timeoutMs:1e4})}catch(s){if(C({cleanupStale:!0}).running)throw s;const i=h();y({fromVersion:a,targetVersion:t,oldDaemonPid:null},{readiness:i})}console.log(),console.log(c.green(`\u2713 Upgrade scheduled ${a} \u2192 ${t}`)),console.log(c.gray(" The independent updater will verify, roll back if needed, and restart Shennian."))}})}async function G(r,n,a,e={}){const d=i=>{r.sendEvent({type:"event",event:"upgrade",payload:i})},t=e.currentVersion??u();let o;try{d({state:"checking",machineId:"self"}),o=a??await f()}catch(i){const p=`Version check failed: ${i instanceof Error?i.message:String(i)}`;return e.suppressResponse||r.sendRes({type:"res",id:n,ok:!1,error:p}),{ok:!1,error:p}}const s=g(t,o);if(s==="none")return e.suppressResponse||r.sendRes({type:"res",id:n,ok:!0,payload:{alreadyLatest:!0,version:t}}),{ok:!0,alreadyLatest:!0,version:t};if(!I(s,e.confirmedMajor===!0))return e.suppressResponse||r.sendRes({type:"res",id:n,ok:!1,error:"Major upgrades require explicit second confirmation"}),{ok:!1,error:"Major upgrades require explicit second confirmation"};const l=U();try{const i=h(),p=e.prepareForUpgrade?.(l)??{activeRunCount:0},{job:k}=y({fromVersion:t,targetVersion:o,oldDaemonPid:process.pid,jobId:l},{readiness:i});d({state:"installing",machineId:"self",version:o,jobId:l,activeRunCount:p.activeRunCount}),d({state:"restarting",machineId:"self",from:t,to:o,jobId:l});const m={from:t,to:o,jobId:l,activeRunCount:p.activeRunCount};return e.suppressResponse||r.sendRes({type:"res",id:n,ok:!0,payload:m}),e.shutdownAfterHandoff?.(),{ok:!0,...m,resultPath:k.resultPath}}catch(i){e.cancelUpgradePreparation?.();const p=i instanceof Error?i.message:String(i);return d({state:"failed",machineId:"self",error:p}),e.suppressResponse||r.sendRes({type:"res",id:n,ok:!1,error:p}),{ok:!1,error:p}}}async function Y(r,n,a={}){const e=await x(a.currentVersion);r.sendRes({type:"res",id:n,ok:!0,payload:e.hasUpdate?{hasUpdate:!0,current:e.current,latest:e.latest,changeType:e.changeType}:{hasUpdate:!1,current:a.currentVersion??u()}})}export{G as handleUpgradeStart,Y as handleUpgradeStatus,I as hasMajorUpgradeConfirmation,N as registerUpgradeCommand,H as restartCurrentDaemonAfterUpgrade};
@@ -48,7 +48,7 @@ export type ShennianConfig = {
48
48
  refreshToken?: string;
49
49
  serverUrl?: string;
50
50
  customAgents?: Record<string, CustomAgentEntry>;
51
- /** Auto-upgrade policy. Legacy "minor" values are normalized to "none" at runtime. */
51
+ /** Auto-upgrade policy. Missing values default to patch; legacy "minor" values stay disabled. */
52
52
  autoUpgrade?: 'none' | 'patch' | 'minor';
53
53
  };
54
54
  export type RuntimeAutoUpgradePolicy = 'none' | 'patch';
@@ -1,2 +1,2 @@
1
- import o from"node:fs";import i from"node:path";import m from"node:os";import p from"node:crypto";function c(){const n=process.env.SHENNIAN_HOME?.trim();return n?i.resolve(n):a()?i.join(m.tmpdir(),"shennian-vitest-runtime",String(process.pid)):i.join(m.homedir(),".shennian")}function s(...n){return i.join(c(),...n)}function f(){return s("config.json")}const g=f();function v(n){return n==="patch"?"patch":"none"}function x(){try{const n=f();if(!o.existsSync(n))return{};const e=JSON.parse(d(o.readFileSync(n,"utf-8")));return e.serverUrl?.includes("shennian.ai")&&(e.serverUrl="https://shennian.net"),e}catch{return{}}}function E(n){const e=c(),r=f(),u=`${JSON.stringify(n,null,2)}
2
- `;if(o.mkdirSync(e,{recursive:!0,mode:448}),process.platform!=="win32"&&o.chmodSync(e,448),process.platform==="win32"){o.writeFileSync(r,u,{encoding:"utf8",mode:384});return}const t=`${r}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;try{o.writeFileSync(t,u,{encoding:"utf8",mode:384,flag:"wx"}),o.chmodSync(t,384),o.renameSync(t,r),o.chmodSync(r,384)}finally{try{o.unlinkSync(t)}catch{}}}function N(){o.mkdirSync(c(),{recursive:!0,mode:448}),o.mkdirSync(s("sessions"),{recursive:!0,mode:448}),process.platform!=="win32"&&(o.chmodSync(c(),448),o.chmodSync(s("sessions"),448))}function a(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}function d(n){return n.charCodeAt(0)===65279?n.slice(1):n}export{g as configPath,N as ensureDir,f as getConfigPath,c as getShennianDir,x as loadConfig,v as normalizeAutoUpgradePolicy,s as resolveShennianPath,E as saveConfig};
1
+ import e from"node:fs";import i from"node:path";import m from"node:os";import p from"node:crypto";function c(){const n=process.env.SHENNIAN_HOME?.trim();return n?i.resolve(n):a()?i.join(m.tmpdir(),"shennian-vitest-runtime",`${process.pid}-${process.env.VITEST_WORKER_ID??"main"}`):i.join(m.homedir(),".shennian")}function s(...n){return i.join(c(),...n)}function f(){return s("config.json")}const g=f();function v(n){return n===void 0||n==="patch"?"patch":"none"}function x(){try{const n=f();if(!e.existsSync(n))return{};const o=JSON.parse(d(e.readFileSync(n,"utf-8")));return o.serverUrl?.includes("shennian.ai")&&(o.serverUrl="https://shennian.net"),o}catch{return{}}}function E(n){const o=c(),r=f(),u=`${JSON.stringify(n,null,2)}
2
+ `;if(e.mkdirSync(o,{recursive:!0,mode:448}),process.platform!=="win32"&&e.chmodSync(o,448),process.platform==="win32"){e.writeFileSync(r,u,{encoding:"utf8",mode:384});return}const t=`${r}.tmp-${process.pid}-${p.randomBytes(6).toString("hex")}`;try{e.writeFileSync(t,u,{encoding:"utf8",mode:384,flag:"wx"}),e.chmodSync(t,384),e.renameSync(t,r),e.chmodSync(r,384)}finally{try{e.unlinkSync(t)}catch{}}}function N(){e.mkdirSync(c(),{recursive:!0,mode:448}),e.mkdirSync(s("sessions"),{recursive:!0,mode:448}),process.platform!=="win32"&&(e.chmodSync(c(),448),e.chmodSync(s("sessions"),448))}function a(){return(process.env.VITEST==="true"||!!process.env.VITEST_WORKER_ID)&&!process.env.SHENNIAN_HOME?.trim()}function d(n){return n.charCodeAt(0)===65279?n.slice(1):n}export{g as configPath,N as ensureDir,f as getConfigPath,c as getShennianDir,x as loadConfig,v as normalizeAutoUpgradePolicy,s as resolveShennianPath,E as saveConfig};
@@ -0,0 +1,16 @@
1
+ import type { DetectedAgent } from '../agents/detect.js';
2
+ import type { DaemonStatus } from '../commands/daemon.js';
3
+ import type { JsonValue } from './json.js';
4
+ import type { DaemonIpcHandler } from './server.js';
5
+ export declare const DAEMON_CONTROL_CAPABILITIES: readonly ["protocol.info", "daemon.status", "agent.list", "agent.auth.completed", "log.read", "upgrade.start", "upgrade.prepare-external", "upgrade.cancel-external"];
6
+ export declare function createDaemonControlHandlers(input: {
7
+ daemonVersion: string;
8
+ protocolVersion: number;
9
+ getStatus: () => DaemonStatus;
10
+ getAgents: () => DetectedAgent[];
11
+ startUpgrade?: (params: JsonValue | undefined) => Promise<JsonValue>;
12
+ prepareExternalUpgrade?: (params: JsonValue | undefined) => Promise<JsonValue> | JsonValue;
13
+ cancelExternalUpgrade?: () => Promise<JsonValue> | JsonValue;
14
+ completeAgentAuthentication?: (agentType: string) => Promise<boolean>;
15
+ }): Record<string, DaemonIpcHandler>;
16
+ export declare function readDaemonLogPage(logFile: string, params: JsonValue | undefined): JsonValue;
@@ -0,0 +1 @@
1
+ import o from"node:fs";import{getAcpLocalMetrics as d}from"../agents/acp/metrics.js";const p=["protocol.info","daemon.status","agent.list","agent.auth.completed","log.read","upgrade.start","upgrade.prepare-external","upgrade.cancel-external"],g=32*1024,m=64*1024;function _(t){return{"protocol.info":()=>{const e=t.getStatus();return{daemonVersion:t.daemonVersion,protocolVersion:t.protocolVersion,launcher:e.launcher,capabilities:[...p]}},"daemon.status":()=>t.getStatus(),"agent.list":()=>({agents:t.getAgents().map(e=>({type:e.type,...e.version?{version:e.version}:{},...e.runtimePlatform?{runtimePlatform:e.runtimePlatform}:{},...e.runtimeIsWsl!==void 0?{runtimeIsWsl:e.runtimeIsWsl}:{}})),acpMetrics:d()}),"agent.auth.completed":async e=>{const a=e&&typeof e=="object"&&!Array.isArray(e)&&typeof e.agentType=="string"?e.agentType:"";if(!/^acp:[a-z0-9][a-z0-9._-]{0,63}$/.test(a))throw new Error("invalid_acp_agent");return{resumed:await t.completeAgentAuthentication?.(a)??!1}},"log.read":e=>y(t.getStatus().logFile,e),"upgrade.start":async e=>{if(!t.startUpgrade)throw new Error("upgrade_unavailable");return t.startUpgrade(e)},"upgrade.prepare-external":async e=>{if(!t.prepareExternalUpgrade)throw new Error("external_upgrade_unavailable");return t.prepareExternalUpgrade(e)},"upgrade.cancel-external":async()=>{if(!t.cancelExternalUpgrade)throw new Error("external_upgrade_unavailable");return t.cancelExternalUpgrade()}}}function y(t,e){const a=e&&typeof e=="object"&&!Array.isArray(e)?e:{},i=typeof a.limitBytes=="number"&&Number.isFinite(a.limitBytes)?a.limitBytes:g,u=Math.max(1,Math.min(Math.floor(i),m));let s=0;try{s=o.statSync(t).size}catch{return{text:"",start:0,end:0,nextBefore:null,eof:!0}}const f=typeof a.before=="number"&&Number.isFinite(a.before)?Math.floor(a.before):s,n=Math.max(0,Math.min(f,s)),r=Math.max(0,n-u);if(n===r)return{text:"",start:r,end:n,nextBefore:r>0?r:null,eof:r===0};const l=Buffer.allocUnsafe(n-r),c=o.openSync(t,"r");try{o.readSync(c,l,0,l.length,r)}finally{o.closeSync(c)}return{text:l.toString("utf8"),start:r,end:n,nextBefore:r>0?r:null,eof:r===0}}export{p as DAEMON_CONTROL_CAPABILITIES,_ as createDaemonControlHandlers,y as readDaemonLogPage};
package/dist/src/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import{Command as se}from"commander";import y from"chalk";import ae from"node:os";import $ from"node:fs";import{loadConfig as p,saveConfig as ce,getConfigPath as me,getShennianDir as de,resolveShennianPath as J,normalizeAutoUpgradePolicy as le}from"./config/index.js";import{CliRelayClient as pe}from"./relay/client.js";import{collectKnownLogSecrets as K,installCliLogRedaction as ge,sanitizeCliLogValue as ue}from"./log-redaction.js";import{registerPairCommand as he,runSmartStart as fe}from"./commands/pair.js";import{registerLogoutCommand as Se}from"./commands/logout.js";import{clearDaemonPidIfOwner as j,createDaemonInstanceId as ve,clearDaemonLauncher as W,findRunningDaemonProcessIds as Ie,isRemoteAccessDisabled as Y,prepareRemoteAccessPause as we,stopPreparedRemoteAccessService as ye,isIsolatedDaemonE2eInstance as Re,registerDaemonCommand as Ce,writeDaemonPid as q,writeDaemonLauncher as Q}from"./commands/daemon.js";import{registerRuntimeCommand as ke}from"./commands/runtime.js";import{registerManagerCommand as De}from"./commands/manager.js";import{registerUpgradeCommand as Ee}from"./commands/upgrade.js";import{registerToolsCommand as Te}from"./commands/tools.js";import{registerIntegrationCommand as be}from"./commands/integration.js";import{registerRoomCommand as Ae}from"./commands/room.js";import{SessionManager as Pe}from"./session/manager.js";import{SERVERS as _,regionToUrl as Me,urlToRegion as Oe}from"./region.js";import{getCurrentVersion as X,handleStartupCrashCheck as Ue}from"./upgrade/engine.js";import{AutoUpgradeScheduler as Ne}from"./upgrade/scheduler.js";import{detectAgents as $e}from"./agents/detect.js";import{augmentProcessPath as Z}from"./env-path.js";Z();const ee=X();import{getCachedAgentInfos as _e,resolveAgentInfos as xe}from"./agents/model-registry.js";import{initCliLogReporter as Le,reportLog as R}from"./log-reporter.js";import{isNativeFusionEnabled as Be}from"./native-fusion/config.js";import{NativeSessionFusionService as Ve}from"./native-fusion/service.js";import{startDaemonLogRetention as Ge}from"./daemon-log.js";import{startDaemonIpcServer as He}from"./daemon-ipc/server.js";import{resolveManagedRoomIpcPaths as Fe}from"./daemon-ipc/paths.js";import{RoomSessionRegistry as ze}from"./room/session-registry.js";import{HttpRoomBindingClient as Je}from"./room/room-binding-client.js";import{TrustedManagedRoomBinder as Ke}from"./room/managed-room-binding.js";import{RoomHostSessionObservationStore as je}from"./room/host-session-observations.js";import{RoomMirrorSyncService as We}from"./room/mirror-sync-service.js";import{RoomActivationService as Ye}from"./room/activation-service.js";import{RoomActivationAudit as qe}from"./room/activation-audit.js";import{RoomAttachmentCache as Qe}from"./room/attachment-cache.js";import{RoomAttachmentUploader as Xe}from"./room/attachment-upload.js";import{ManagedRoomContextAuthority as Ze}from"./room/managed-context.js";import{createManagedRoomExecutor as eo}from"./room/managed-room-ipc.js";import{SESSION_SYNC_PROTOCOL_VERSION as oo,SESSION_SYNC_ADVERTISED_CAPABILITIES as no}from"@shennian/wire";import{getDaemonInstallationId as to}from"./session/store.js";import{synchronizeMachinePersonalAuthority as ro}from"./personal-sync/machine-authority.js";import{CLI_SESSION_SYNC_DATA_EPOCH as io,ensurePersonalDataEpoch as so}from"./personal-sync/epoch.js";import{DeletionSyncRetryWaitError as ao,DeletionSyncTerminalError as co}from"./session/deletion-sync-state.js";const oe=de(),mo=J("daemon.pid");function lo(){try{const o=$.readFileSync(mo,"utf-8").trim();if(o.startsWith("{")){const a=JSON.parse(o),m=Number(a.pid);return Number.isInteger(m)&&m>0?m:null}const r=parseInt(o,10);return Number.isInteger(r)&&r>0?r:null}catch{return null}}function po(o){return o.replace(/^https:\/\//,"wss://").replace(/^http:\/\//,"ws://")}function x(){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 go(o){const r=[`trigger=${o.trigger??"unknown"}`,`phase=${o.phase}`,`code=${o.code??"unknown"}`,`attempt=${o.reconnectAttempt}`];return o.reason&&r.push(`reason=${o.reason}`),o.error&&r.push(`error=${o.error}`),r.join(" ")}async function ne(o,r=5e3){const a=Date.now();for(;Date.now()-a<r;){try{process.kill(o,0)}catch{return!0}await new Promise(m=>setTimeout(m,100))}try{return process.kill(o,0),!1}catch{return!0}}const s=new se;s.name("shennian").description("Shennian \u2014 AI Agent Control Plane").version(ee),s.enablePositionalOptions(),be(s),Ae(s),s.option("--api <url>","Server URL override").option("--name <name>","Machine name",ae.hostname()).action(async o=>{const r=p(),a=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??r.serverUrl??void 0;await fe(a,o.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 o=>{const r=J("env.json");try{const e=JSON.parse($.readFileSync(r,"utf-8"));for(const[i,n]of Object.entries(e))process.env[i]||(process.env[i]=n);Z()}catch{}ge(K(process.env,p()));const a=Ge();Y()&&(console.log(`[${new Date().toISOString()}] remote access disabled, service start skipped`),clearInterval(a),process.exit(0));const m=!!(process.env.INVOCATION_ID||process.env.JOURNAL_STREAM||process.env.SHENNIAN_DESKTOP_SERVER_URL),L=async(e,i)=>{console.log(`[${new Date().toISOString()}] ${i} (PID ${e})`),process.kill(e,"SIGTERM"),await ne(e)||(process.kill(e,"SIGKILL"),await ne(e,2e3))};try{const e=lo();if(e&&e!==process.pid)try{process.kill(e,0),m?await L(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 M=Re(process.env,oe)?[]:Ie(process.pid);if(M.length>0)if(m)for(const e of M)try{await L(e,"managed start taking over from orphan daemon")}catch{}else console.log(`[${new Date().toISOString()}] daemon already running (PID ${M[0]}), skipping duplicate start`),process.exit(0);const g=ve();q(process.pid,g,{version:ee}),Q(process.pid,void 0,g),process.on("exit",()=>{j(process.pid,g),W(g)}),await Ue()&&(console.log(`[${new Date().toISOString()}] Rolled back to previous version, restarting...`),process.exit(0));try{so()}catch(e){console.error(`[personal-sync] data epoch initialization failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}const c=p(),h=o.api??process.env.SHENNIAN_DESKTOP_SERVER_URL??c.serverUrl??_.cn.url,u=X(),O=$e(),v=O.map(e=>e.type),U=_e(O),f=new ze,C=new je,I=new Ze(f),S=new Je(h),re=new Qe({actionClient:S}),ie=new Xe({actionClient:S});let k=null,d;const B=eo({authority:I,actionClient:S,attachmentCache:re,attachmentUploader:ie,observations:C,getMachineCredentials:()=>({machineToken:p().machineToken}),onPaused:({registryKey:e,sessionId:i})=>{const t=f.getByRegistryKey(e)?.binding;t&&f.setBinding({registryKey:e,bindingId:t.bindingId,roomId:t.roomId,agentIdentityId:t.agentIdentityId,agentMembershipId:t.agentMembershipId,status:"paused",activationMode:"paused"}),I.revokeSession(e),d?.discardPendingManagedRoomMessages(i)}}),D=await He({paths:Fe(),handlers:{"managed-room.execute":e=>B(e)}});let V=!1;const E=()=>{V||(V=!0,D.close().finally(()=>process.exit(0)))};if(process.once("SIGINT",E),process.once("SIGTERM",E),!c.machineToken){console.error('Shennian CLI is not paired. Run "shennian pair --browser" or "shennian pair --token" first.'),await D.close(),process.exitCode=1;return}process.removeListener("SIGINT",E),process.removeListener("SIGTERM",E);const G=`${po(h)}/relay/machine`,N=c.machineToken;if(!N)return;try{await ro({serverUrl:h,machineToken:N,machineId:c.machineId})}catch(e){console.error(`[personal-sync] machine authority verification failed: ${e instanceof Error?e.message:String(e)}`),await D.close(),process.exitCode=1;return}c.machineId&&Le(h,c.machineId),console.log(`[${new Date().toISOString()}] Connecting to ${G}... (v${u}) agents: ${v.join(",")}`),R({level:"info",wsEvent:"daemon.start",metadata:{version:u,agents:v}});let T=null,b=null,A=0;const H=le(c.autoUpgrade),l=new pe({serverUrl:G,machineToken:N,cliVersion:u,agentList:v,autoUpgradePolicy:H,sessionSyncProtocolVersion:oo,sessionSyncCapabilities:no,daemonInstallationId:to(),dataEpoch:io,onConnected:()=>{console.log(`[${new Date().toISOString()}] \u2713 Connected`),R({level:"info",wsEvent:"daemon.connected"}),U.some(n=>n.models.length>0)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:U}}),xe(O,{serverUrl:c.serverUrl??h,authToken:c.machineToken??c.accessToken}).then(n=>{JSON.stringify(n)!==JSON.stringify(U)&&l.sendEvent({type:"event",event:"machine.agents",payload:{agentList:v,agents:n}})}).catch(()=>{}),import("./upgrade/engine.js").then(({readUpgradeAttempt:n,clearUpgradeAttempt:t})=>{const w=n();t(),w&&(console.log(`[${new Date().toISOString()}] [upgrade] Reporting success: ${w.from} \u2192 ${w.to}`),l.sendEvent({type:"event",event:"upgrade",payload:{state:"success",machineId:"self",from:w.from,to:w.to}}))}).catch(()=>{}),P.start(),b?.start();const e=++A,i=async()=>{try{await d.synchronizeSessionDeletions(),e===A&&l.getState()==="connected"&&T?.handleConnected()}catch(n){if(console.error("[session.deletions] startup catch-up failed",n),e===A&&l.getState()==="connected"){if(n instanceof co)return;const t=n instanceof ao?Math.max(1e3,n.retryAfterMs):5e3;setTimeout(()=>{i()},t).unref?.()}}};i()},onDisconnected:e=>{A+=1,b?.stop(),console.log(`[${new Date().toISOString()}] \u26A0 Disconnected, reconnecting... ${go(e)}`),R({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}`),R({level:"info",type:"ws",wsEvent:e.method,wsDirection:"in",traceId:e.traceId}),d.handleReq(e)}});T=Be()?new Ve(l):null,b=new We({registry:f,observations:C,actionClient:S,getMachineCredentials:()=>({machineToken:p().machineToken}),onMessages:async(e,i,n,t)=>{await k?.processMessages(e,i,n,t)},onBindingPaused:e=>{I.revokeSession(e.registryKey),d?.discardPendingManagedRoomMessages(e.nianSessionId)}});const P=new Ne({currentVersion:u,machineId:c.machineId??"unknown-machine",serverUrl:c.serverUrl??h,getIdleState:()=>d.getUpgradeIdleState(),startUpgrade:async e=>{const{handleUpgradeStart:i}=await import("./commands/upgrade.js");await i(l,"auto-upgrade",e,{currentVersion:u})}});P.setPolicy(H),d=new Pe(l,T,u,P,new Ke({registry:f,observations:C,bindingClient:S,getMachineCredentials:()=>{const e=p();return{machineId:e.machineId,machineToken:e.machineToken}},hostProcessIdentity:()=>`daemon:${g}`}),{prepare:()=>we(),isDisabled:()=>Y(),shutdownAfterResponse:()=>{setTimeout(()=>{ye(),process.kill(process.pid,"SIGTERM")},100).unref?.()}},e=>I.revokeToken(e),B),k=new Ye({registry:f,observations:C,enqueueConversationBatch:e=>d.enqueueManagedRoomBatch(e),loadContext:async e=>{const i=p().machineToken?.trim(),n=e.binding;if(!i||!n)throw new Error("managed_room_context_unavailable");const t=await S.status({machineToken:i,roomId:n.roomId,bindingId:n.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,i)=>{const n=p().machineToken;if(!n)throw new Error("machine_not_authorized");return I.issue({registryKey:e,machineCredential:n,activationCauseMessageId:i})},audit:new qe}),d.setManagedRoomTurnPreparer(e=>k.prepareConversationTurn(e)),d.setManagedAgentDirectTurnPreparer(e=>k.prepareDirectConversationTurn(e)),$.mkdirSync(oe,{recursive:!0}),q(process.pid,g,{version:u}),Q(process.pid,void 0,g),l.connect(),process.stdin.resume();let F=!1;const z=async()=>{if(!F){F=!0,console.log(y.gray(`
3
- Disconnecting...`)),R({level:"info",wsEvent:"daemon.stop"}),clearInterval(a);try{await d.cleanup(),P.stop(),T?.stop(),b?.stop(),l.disconnect(),await D.close(),j(process.pid,g),W(g)}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(()=>x()),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(()=>x()),s.command("room-tool-server",{hidden:!0}).description("(internal) Run the official Shennian Room MCP tool").allowUnknownOption(!0).action(()=>x());const te=s.command("config").description("View or modify configuration");te.command("show",{isDefault:!0}).description("Show current configuration").action(()=>{console.log(`Config file: ${me()}`);const o=p(),r=ue(o,K(process.env,o));if(o.serverUrl){const a=Oe(o.serverUrl);console.log(`Region: ${a} (${_[a].label})`)}console.log(JSON.stringify(r,null,2))}),te.command("set").description("Update a config value").argument("<key>",'Config key (e.g. "server")').argument("<value>",'Config value (e.g. "cn" or "global")').action((o,r)=>{const a=p();if(o==="server"){r!=="cn"&&r!=="global"&&(console.error(y.red('\u2717 Value must be "cn" or "global"')),process.exit(1));const m=r;a.serverUrl=Me(m),ce(a),console.log(y.green(`\u2713 Server set to ${_[m].label}`)),console.log(y.yellow(" Restart the background service for this to take effect: shennian stop && shennian start"));return}console.error(y.red(`\u2717 Unknown config key: ${o}. Supported: server`)),process.exit(1)}),he(s),Se(s),Ce(s),ke(s),De(s),Ee(s),Te(s),s.parse();
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(`
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();
@@ -15,4 +15,27 @@ type LogEntry = {
15
15
  export declare function initCliLogReporter(serverUrl: string, machineId: string): void;
16
16
  export declare function generateTraceId(): string;
17
17
  export declare function reportLog(entry: LogEntry): void;
18
+ export type UpgradeAggregateMetric = {
19
+ kind: 'upgrade';
20
+ result: 'healthy' | 'rolled_back' | 'failed';
21
+ stage: 'installing' | 'verifying' | 'starting' | 'recovering' | 'terminal';
22
+ durationMs?: number;
23
+ retries?: number;
24
+ errorCode?: string;
25
+ };
26
+ export type RecoveryAggregateMetric = {
27
+ kind: 'recovery';
28
+ result: 'terminal_reconciled' | 'reattached' | 'resumed' | 'continued' | 'interrupted_by_upgrade' | 'recovery_failed';
29
+ adapterType: string;
30
+ stage: 'inspect' | 'reattach' | 'resume' | 'continue' | 'terminal';
31
+ durationMs: number;
32
+ retries: number;
33
+ errorCode?: string;
34
+ };
35
+ /**
36
+ * The general remote logger remains disabled. This deliberately narrow path
37
+ * emits only enumerated aggregate dimensions: its type cannot accept session,
38
+ * prompt, filesystem, attachment, tool, token, or stderr fields.
39
+ */
40
+ export declare function reportUpgradeAggregateMetric(metric: UpgradeAggregateMetric | RecoveryAggregateMetric): void;
18
41
  export {};
@@ -1 +1 @@
1
- import n from"node:crypto";let e,t;function p(r,o){e=r.replace(/\/$/,""),t=o}function c(){return n.randomBytes(8).toString("hex")}function a(r){}export{c as generateTraceId,p as initCliLogReporter,a as reportLog};
1
+ import s from"node:crypto";let o,t;function l(e,r){o=e.replace(/\/$/,""),t=r}function p(){return s.randomBytes(8).toString("hex")}function c(e){}function n(e,r){return/^[A-Za-z0-9:_.-]{1,64}$/.test(e)?e:r}function f(e){if(!o)return;const r=Math.max(0,Math.min(864e5,Math.floor(e.durationMs??0))),a=Math.max(0,Math.min(100,Math.floor(e.retries??0))),d=e.kind==="recovery"?"upgrade.recovery.outcome":"upgrade.outcome",i={kind:e.kind,result:e.result,stage:e.stage,durationMs:r,retries:a,...e.kind==="recovery"?{adapterType:n(e.adapterType,"unknown")}:{},...e.errorCode?{errorCode:n(e.errorCode,"unknown")}:{}};fetch(`${o}/log`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({traceId:p(),source:"cli",type:"internal",level:e.result==="recovery_failed"||e.result==="failed"?"warn":"info",wsEvent:d,durationMs:r,metadata:i})}).catch(()=>{})}export{p as generateTraceId,l as initCliLogReporter,c as reportLog,f as reportUpgradeAggregateMetric};
@@ -10,6 +10,7 @@ export type CliRelayOptions = {
10
10
  sessionSyncCapabilities?: readonly SessionSyncCapability[];
11
11
  daemonInstallationId?: string;
12
12
  dataEpoch?: number;
13
+ updaterProtocolVersion?: number;
13
14
  onReq?: (req: ReqFrame) => void;
14
15
  onConnected?: () => void;
15
16
  onDisconnected?: (info: CliRelayDisconnectInfo) => void;